Skip to content

feat(analyzers): add AL0018 to warn when Version.props not imported - #27

Merged
ANcpLua merged 1 commit into
mainfrom
feat/al0018-version-props-import
Jan 20, 2026
Merged

feat(analyzers): add AL0018 to warn when Version.props not imported#27
ANcpLua merged 1 commit into
mainfrom
feat/al0018-version-props-import

Conversation

@ANcpLua

@ANcpLua ANcpLua commented Jan 20, 2026

Copy link
Copy Markdown
Owner

Summary

  • Add new analyzer AL0018 that reports when Directory.Build.props doesn't import Version.props
  • Enforces centralized version management pattern used with AL0017
  • Checks for Import elements referencing Version.props (supports various path formats)

Test plan

  • ShouldReportWhenVersionPropsNotImported - warns when no import exists
  • ShouldNotReportWhenVersionPropsImported - no warning with proper import
  • ShouldNotReportWhenVersionPropsImportedWithPath - handles relative paths
  • ShouldNotReportForOtherPropsFiles - only checks Directory.Build.props

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added AL0018 code analyzer that warns when Version.props is not imported in Directory.Build.props files, helping ensure consistent version management practices across builds.
  • Tests

    • Added comprehensive test coverage for the AL0018 analyzer.

✏️ Tip: You can customize this high-level summary in your review settings.

Copilot AI review requested due to automatic review settings January 20, 2026 21:11
@coderabbitai

coderabbitai Bot commented Jan 20, 2026

Copy link
Copy Markdown
Contributor

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

A new Roslyn analyzer (AL0018) is introduced to enforce importing Version.props in Directory.Build.props files. It scans XML files for the required import element, reports diagnostics when missing, and includes localized resource strings and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Analyzer Implementation
src/ANcpLua.Analyzers/Analyzers/AL0018VersionPropsNotImportedAnalyzer.cs
New sealed diagnostic analyzer that scans AdditionalFiles for Directory.Build.props, parses XML content, detects absence of Version.props Import element (case-insensitive), and reports diagnostic at file start. Gracefully handles XML parsing failures via exception handling.
Resource Strings
src/ANcpLua.Analyzers/Resources.Designer.cs, src/ANcpLua.Analyzers/Resources.resx
Added three resource accessors and entries (AL0018AnalyzerTitle, AL0018AnalyzerMessageFormat, AL0018AnalyzerDescription) for localized diagnostic messaging regarding Version.props import requirement.
Test Suite
tests/ANcpLua.Analyzers.Tests/AL0018AnalyzerTests.cs
New test class with five Fact tests validating: diagnostic reported when Version.props not imported, no diagnostic when imported (including relative paths), and no false positives for other .props files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title clearly and specifically summarizes the main change: adding a new analyzer AL0018 that warns when Version.props is not imported, which directly matches the implementation across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist

Copy link
Copy Markdown

Summary of Changes

Hello @ANcpLua, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a new C# diagnostic analyzer, AL0018, aimed at promoting best practices for centralized version management in .NET projects. The analyzer identifies 'Directory.Build.props' files that do not explicitly import 'Version.props', a common pattern for defining package versions across a solution. By flagging these omissions, it helps ensure consistency and simplifies version updates, aligning with existing version management patterns.

Highlights

  • New Analyzer AL0018 Introduced: A new diagnostic analyzer, AL0018, has been added to enforce centralized version management by warning when 'Directory.Build.props' does not import 'Version.props'.
  • XML Parsing for Import Detection: The analyzer parses the XML content of 'Directory.Build.props' files to identify '' elements that reference 'Version.props', supporting various path formats.
  • Resource String Updates: Corresponding resource strings for AL0018 (title, message, description) have been added to 'Resources.resx' and 'Resources.Designer.cs'. Resource strings for AL0026 and AL0027 were also added in the same commit.
  • Comprehensive Unit Testing: Dedicated unit tests have been implemented to validate the analyzer's behavior, covering scenarios where 'Version.props' is missing, correctly imported (directly and with relative paths), and ensuring it only applies to 'Directory.Build.props' files.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new analyzer, AL0018, to ensure Version.props is imported in Directory.Build.props for centralized version management. The implementation is solid, and the accompanying tests cover the main scenarios. I've provided feedback to enhance the analyzer's correctness, improve maintainability, and address minor performance considerations. My suggestions include refining the import detection logic to prevent false negatives, adding a test case for this refinement, and improving code clarity and organization.

