feat: add FFS0049 — detect unpermitted SuppressMessage attributes#422
Conversation
bcec434 to
ce6dc65
Compare
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
There was a problem hiding this comment.
Pull request overview
Adds a new analyzer rule (FFS0049) to restrict [SuppressMessage] usage to an explicit allowlist, making unauthorized suppressions fail fast before justification-related checks run.
Changes:
- Introduces FFS0049 (
SuppressMessage is not permitted for '{checkId}') and short-circuits further suppression validation when a suppression is not allowlisted. - Adds an allowlist mechanism (
AllowedSuppressions) supporting(category, checkIdPrefix)matching plus a context-awareWhenAllowedpredicate. - Updates unit tests to expect FFS0049 for non-allowlisted suppressions and to validate the initial allowlisted Roslynator case.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
| File | Description |
|---|---|
| src/FunFair.CodeAnalysis/SuppressMessageDiagnosticsAnalyzer.cs | Adds FFS0049, allowlist/predicate evaluation, and early-return behavior before justification checks. |
| src/FunFair.CodeAnalysis/Helpers/Rules.cs | Adds the new rule ID constant FFS0049. |
| src/FunFair.CodeAnalysis.Tests/SuppressMessageDiagnosticsAnalyzerTests.cs | Updates existing tests for new behavior and adds coverage for allowlisted vs non-allowlisted suppressions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| p.Modifiers.Any(m => m.IsKind(SyntaxKind.ParamsKeyword)) && IsReadOnlySpanType(p.Type) | ||
| ); | ||
| } | ||
| ), | ||
| ]; | ||
|
|
||
| private static bool IsReadOnlySpanType(TypeSyntax? type) | ||
| { | ||
| return type switch | ||
| { | ||
| GenericNameSyntax generic => | ||
| StringComparer.Ordinal.Equals(x: generic.Identifier.Text, y: "ReadOnlySpan"), | ||
| QualifiedNameSyntax { Right: GenericNameSyntax rightGeneric } => | ||
| StringComparer.Ordinal.Equals(x: rightGeneric.Identifier.Text, y: "ReadOnlySpan"), | ||
| _ => false, | ||
| }; |
| attributeSyntax.ReportDiagnostics( | ||
| syntaxNodeAnalysisContext: syntaxNodeAnalysisContext, | ||
| rule: RuleNotPermitted, | ||
| checkId ?? string.Empty |
| if (named?.Expression is LiteralExpressionSyntax namedLit) | ||
| { | ||
| return namedLit.Token.ValueText; | ||
| } |
| if (positional?.Expression is LiteralExpressionSyntax posLit) | ||
| { | ||
| return posLit.Token.ValueText; | ||
| } |
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
Agent-Logs-Url: https://github.com/funfair-tech/funfair-server-code-analysis/sessions/fa2d4473-ba67-4ec5-8d3f-1470d8c494e1 Co-authored-by: credfeto <1020430+credfeto@users.noreply.github.com>
…ing in FFS0049 Agent-Logs-Url: https://github.com/funfair-tech/funfair-server-code-analysis/sessions/fa2d4473-ba67-4ec5-8d3f-1470d8c494e1 Co-authored-by: credfeto <1020430+credfeto@users.noreply.github.com>
… in FFS0049 Prompt: can you whitelist this rule so its allowed anywhere: [SuppressMessage(category: "Nullable.Extended.Analyzer", checkId: "NX0001: Suppression of NullForgiving operator is not required", Justification = "Required here")]
…e SuppressMessage
…hods and classes Prompt: any method with the Benchmark attribute is allowed to suppress [SuppressMessage(category: "codecracker.CSharp", checkId: "CC0091:MarkMembersAsStatic", Justification = "Benchmark")] [SuppressMessage(category: "Microsoft.Performance", checkId: "CA1822:MarkMembersAsStatic", Justification = "Benchmark")] and any class that has methods with Benchmark attribute on them are allowed [SuppressMessage(category: "FunFair.CodeAnalysis", checkId: "FFS0012: Make sealed static or abstract", Justification = "Benchmark")]
3c601be to
9380561
Compare
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
- Added FFS0049, NX0001, CC0091/CA1822/FFS0012 entries to CHANGELOG - Fixed Ordinal comparison entry in CHANGELOG - Updated solution to include .editorconfig and .globalconfig; removed CodeAnalysis.ruleset Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
- IsReadOnlySpanType now uses semantic model (GetTypeInfo) to verify System.ReadOnlySpan`1 instead of syntax identifier text matching - GetStringAttributeArgument now uses SemanticModel.GetConstantValue so const fields and nameof() expressions are correctly resolved - Fallback checkId in FFS0049 diagnostic changed from empty string to '<unknown>' for clearer error messages Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
…icsAnalyzer - Extract GetStringAttributeArgument to AttributeArgumentHelpers.GetStringArgument as a reusable helper for any analyzer needing to read attribute argument values - Simplify IsReadOnlySpanType to use ToFullyQualifiedName() from TypeSymbolHelpers instead of two separate MetadataName + namespace checks - Cache SupportedDiagnostics in SupportedDiagnosticsCache static field, consistent with other multi-rule analyzers in the project Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Baseline (net9.0 Release): - NoSuppressionAsync: 560 B - AllowedSuppressionAsync: 560 B - DisallowedSuppressionAsync: 592 B Thresholds set at 1024 B (~1.8x headroom). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Super-linter summary
All files and directories linted successfully For more information, see the GitHub Actions workflow run Powered by Super-linter |
[SuppressMessage]could be used freely on any member as long as it had aJustification, with no enforcement of which warnings are permitted to be suppressed.Changes
SuppressMessage is not permitted for '{checkId}'): fires when[SuppressMessage]is used for a(category, checkId)not in an explicit allowlistAllowedSuppressiontype: each entry carriesCategory,CheckIdPrefix, and aWhenAllowedpredicate receiving the fullSyntaxNodeAnalysisContext, enabling location-aware conditionsInitial allowlist entry
RCS1231(Roslynator.Analyzers) is permitted only when the suppressed method has aparams ReadOnlySpan<T>parameter — the one case where the analyzer fires incorrectly (you can't make aparamsspan ref-readonly):Extending the allowlist requires adding a single
AllowedSuppressionentry toAllowedSuppressions. CheckId prefix matching handles both short form ("RCS1231") and description form ("RCS1231: Spans should be ref read-only"), using case-sensitive ordinal comparison.