Token导航 LogoToken导航

Agent Skill · 前端设计

dotnet-roslyn-analyzers(dotnet roslyn 分析器)

dotnet-roslyn-analyzers 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

dotnet-roslyn-analyzers 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证信息还不明确,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 5,854。

总安装 588周安装 24GitHub Stars 16下载 190核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
unknown

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:dotnet-roslyn-analyzers(dotnet roslyn 分析器)
来源仓库:https://github.com/wshaddix/dotnet-skills
仓库路径:skills/dotnet-roslyn-analyzers
安装命令:
npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-roslyn-analyzers
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-roslyn-analyzers

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

指导自定义 Roslyn 分析器开发,包括诊断提供者和代码修复。适用于创建代码规范检查、多版本兼容和 NuGet 打包流程。通过 GitHub 安装,需遵循 DiagnosticDescriptor 约定和测试规范。消费现有分析器时应参考 dotnet-quality 技能获取配置指导。

dotnet-roslyn-analyzers 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/dotnet-roslyn-analyzers

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-roslyn-a…;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

dotnet-roslyn-analyzers 怎么安装?

在终端执行 npx skills add https://github.com/wshaddix/dotnet-skills --skill dotnet-roslyn-analyzers。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

dotnet-roslyn-analyzers 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

dotnet-roslyn-analyzers 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

dotnet-roslyn-analyzers 是免费的吗?

公开资料没有标注明确许可证,收费和商用限制都无法确认。正式使用前请到来源仓库核对授权说明。

dotnet-roslyn-analyzers 用的人多吗?

公开数据是累计安装 588 次,近一周安装 24 次,下载 190 次,GitHub 16 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

dotnet-roslyn-analyzers 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 许可证没有明确标注,能不能商用、能不能再分发都不确定。
  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。

不太适合

  • 需要明确授权才能用到公司项目里的场景。
  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
dotnet-roslyn-analyzers

dotnet roslyn 分析器 · 本页

58816需要联网未明确通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

dotnet-roslyn-analyzers

Guidance for authoring custom Roslyn analyzers, code fix providers, code refactoring providers, and diagnostic suppressors. Covers project setup, DiagnosticDescriptor conventions, analysis context registration, code fix actions, code refactoring actions, multi-Roslyn-version targeting (3.8 through 4.14), testing with Microsoft.CodeAnalysis.Testing, NuGet packaging, and performance best practices.

Scope boundary: This skill covers *writing* analyzers. For *consuming and configuring* existing analyzers (CA rules, EditorConfig severity, third-party packages), see [skill:dotnet-add-analyzers]. For *authoring source generators* (IIncrementalGenerator, syntax providers, code emission), see [skill:dotnet-csharp-source-generators]. Analyzers and source generators share the same NuGet packaging layout (analyzers/dotnet/cs/) and Microsoft.CodeAnalysis.CSharp dependency, but serve different purposes: analyzers report diagnostics, generators emit code.

Cross-references: [skill:dotnet-csharp-source-generators] for shared Roslyn packaging concepts and IIncrementalGenerator patterns, [skill:dotnet-add-analyzers] for consuming and configuring analyzers, [skill:dotnet-testing-strategy] for general test organization and framework selection, [skill:dotnet-csharp-coding-standards] for naming conventions applied to analyzer code.


Project Setup

Analyzer projects must target netstandard2.0. The compiler loads analyzers into various host processes (Visual Studio on.NET Framework/Mono, MSBuild on.NET Core, dotnet build CLI) -- targeting net8.0+ breaks compatibility with hosts that do not run on that runtime.

<Project Sdk="Microsoft.NET.Sdk">
  <PropertyGroup>
    <TargetFramework>netstandard2.0</TargetFramework>
    <EnforceExtendedAnalyzerRules>true</EnforceExtendedAnalyzerRules>
    <IsRoslynComponent>true</IsRoslynComponent>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <!-- NuGet: Microsoft.CodeAnalysis.CSharp -->
    <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.12.0" PrivateAssets="all" />
    <!-- NuGet: Microsoft.CodeAnalysis.Analyzers (meta-diagnostics for analyzer authors) -->
    <PackageReference Include="Microsoft.CodeAnalysis.Analyzers" Version="3.11.0" PrivateAssets="all" />
  </ItemGroup>