Comment on lines +77 to +79
.Any(static import =>
import.Attribute("Project") is { Value: { } projectValue } &&
projectValue.Contains(VersionPropsFileName, StringComparison.OrdinalIgnoreCase));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The current logic using string.Contains is too broad and can lead to false negatives. For example, it would incorrectly match an import for a file named MyVersion.props. To make the check more precise, you should verify that the filename part of the Project attribute's value is exactly Version.props.

You can achieve this using System.IO.Path.GetFileName(). This change will also require making the lambda non-static to access VersionPropsFileName.

                .Any(import =>
                    import.Attribute("Project") is { Value: { } projectValue } &&
                    System.IO.Path.GetFileName(projectValue).Equals(VersionPropsFileName, StringComparison.OrdinalIgnoreCase));

Comment on lines +56 to +57
.Where(static f => f.Path.EndsWith(DirectoryBuildPropsFileName, StringComparison.OrdinalIgnoreCase))
.ToList();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The .ToList() call materializes the collection into a list, which causes an unnecessary memory allocation. The foreach loop can iterate directly over the IEnumerable<T> returned by Where. Removing this call will make the code slightly more efficient.

            .Where(static f => f.Path.EndsWith(DirectoryBuildPropsFileName, StringComparison.OrdinalIgnoreCase));

Comment on lines +83 to +86
var location = Location.Create(propsFile.Path, sourceText.Lines[0].Span,
new Microsoft.CodeAnalysis.Text.LinePositionSpan(
new Microsoft.CodeAnalysis.Text.LinePosition(0, 0),
new Microsoft.CodeAnalysis.Text.LinePosition(0, 0)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The creation of the Location object can be simplified. The current implementation uses the TextSpan of the entire first line (sourceText.Lines[0].Span) but a zero-length LinePositionSpan. This is slightly inconsistent.

For clarity, it's better to use a zero-length TextSpan at the start of the file to match the zero-length LinePositionSpan.

                var location = Location.Create(propsFile.Path, 
                    new Microsoft.CodeAnalysis.Text.TextSpan(0, 0),
                    new Microsoft.CodeAnalysis.Text.LinePositionSpan(
                        new Microsoft.CodeAnalysis.Text.LinePosition(0, 0),
                        new Microsoft.CodeAnalysis.Text.LinePosition(0, 0)));

var diagnostic = Diagnostic.Create(Rule, location);
context.ReportDiagnostic(diagnostic);
}
} catch (Exception) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Catching the generic System.Exception can hide other unexpected issues. It's better to catch the more specific System.Xml.XmlException that XDocument.Parse throws on failure. This ensures that you are only handling expected parsing errors and not masking other potential bugs.

        } catch (System.Xml.XmlException) {

Comment on lines +287 to +296
<!-- AL0018: Version.props not imported -->
<data name="AL0018AnalyzerTitle" xml:space="preserve">
<value>Version.props not imported</value>
</data>
<data name="AL0018AnalyzerMessageFormat" xml:space="preserve">
<value>Directory.Build.props should import Version.props for centralized version management</value>
</data>
<data name="AL0018AnalyzerDescription" xml:space="preserve">
<value>Version.props should be imported in Directory.Build.props to enable centralized package version management using $(VariableName) syntax.</value>
</data>

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

For better maintainability and consistency, the resource entries for AL0018 should be grouped with other version management-related analyzers. Please move this block to be after the entries for AL0017 (around line 206). After reordering and saving, the Resources.Designer.cs file will be regenerated with the correct order as well.

// No diagnostics expected - only Directory.Build.props is checked
await test.RunAsync(TestContext.Current.CancellationToken);
}
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