</Project>
  • EnforceExtendedAnalyzerRules enables RS-series meta-diagnostics that catch common analyzer authoring mistakes (see Meta-Diagnostics section below).
  • IsRoslynComponent enables IDE tooling support for the project.
  • LangVersion>latest lets you write modern C# in the analyzer itself while still targeting netstandard2.0.
  • All Roslyn SDK packages must use PrivateAssets="all" to avoid shipping them as transitive dependencies.

DiagnosticAnalyzer

Every analyzer inherits from DiagnosticAnalyzer and must be decorated with [DiagnosticAnalyzer(LanguageNames.CSharp)].

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class NoPublicFieldsAnalyzer : DiagnosticAnalyzer
{
    // Diagnostic ID uses project prefix + sequential number
    public const string DiagnosticId = "MYLIB001";

    private static readonly DiagnosticDescriptor Rule = new(
        id: DiagnosticId,
        title: "Public fields should be properties",
        messageFormat: "Field '{0}' is public; use a property instead",
        category: "Design",
        defaultSeverity: DiagnosticSeverity.Warning,
        isEnabledByDefault: true,
        helpLinkUri: $"https://example.com/docs/rules/{DiagnosticId}");

    // Return an ImmutableArray -- allocating a new array per call is RS1030-adjacent waste
    public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
        = ImmutableArray.Create(Rule);

    public override void Initialize(AnalysisContext context)
    {
        // Required: enable concurrent execution and generated code analysis config
        context.EnableConcurrentExecution();
        context.ConfigureGeneratedCodeAnalysis(
            GeneratedCodeAnalysisFlags.None);

        context.RegisterSymbolAction(AnalyzeField, SymbolKind.Field);
    }

    private static void AnalyzeField(SymbolAnalysisContext context)
    {
        var field = (IFieldSymbol)context.Symbol;

        if (field.DeclaredAccessibility == Accessibility.Public
            && !field.IsConst
            && !field.IsReadOnly)
        {
            var diagnostic = Diagnostic.Create(
                Rule,
                field.Locations[0],
                field.Name);

            context.ReportDiagnostic(diagnostic);
        }
    }
}

Analysis Context Registration

Choose the most appropriate registration method for your analysis:

MethodGranularityUse When
RegisterSyntaxNodeActionIndividual syntax nodesPattern matching on specific syntax (e.g., if statements, method declarations)
RegisterSymbolActionDeclared symbolsChecking symbol-level properties (accessibility, type, attributes)
RegisterOperationActionIL-level operationsAnalyzing semantic operations (assignments, invocations) independent of syntax
RegisterSyntaxTreeActionEntire syntax treeFile-level checks (e.g., missing headers, encoding)
RegisterCompilationStartActionCompilation startWhen you need to accumulate state across the compilation, then report at end
RegisterCompilationActionFull compilationOne-shot analysis after all files are compiled
// RegisterSyntaxNodeAction -- analyze specific syntax nodes
context.RegisterSyntaxNodeAction(
    AnalyzeInvocation,
    SyntaxKind.InvocationExpression);

// RegisterOperationAction -- analyze semantic operations
context.RegisterOperationAction(
    AnalyzeAssignment,
    OperationKind.SimpleAssignment);

// RegisterCompilationStartAction -- accumulate state, then report at compilation end
context.RegisterCompilationStartAction(compilationContext =>
{
    // Resolve types once at compilation start
    var disposableType = compilationContext.Compilation
        .GetTypeByMetadataName("System.IDisposable");

    if (disposableType is null)
        return;

    compilationContext.RegisterSymbolAction(
        ctx => AnalyzeTypeDisposal(ctx, disposableType),
        SymbolKind.NamedType);
});

DiagnosticDescriptor Conventions

Follow these conventions for all custom analyzers:

ID Prefix Patterns

Use a short, unique prefix derived from your project or library name, followed by a sequential number:

PatternExampleWhen
PROJ###MYLIB001Single-project analyzers
AREA####PERF0001Category-scoped analyzers (performance, security)
XX####MA0042Short-prefix convention (e.g., Meziantou.Analyzer)

Avoid prefixes reserved by the.NET platform: CA (code analysis), CS (compiler), RS (Roslyn SDK), IDE (code style), IL (linker), SYSLIB (runtime). Include the namespace in the ID constant to prevent collisions when multiple analyzer packages are installed.

Category Naming

Use standard.NET analysis categories where applicable:

Design, Globalization, Interoperability, Maintainability, Naming, Performance, Reliability, Security, Style, Usage

For domain-specific categories, use a clear, titlecase name (e.g., EntityFramework, AspNetCore).

Severity Selection

SeverityUse When
ErrorCode will not work correctly at runtime (null deref, SQL injection, resource leak)
WarningCode works but violates best practices or has performance issues
InfoSuggestion for improvement, not a defect
HiddenIDE-only refactoring suggestion, not shown in build output

Default to Warning for most rules. Use Error sparingly -- users cannot suppress errors via EditorConfig without disabling the rule entirely.

helpLinkUri

Always provide a non-null helpLinkUri (RS1015 enforces this). Point to stable documentation:

helpLinkUri: $"https://github.com/myorg/mylib/blob/main/docs/rules/{DiagnosticId}.md"

CodeFixProvider

Code fix providers offer automated corrections for diagnostics. They inherit from CodeFixProvider and register fixes for specific diagnostic IDs.

using System.Collections.Immutable;
using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeFixes;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[ExportCodeFixProvider(LanguageNames.CSharp, Name = nameof(NoPublicFieldsCodeFixProvider))]
[Shared]
public sealed class NoPublicFieldsCodeFixProvider : CodeFixProvider
{
    public override ImmutableArray<string> FixableDiagnosticIds { get; }
        = ImmutableArray.Create(NoPublicFieldsAnalyzer.DiagnosticId);

    // Enable FixAll support -- RS1016 flags missing FixAllProvider
    public override FixAllProvider GetFixAllProvider()
        => WellKnownFixAllProviders.BatchFixer;

    public override async Task RegisterCodeFixesAsync(CodeFixContext context)
    {
        var root = await context.Document
            .GetSyntaxRootAsync(context.CancellationToken)
            .ConfigureAwait(false);

        var diagnostic = context.Diagnostics[0];
        var diagnosticSpan = diagnostic.Location.SourceSpan;

        var fieldDeclaration = root?.FindToken(diagnosticSpan.Start)
            .Parent?
            .AncestorsAndSelf()
            .OfType<FieldDeclarationSyntax>()
            .FirstOrDefault();

        if (fieldDeclaration is null)
            return;

        // EquivalenceKey enables FixAll grouping -- RS1010/RS1011 require unique keys
        context.RegisterCodeFix(
            CodeAction.Create(
                title: "Convert to property",
                createChangedDocument: ct =>
                    ConvertToPropertyAsync(context.Document, fieldDeclaration, ct),
                equivalenceKey: "ConvertToProperty"),
            diagnostic);
    }

    private static async Task<Document> ConvertToPropertyAsync(
        Document document,
        FieldDeclarationSyntax fieldDeclaration,
        CancellationToken cancellationToken)
    {
        var root = await document
            .GetSyntaxRootAsync(cancellationToken)
            .ConfigureAwait(false);

        var variable = fieldDeclaration.Declaration.Variables[0];
        var propertyName = variable.Identifier.Text;

        // Build auto-property with same type and accessibility
        var property = SyntaxFactory.PropertyDeclaration(
                fieldDeclaration.Declaration.Type,
                propertyName)
            .WithModifiers(fieldDeclaration.Modifiers)
            .WithAccessorList(
                SyntaxFactory.AccessorList(
                    SyntaxFactory.List(new[]
                    {
                        SyntaxFactory.AccessorDeclaration(SyntaxKind.GetAccessorDeclaration)
                            .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken)),
                        SyntaxFactory.AccessorDeclaration(SyntaxKind.SetAccessorDeclaration)
                            .WithSemicolonToken(SyntaxFactory.Token(SyntaxKind.SemicolonToken))
                    })))
            .WithLeadingTrivia(fieldDeclaration.GetLeadingTrivia())
            .WithTrailingTrivia(fieldDeclaration.GetTrailingTrivia());

        var newRoot = root!.ReplaceNode(fieldDeclaration, property);
        return document.WithSyntaxRoot(newRoot);
    }
}