To ensure the analyzer's logic is robust against false negatives, it would be beneficial to add a test case that checks for imports of files with names similar to Version.props (e.g., MyVersion.props). This test should verify that a diagnostic is correctly reported in such a scenario.

    [Fact]
    public async Task ShouldReportWhenSimilarButIncorrectPropsImported() {
        var directoryBuildProps = """
            <Project>
                <Import Project="MyVersion.props" />
            </Project>
            """

        var test = new CSharpAnalyzerTest<Al0018VersionPropsNotImportedAnalyzer, DefaultVerifier> {
            TestCode = EmptyCode,
            TestState = { AdditionalFiles = { ("Directory.Build.props", directoryBuildProps) } }
        };

        test.ExpectedDiagnostics.Add(
            new DiagnosticResult(Al0018VersionPropsNotImportedAnalyzer.DiagnosticId, DiagnosticSeverity.Warning)
                .WithLocation("Directory.Build.props", 1, 1));

        await test.RunAsync(TestContext.Current.CancellationToken);
    }
}

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new analyzer AL0018 that enforces centralized version management by warning when Directory.Build.props files don't import Version.props. This works in conjunction with AL0017 to ensure version properties are properly defined and imported.

Changes:

  • Adds AL0018VersionPropsNotImportedAnalyzer to detect missing Version.props imports
  • Adds comprehensive test coverage with 4 test scenarios
  • Adds resource strings for diagnostic messages

Reviewed changes

Copilot reviewed 3 out of 4 changed files in this pull request and generated 3 comments.

File Description
src/ANcpLua.Analyzers/Analyzers/AL0018VersionPropsNotImportedAnalyzer.cs New analyzer implementation that checks Directory.Build.props files for Version.props import statements
tests/ANcpLua.Analyzers.Tests/AL0018AnalyzerTests.cs Comprehensive test coverage including positive/negative cases and edge cases
src/ANcpLua.Analyzers/Resources.resx Adds AL0018 resource strings (also includes unrelated AL0026/AL0027 entries)
src/ANcpLua.Analyzers/Resources.Designer.cs Generated resource accessors for AL0018 (also includes unrelated AL0026/AL0027 entries)
Files not reviewed (1)
  • src/ANcpLua.Analyzers/Resources.Designer.cs: Language not supported

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +414 to +443
internal static string AL0026AnalyzerMessageFormat {
get {
return ResourceManager.GetString("AL0026AnalyzerMessageFormat", resourceCulture);
}
}

internal static string AL0026AnalyzerDescription {
get {
return ResourceManager.GetString("AL0026AnalyzerDescription", resourceCulture);
}
}

internal static string AL0027AnalyzerTitle {
get {
return ResourceManager.GetString("AL0027AnalyzerTitle", resourceCulture);
}
}

internal static string AL0027AnalyzerMessageFormat {
get {
return ResourceManager.GetString("AL0027AnalyzerMessageFormat", resourceCulture);
}
}

internal static string AL0027AnalyzerDescription {
get {
return ResourceManager.GetString("AL0027AnalyzerDescription", resourceCulture);
}
}

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Resources.Designer.cs file includes resource property accessors for AL0026 and AL0027 analyzers (lines 408-442), which are not part of this pull request. These entries should be removed to keep the PR scope focused on AL0018 only.

Suggested change
internal static string AL0026AnalyzerMessageFormat {
get {
return ResourceManager.GetString("AL0026AnalyzerMessageFormat", resourceCulture);
}
}
internal static string AL0026AnalyzerDescription {
get {
return ResourceManager.GetString("AL0026AnalyzerDescription", resourceCulture);
}
}
internal static string AL0027AnalyzerTitle {
get {
return ResourceManager.GetString("AL0027AnalyzerTitle", resourceCulture);
}
}
internal static string AL0027AnalyzerMessageFormat {
get {
return ResourceManager.GetString("AL0027AnalyzerMessageFormat", resourceCulture);
}
}
internal static string AL0027AnalyzerDescription {
get {
return ResourceManager.GetString("AL0027AnalyzerDescription", resourceCulture);
}
}