Key CodeFixProvider Patterns

  • EquivalenceKey: Every CodeAction must have a unique equivalenceKey for FixAll support (RS1010, RS1011). Use the fix description or diagnostic ID as the key.
  • Document vs. Solution modification: Use createChangedDocument when the fix modifies a single file. Use createChangedSolution when the fix must rename symbols across files or add new files.
  • Trivia preservation: Always transfer leading/trailing trivia from replaced nodes to maintain formatting and comments.
  • FixAllProvider: Return WellKnownFixAllProviders.BatchFixer for batch-applicable fixes. Omit only for fixes that require user interaction or have cross-fix dependencies.

DiagnosticSuppressor

A DiagnosticSuppressor conditionally suppresses diagnostics from other analyzers. Use this when your codebase has a pattern that a third-party analyzer flags incorrectly, and EditorConfig cannot express the suppression condition.

using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.Diagnostics;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class CustomNullCheckSuppressor : DiagnosticSuppressor
{
    private static readonly SuppressionDescriptor SuppressCA1062 = new(
        id: "MYLIB_SUP001",
        suppressedDiagnosticId: "CA1062",
        justification: "Parameter is validated by custom Guard.NotNull helper");

    public override ImmutableArray<SuppressionDescriptor> SupportedSuppressions { get; }
        = ImmutableArray.Create(SuppressCA1062);

    public override void ReportSuppressions(SuppressionAnalysisContext context)
    {
        foreach (var diagnostic in context.ReportedDiagnostics)
        {
            var tree = diagnostic.Location.SourceTree;
            if (tree is null)
                continue;

            var root = tree.GetRoot(context.CancellationToken);
            var node = root.FindNode(diagnostic.Location.SourceSpan);

            // Walk up to the containing method and check for Guard.NotNull call
            var method = node.FirstAncestorOrSelf<Microsoft.CodeAnalysis.CSharp.Syntax.MethodDeclarationSyntax>();
            if (method is null)
                continue;

            var methodText = method.ToFullString();

            // Simplified check -- production code should use semantic analysis
            if (methodText.Contains("Guard.NotNull"))
            {
                context.ReportSuppression(
                    Suppression.Create(SuppressCA1062, diagnostic));
            }
        }
    }
}

When to Use DiagnosticSuppressor vs. EditorConfig

ApproachUse When
EditorConfig severity overrideSuppression applies unconditionally to all instances of a rule
[SuppressMessage] attributeSuppression applies to a specific code location with a justification
DiagnosticSuppressorSuppression depends on code structure or patterns (e.g., custom validation, code generation markers)

Suppressors cannot report new diagnostics -- they can only suppress existing ones. They participate in the same analyzer pipeline and follow the same netstandard2.0 targeting requirements.

Version gate: DiagnosticSuppressor requires Roslyn 3.8+. If your analyzer package targets older Roslyn versions via multi-version packaging, guard suppressor registration behind #if ROSLYN_3_8_OR_GREATER (see Multi-Roslyn-Version Targeting below).

CodeRefactoringProvider

A CodeRefactoringProvider offers code transformations triggered by the user (lightbulb menu) without requiring a diagnostic. Use this for structural improvements, pattern applications, or code generation that are not defects.

using System.Composition;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp.Syntax;

[ExportCodeRefactoringProvider(LanguageNames.CSharp, Name = nameof(ExtractInterfaceRefactoring))]
[Shared]
public sealed class ExtractInterfaceRefactoring : CodeRefactoringProvider
{
    public override async Task ComputeRefactoringsAsync(CodeRefactoringContext context)
    {
        var root = await context.Document
            .GetSyntaxRootAsync(context.CancellationToken)
            .ConfigureAwait(false);

        var node = root?.FindNode(context.Span);
        var classDecl = node?.FirstAncestorOrSelf<ClassDeclarationSyntax>();

        if (classDecl is null)
            return;

        // Only offer when cursor is on the class identifier
        if (!classDecl.Identifier.Span.IntersectsWith(context.Span))
            return;

        context.RegisterRefactoring(
            CodeAction.Create(
                title: $"Extract interface I{classDecl.Identifier.Text}",
                createChangedSolution: ct =>
                    ExtractInterfaceAsync(context.Document, classDecl, ct),
                equivalenceKey: "ExtractInterface"));
    }

    private static async Task<Solution> ExtractInterfaceAsync(
        Document document,
        ClassDeclarationSyntax classDecl,
        CancellationToken cancellationToken)
    {
        // ... build interface from public method signatures, add base type
        // See details.md for complete implementation
        throw new NotImplementedException();
    }
}

See details.md for the complete ExtractInterfaceAsync implementation (which adds a methods parameter for the extracted method list) and CSharpCodeRefactoringVerifier<T> test examples.

Key CodeRefactoringProvider Patterns

  • Span check: Only offer refactorings when the cursor/selection intersects the relevant node. Broad span matching clutters the lightbulb menu.
  • No diagnostic required: Unlike CodeFixProvider, refactoring providers do not fix a diagnostic -- they offer optional transformations.
  • Solution-level changes: Use createChangedSolution when the refactoring adds files, renames symbols, or modifies multiple documents.
  • Attribute requirements: Decorate with [ExportCodeRefactoringProvider(LanguageNames.CSharp)] and [Shared] (MEF). The provider is not registered via SupportedDiagnostics.
  • Testing: Use CSharpCodeRefactoringVerifier<T> from Microsoft.CodeAnalysis.CSharp.CodeRefactoring.Testing (see details.md).

Multi-Roslyn-Version Targeting

Analyzer NuGet packages can ship multiple DLLs targeting different Roslyn versions, allowing the analyzer to use newer APIs when available while maintaining compatibility with older compilers.

Version Boundaries

The Roslyn SDK uses these version boundaries for multi-targeting:

Roslyn VersionShips WithKey APIs Added
3.8VS 16.8 /.NET 5 SDKDiagnosticSuppressor, IOperation improvements
4.2VS 17.2 /.NET 6 SDKRegisterHostObjectAction, improved incremental analysis
4.4VS 17.4 /.NET 7 SDKSyntaxValueProvider.ForAttributeWithMetadataName (generators)
4.6VS 17.6 /.NET 8 SDKInterceptors preview, enhanced IOperation nodes
4.8VS 17.8 /.NET 8 U1CollectionExpression syntax support
4.14VS 17.14 /.NET 10 SDKLatest API surface

Project Configuration (Meziantou.Analyzer Pattern)

Define a $(RoslynVersion) MSBuild property and reference Microsoft.CodeAnalysis.CSharp using Version="$(RoslynVersion).0". For multi-version builds, use Directory.Build.targets to parameterize the Roslyn version across build configurations (see details.md for the full project structure).

Conditional Compilation Constants

Define constants following the ROSLYN_X_Y and ROSLYN_X_Y_OR_GREATER pattern in Directory.Build.targets:

<PropertyGroup Condition="'$(RoslynVersion)' >= '3.8'">
  <DefineConstants>$(DefineConstants);ROSLYN_3_8;ROSLYN_3_8_OR_GREATER</DefineConstants>
</PropertyGroup>
<!-- Repeat for 4.2, 4.4, 4.6, 4.8, 4.14 -- see details.md for all six -->

Use these constants to guard version-specific code:

#if ROSLYN_4_8_OR_GREATER
    // CollectionExpression operation kind available in Roslyn 4.8+
    context.RegisterOperationAction(AnalyzeCollectionExpression,
        OperationKind.CollectionExpression);
#endif

NuGet Packaging Paths