Copilot uses AI. Check for mistakes.
Comment on lines +287 to +296
<!-- AL0018: Version.props not imported -->
<data name="AL0018AnalyzerTitle" xml:space="preserve">
<value>Version.props not imported</value>
</data>
<data name="AL0018AnalyzerMessageFormat" xml:space="preserve">
<value>Directory.Build.props should import Version.props for centralized version management</value>
</data>
<data name="AL0018AnalyzerDescription" xml:space="preserve">
<value>Version.props should be imported in Directory.Build.props to enable centralized package version management using $(VariableName) syntax.</value>
</data>

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The AL0018 resource entries are placed at the end of the file (after AL0027), but should be placed in numerical order after AL0017 resources. This would improve consistency with the project's organizational structure where analyzer resources are ordered by their numeric IDs.

Copilot uses AI. Check for mistakes.
Comment on lines +267 to +286
<!-- AL0026: Avoid DateTime time accessors -->
<data name="AL0026AnalyzerTitle" xml:space="preserve">
<value>Avoid DateTime time accessors</value>
</data>
<data name="AL0026AnalyzerMessageFormat" xml:space="preserve">
<value>'{0}' should be replaced with TimeProvider.System.GetUtcNow() for better testability</value>
</data>
<data name="AL0026AnalyzerDescription" xml:space="preserve">
<value>DateTime time accessors make code difficult to test. Use TimeProvider.System.GetUtcNow() instead, which can be mocked in tests.</value>
</data>
<!-- AL0027: Avoid legacy JSON library -->
<data name="AL0027AnalyzerTitle" xml:space="preserve">
<value>Avoid legacy JSON library</value>
</data>
<data name="AL0027AnalyzerMessageFormat" xml:space="preserve">
<value>'{0}' is from a legacy JSON library. Use System.Text.Json instead.</value>
</data>
<data name="AL0027AnalyzerDescription" xml:space="preserve">
<value>The legacy JSON library should be replaced with System.Text.Json for better performance and native .NET support.</value>
</data>

Copilot AI Jan 20, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The Resources.resx file includes resource entries for AL0026 and AL0027 analyzers (lines 267-286), which are not part of this pull request. These entries should be removed to keep the PR scope focused on AL0018 only.

Suggested change
<!-- AL0026: Avoid DateTime time accessors -->
<data name="AL0026AnalyzerTitle" xml:space="preserve">
<value>Avoid DateTime time accessors</value>
</data>
<data name="AL0026AnalyzerMessageFormat" xml:space="preserve">
<value>'{0}' should be replaced with TimeProvider.System.GetUtcNow() for better testability</value>
</data>
<data name="AL0026AnalyzerDescription" xml:space="preserve">
<value>DateTime time accessors make code difficult to test. Use TimeProvider.System.GetUtcNow() instead, which can be mocked in tests.</value>
</data>
<!-- AL0027: Avoid legacy JSON library -->
<data name="AL0027AnalyzerTitle" xml:space="preserve">
<value>Avoid legacy JSON library</value>
</data>
<data name="AL0027AnalyzerMessageFormat" xml:space="preserve">
<value>'{0}' is from a legacy JSON library. Use System.Text.Json instead.</value>
</data>
<data name="AL0027AnalyzerDescription" xml:space="preserve">
<value>The legacy JSON library should be replaced with System.Text.Json for better performance and native .NET support.</value>
</data>

Copilot uses AI. Check for mistakes.
Add new analyzer that reports when Directory.Build.props doesn't import
Version.props for centralized version management.