Multi-version analyzers use version-specific NuGet paths: analyzers/dotnet/roslyn{version}/cs/ for each version, plus analyzers/dotnet/cs/ as the fallback for hosts below 3.8. The host selects the DLL from the highest matching roslyn{version} directory. Use <None Include="..." Pack="true" PackagePath="analyzers/dotnet/roslyn{version}/cs" /> items to place each build in its correct path. See details.md for the complete packaging.csproj and pack verification commands.

Multi-Version Test Matrix

Test each Roslyn version build independently by parameterizing $(RoslynVersion) in the test project. Use xUnit v3 with Microsoft.Testing.Platform v2 (MTP2) for the test runner:

# Build and test each Roslyn version (xUnit v3 + MTP2)
for version in 3.8 4.2 4.4; do
  dotnet test -p:RoslynVersion=$version
done

See details.md for a complete multi-version project structure (Directory.Build.props, Directory.Build.targets, packaging.csproj, and GitHub Actions CI matrix with xUnit v3).


Testing Analyzers

Use the Microsoft.CodeAnalysis.Testing infrastructure for ergonomic, high-level analyzer testing. This is preferred over raw CSharpCompilation testing.

Required NuGet Packages

<PropertyGroup>
  <!-- Enable Microsoft.Testing.Platform v2 runner -->
  <UseMicrosoftTestingPlatformRunner>true</UseMicrosoftTestingPlatformRunner>
</PropertyGroup>

<ItemGroup>
  <!-- NuGet: Microsoft.CodeAnalysis.CSharp.Analyzer.Testing (framework-agnostic, uses DefaultVerifier) -->
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Analyzer.Testing" Version="1.1.3" />
  <!-- NuGet: Microsoft.CodeAnalysis.CSharp.CodeFix.Testing -->
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp.CodeFix.Testing" Version="1.1.3" />
  <!-- NuGet: Microsoft.CodeAnalysis.CSharp.Workspaces (dependency) -->
  <PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="4.12.0" />
  <!-- NuGet: xunit.v3 (xUnit v3 test framework) -->
  <PackageReference Include="xunit.v3" Version="3.2.2" />
</ItemGroup>
Migration note: The framework-specific packages (e.g., Microsoft.CodeAnalysis.CSharp.Analyzer.Testing.XUnit) are obsolete. Use the generic packages with DefaultVerifier instead. This decouples Roslyn testing infrastructure from the test framework, making it compatible with xUnit v3 and Microsoft.Testing.Platform v2 (MTP2).

Analyzer-Only Testing

Use CSharpAnalyzerVerifier<T> to test that diagnostics are reported (or not reported) correctly:

using Microsoft.CodeAnalysis.Testing;
using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpAnalyzerVerifier<
    NoPublicFieldsAnalyzer,
    Microsoft.CodeAnalysis.Testing.DefaultVerifier>;

public class NoPublicFieldsAnalyzerTests
{
    [Fact]
    public async Task PublicField_ReportsDiagnostic()
    {
        var test = """
            public class MyClass
            {
                public int {|MYLIB001:Value|};
            }
            """;

        await Verify.VerifyAnalyzerAsync(test);
    }

    [Fact]
    public async Task PrivateField_NoDiagnostic()
    {
        var test = """
            public class MyClass
            {
                private int _value;
            }
            """;

        await Verify.VerifyAnalyzerAsync(test);
    }

    // Also test: public const fields (no diagnostic), public readonly fields (no diagnostic)
}

Diagnostic Markup Syntax

The testing framework uses markup to indicate expected diagnostic locations:

MarkupMeaning
`[text
`{DIAG_ID:text
`{DIAG_ID:text{

Analyzer + CodeFix Testing

Use CSharpCodeFixVerifier<TAnalyzer, TCodeFix> to test the full analyzer-to-fix pipeline:

using Verify = Microsoft.CodeAnalysis.CSharp.Testing.CSharpCodeFixVerifier<
    NoPublicFieldsAnalyzer,
    NoPublicFieldsCodeFixProvider,
    Microsoft.CodeAnalysis.Testing.DefaultVerifier>;

public class NoPublicFieldsCodeFixTests
{
    [Fact]
    public async Task PublicField_FixConvertsToProperty()
    {
        var test = """
            public class MyClass
            {
                public int {|MYLIB001:Value|};
            }
            """;

        var fixedCode = """
            public class MyClass
            {
                public int Value { get; set; }
            }
            """;

        await Verify.VerifyCodeFixAsync(test, fixedCode);
    }
}

Multi-File Test Scenarios

For multi-file tests, use the Verify.Test class with TestState.Sources to add multiple named source files. Set expected diagnostics in each file using the standard markup syntax. Use test.RunAsync() instead of the static VerifyAnalyzerAsync shorthand.


NuGet Packaging

Analyzers are shipped as NuGet packages with a specific directory layout. The assemblies go under analyzers/dotnet/cs/, not lib/.

Package Layout

MyAnalyzers.nupkg
  analyzers/
    dotnet/
      cs/
        MyAnalyzers.dll           # Analyzer assembly
        MyAnalyzers.CodeFixes.dll # Code fix assembly (optional, separate)
  lib/
    netstandard2.0/
      _._                        # Empty placeholder (no runtime dependency)

Project Configuration

<!-- Analyzer .csproj -->
<PropertyGroup>
  <IncludeBuildOutput>false</IncludeBuildOutput>
  <DevelopmentDependency>true</DevelopmentDependency>
  <SuppressDependenciesWhenPacking>true</SuppressDependenciesWhenPacking>
</PropertyGroup>

<ItemGroup>
  <None Include="$(OutputPath)\$(AssemblyName).dll"
        Pack="true"
        PackagePath="analyzers/dotnet/cs" />
</ItemGroup>

Separate Analyzer and Code Fix Assemblies

For multi-analyzer NuGet packages, separating analyzers from code fixes improves IDE load time. The IDE loads code fix assemblies lazily (only when the user requests a fix), while analyzer assemblies load immediately. Create two projects (MyAnalyzers and MyAnalyzers.CodeFixes), then pack both DLLs into analyzers/dotnet/cs/ using <None Include="..." Pack="true" PackagePath="analyzers/dotnet/cs" /> items.

Pack Verification

After packing, verify the layout with unzip -l./bin/Release/MyAnalyzers.1.0.0.nupkg | grep analyzers/ (nupkg files are zip archives). See [skill:dotnet-csharp-source-generators] for additional shared packaging concepts (the analyzers/dotnet/cs/ layout is identical for both analyzers and source generators).


Performance Best Practices

Analyzers run in real-time during editing. Poor performance degrades the IDE experience for every user of the analyzer.

Allocation-Free Callbacks

Avoid allocations in hot-path callbacks. Every RegisterSyntaxNodeAction callback runs per-node, potentially thousands of times per keystroke:

// BAD: resolves type on every symbol callback (per-symbol overhead)
context.RegisterSymbolAction(symbolCtx =>
{
    // GetTypeByMetadataName is called for EVERY symbol in the compilation
    var disposableType = symbolCtx.Compilation
        .GetTypeByMetadataName("System.IDisposable");
    if (disposableType is null) return;

    Analyze(symbolCtx, disposableType);
}, SymbolKind.NamedType);

// GOOD: resolve state once per compilation, closure allocated once
context.RegisterCompilationStartAction(compilationCtx =>
{
    var importantType = compilationCtx.Compilation
        .GetTypeByMetadataName("System.IDisposable");
    if (importantType is null) return;

    // One closure per compilation -- acceptable cost
    compilationCtx.RegisterSymbolAction(
        symbolCtx => Analyze(symbolCtx, importantType),
        SymbolKind.NamedType);
});
Note: A single closure allocated once per compilation inside RegisterCompilationStartAction is acceptable. The anti-pattern is resolving types or allocating closures inside per-node or per-symbol callbacks, where the cost multiplies across thousands of invocations.

Symbol-Based Filtering

Prefer symbol-based analysis over syntax-based analysis when both are viable. Symbol analysis operates on the compiler's resolved model and avoids re-parsing:

// Prefer: RegisterSymbolAction for declared-symbol checks
context.RegisterSymbolAction(ctx =>
{
    var method = (IMethodSymbol)ctx.Symbol;
    if (method.ReturnsVoid && method.Parameters.Length == 0) { /* ... */ }
}, SymbolKind.Method);

// Avoid: RegisterSyntaxNodeAction when symbol analysis suffices
// Syntax analysis requires manually resolving types and handling aliases

ImmutableArray for SupportedDiagnostics

Cache SupportedDiagnostics as an ImmutableArray<DiagnosticDescriptor> field. The runtime calls this property frequently:

// GOOD: single allocation, cached
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics { get; }
    = ImmutableArray.Create(Rule1, Rule2, Rule3);

// BAD: allocates a new array on every access
public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics
    => ImmutableArray.Create(Rule1, Rule2, Rule3);

Additional Performance Guidelines

  • Enable concurrent execution: Always call context.EnableConcurrentExecution() unless your analyzer has shared mutable state (which it should not).
  • Avoid per-compilation state in fields: Do not store per-compilation data in analyzer instance fields (RS1008). Use RegisterCompilationStartAction to scope state to a single compilation.
  • Filter early in syntax predicates: When using RegisterSyntaxNodeAction, register for the most specific SyntaxKind possible to minimize callback frequency.
  • Avoid Compilation.GetSemanticModel(): Use the SemanticModel provided by the analysis context instead (RS1030).

Common Meta-Diagnostics (RS-Series)

The Microsoft.CodeAnalysis.Analyzers package reports RS-series diagnostics on your analyzer code itself. These are invaluable for catching authoring mistakes at compile time. Enable them via EnforceExtendedAnalyzerRules in your project file.

Frequently Encountered RS Diagnostics

IDTitleWhat It Catches
RS1001Missing DiagnosticAnalyzerAttributeAnalyzer class missing [DiagnosticAnalyzer(LanguageNames.CSharp)]
RS1004Recommend adding language supportAnalyzer supports C# but not VB (informational)
RS1007Provide localizable argumentsDiagnosticDescriptor strings should use LocalizableResourceString
RS1008Avoid storing per-compilation dataInstance fields holding compilation-specific data break concurrent execution
RS1010Create code actions with unique EquivalenceKeyCodeAction missing equivalence key for FixAll support
RS1015Provide non-null helpLinkUriDiagnosticDescriptor has null or empty help link
RS1016Code fix providers should provide FixAll supportMissing GetFixAllProvider() override
RS1017DiagnosticId must be non-null constantDiagnostic ID is not a compile-time constant
RS1022Do not use Workspaces assembly typesAnalyzer references Microsoft.CodeAnalysis.Workspaces (not available in all hosts)
RS1024Symbols should be compared for equalityUsing == instead of SymbolEqualityComparer for symbol comparison
RS1026Enable concurrent executionMissing context.EnableConcurrentExecution() call
RS1029Do not use reserved diagnostic IDsUsing CA, CS, IDE, or other platform-reserved prefixes
RS1030Do not invoke Compilation.GetSemanticModel()Use the SemanticModel from the analysis context instead
RS1035Do not use APIs banned for analyzersUsing APIs not available in all analyzer host environments
RS1041Compiler extensions should target netstandard2.0Project targets a framework other than netstandard2.0

Release Tracking (RS2000-Series)

For mature analyzers, enable release tracking to manage diagnostic ID lifecycle:

IDTitleWhat It Catches
RS2000Add analyzer diagnostic IDs to analyzer releaseNew diagnostic ID not tracked in release file
RS2001Ensure up-to-date entry for analyzer diagnostic IDsRelease tracking file is out of sync
RS2008Enable analyzer release trackingProject should opt into release tracking

Release tracking uses AnalyzerReleases.Shipped.md and AnalyzerReleases.Unshipped.md files to track which diagnostic IDs have been published.


References

平台分布

Codex

32.87%
按下载量换算62

Claude

30.07%
按下载量换算57

Cursor

19.24%
按下载量换算37

Gemini CLI

9.88%
按下载量换算19

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。目前只有一个公开来源,建议同时核对源仓库。

继续浏览同类 Skills