- Checks for Import elements referencing Version.props
- Supports various path formats (Version.props, ../build/Version.props)
- Only checks Directory.Build.props files in AdditionalFiles
- Includes 4 tests covering various scenarios

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@ANcpLua
ANcpLua force-pushed the feat/al0018-version-props-import branch from 9245eb3 to c8596f8 Compare January 20, 2026 21:51
@ANcpLua
ANcpLua merged commit 66c600c into main Jan 20, 2026
10 checks passed
@ANcpLua
ANcpLua deleted the feat/al0018-version-props-import branch January 20, 2026 21:55
ANcpLua added a commit that referenced this pull request May 28, 2026
…e strings (#178)

Closes task #27 ("AL wave: rewrite analyzer docs generator (post-renumber)").

What landed (no AL package version bump — pure doc + internal tools change):

New under tools/ANcpLua.Analyzers.DocsGenerator/ (docs-only, never shipped
in the analyzer DLL because the 2.0.1 analyzer no longer emits AL0xxx;
nothing at runtime needs the rename map):
- AlIdMigrationCatalog.cs — 89 AL0xxx → AL1xxx rename rows hand-transcribed
  from eng/analyzer-renumber-plan.md §2. Validate() asserts structural
  invariants (no duplicate OldId/NewId, every NewId matches ^AL1[0-8]\d{2}$,
  every OldId matches ^AL\d{4}$). No hardcoded ExpectedCount — count is a
  consequence of the invariants, not a property worth asserting on its own.
  Public class (not internal) so the tests project can call Validate() via
  ProjectReference without InternalsVisibleTo — IVT would expose the tools
  Exe's top-level Program and collide with the tests project's own Program
  (CS0433). Public is safe because the tools assembly is never packed.
- MigrationCatalogRenderer.cs — mirrors QYL's CatalogStatistics-driven
  section-array pattern (qyl repo's
  tools/Qyl.OpenTelemetry.SemanticConventions.Analyzers.DocsGenerator/
  MigrationCatalogRenderer.cs:21-186). 6 sections: header, summary,
  completion audit, band breakdown table, Old→New mapping table sorted by
  NewId, regenerate footer.

Orchestrator wiring (DocsGenerator.cs):
- Compute MigrationCatalogStats once at Run() and thread to Audit/Check/
  Generate. Mirrors the qyl repo's CatalogStatistics threading.
- Generate step 3 writes docs/migration-catalog.md.
- Check step 3 enforces drift detection on the same file.
- Audit prints catalog stats (89 renames, 9 bands).

RepoLayout.cs: new MigrationCatalogPath(repoRoot) accessor.

Cosmetic fix in IndexDocsRenderer.cs (4 occurrences):
- Replace post-renumber-stale "AL00xx–AL18xx" with "AL10xx–AL18xx". The
  AL00xx range no longer hosts this analyzer's IDs — sibling packages
  (AotReflection, ExtensibleEnumMirror, DiscriminatedUnion) own AL0xxx
  per eng/analyzer-renumber-plan.md §0. Each replacement has a
  "// renumber: bump if AL bands shift again" signpost above it.
- Added a "See also" link to the new docs/migration-catalog.md.

EnforceIdsRewriter.cs comment: "/// AL00XX:" placeholder → "/// AL####:"
(the actual regex was already AL-prefix-agnostic; only the comment was
stale).

Mandatory unit test (the highest-leverage piece of the whole change):
- tests/ANcpLua.Analyzers.Tests/AnalyzerConventionTests.cs gains
  AlIdMigrationCatalog_StructuralInvariants_Hold which runs Validate()
  on every CI build. Catches hand-transcription drift in the 89-row
  Entries array immediately, instead of only when someone runs --check
  on a dev machine.

Generated docs/migration-catalog.md committed for --check drift gating.

Verification:
- dotnet build tools/ANcpLua.Analyzers.DocsGenerator/ --nologo -warnaserror
  -> 0 warnings, 0 errors
- dotnet build tests/ANcpLua.Analyzers.Tests/ --nologo -warnaserror
  -> 0 warnings, 0 errors
- dotnet run --project tests/ANcpLua.Analyzers.Tests/ -> 758 pass, 0 fail
- dotnet run --project tools/ANcpLua.Analyzers.DocsGenerator/
  -> emits 7 artifacts including the new docs/migration-catalog.md
- dotnet run --project tools/ANcpLua.Analyzers.DocsGenerator/ -- --check
  -> all artifacts up to date (clean drift gate)
- dotnet run --project tools/ANcpLua.Analyzers.DocsGenerator/ -- --audit
  -> 89 renames across 9 bands; counts match renumber-plan §1 exactly
- grep -c AL00xx docs/ANcpLua.Analyzers.md -> 0
- grep -cE '^\| `AL0' docs/migration-catalog.md -> 89
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants