Six custom code analyzers for AL (Microsoft Dynamics 365 Business Central), built on the Microsoft.Dynamics.Nav.CodeAnalysis SDK (the "NAV SDK"). Each cop is a .NET project under src/ with a sibling *.Test project; ALCops.Common is the shared library (it also hosts the cross-cutting CM diagnostics); ALCops.Analyzers is a CI-only NuGet meta-package (not in the .sln).
| Project | Prefix | Help URI slug | CodeFixes |
|---|---|---|---|
ALCops.ApplicationCop |
AC |
applicationcop |
yes |
ALCops.DocumentationCop |
DC |
documentationcop |
no |
ALCops.FormattingCop |
FC |
formattingcop |
yes |
ALCops.LinterCop |
LC |
lintercop |
yes |
ALCops.PlatformCop |
PC |
platformcop |
yes |
ALCops.TestAutomationCop |
TA |
testautomationCop (sic, matches descriptors) |
no |
ALCops.Common |
CM |
common |
no |
Per cop: DiagnosticIds.cs, DiagnosticDescriptors.cs, ALCops.{Cop}Analyzers.resx (messages; generates a strongly-typed class at build), Analyzers/{RuleName}.cs, CodeFixes/{RuleName}CodeFixProvider.cs. Tests: src/ALCops.{Cop}.Test/Rules/{RuleName}/{RuleName}.cs + HasDiagnostic/, NoDiagnostic/, HasFix/ .al fixtures.
dotnet build ALCops.sln
dotnet test ALCops.sln
dotnet test src/ALCops.LinterCop.Test/ --filter "FullyQualifiedName~{RuleName}"
dotnet test src/ALCops.LinterCop.Test/ --filter "FullyQualifiedName~{RuleName}.HasDiagnostic"-
Shared MSBuild settings live in
Directory.Build.props; package versions inDirectory.Packages.props(Central Package Management - noVersion=onPackageReference). -
Static analysis (NetAnalyzers,
Microsoft.CodeAnalysis.Analyzers, Roslynator,.editorconfigcode style) is enforced: every analyzer warning is a build error, anddotnet format ALCops.sln --verify-no-changesis a blocking CI gate. Suppressions live in.editorconfigwith a reason. See.claude/rules/code-analysis.md. -
Requires BC Dev Tools at
../../Microsoft.Dynamics.BusinessCentral.Development.Tools(repo-root relative) or/p:BcDevToolsDir=<path>..vscode/Setup-BCDevTools.ps1downloads them. -
Local builds target
net10.0only. CI (ContinuousIntegrationBuild=true) buildsnetstandard2.1;net8.0;net10.0because BC ships the SDK in all three. Test projects targetnet10.0and switch viaNavTargetFramework. -
Before merging, build every cop for all three TFMs locally:
dotnet build src/ALCops.{Cop}/ALCops.{Cop}.csproj -c Release -p:ContinuousIntegrationBuild=true --no-incremental— analyzers evaluate one compilation at a time, so#if-guarded code only surfaces there. -
Nullable warnings
CS8600;CS8602;CS8603;CS8604;CS8605are errors.
- Consult the
nav-sdk-docsplugin first, then the decompiled NAV SDK source, before using any SDK API./nav-sdk-docs:sdk-lookupgives the per-version availability and the documented behaviour;../nav-sdk-sourceis the evidence for what the docs do not cover. Syntax kinds, operation shapes, and symbol members are undocumented and version-dependent. See.claude/rules/analyzer-development.md(§NAV SDK source) and.claude/rules/symbol-resolution.md. - Every analyzer must compile on
netstandard2.1. Guard newer C# features and missing SDK APIs; net8.0-only analyzers compile as empty stubs under#if NETSTANDARD2_1. See.claude/rules/netstandard21-compatibility.md. - Never assume analyzer callback ordering or that every callback runs (the host's partial-analysis module pass skips all per-declaration callbacks). No two-phase accumulator patterns. See
.claude/rules/sdk-analysis-scope.md. - Analyzers extend plain
DiagnosticAnalyzer. Do not switch them to theALCopsDiagnosticAnalyzer/{Cop}Analyzerexception harness: deriving from a Common-based type makesalcfail withAL1003(issue #389). The harness stays test-only until a loader-safe approach exists. See.claude/rules/analyzer-exception-harness.md. ALCopsSettings.csandalcops.schema.jsonmust stay in sync; a parity test enforces it. See.claude/rules/settings-schema.md.- Diagnostic IDs are
{Prefix}{4 digits}, sequential per cop. Help URI:https://alcops.dev/docs/analyzers/{copslug}/{id}/. Every new rule needs a page in the sibling docs repo (../alcops.dev,content/docs/analyzers/{copslug}/{ID}.md). - Resx keys:
{RuleName}Title,{RuleName}MessageFormat,{RuleName}Description. Descriptor field,DiagnosticIdsfield, analyzer class, and test folder all share the rule name.
mainis protected — never commit to it. Branch frommain:feat/<desc>,fix/<desc>,docs/<desc>,chore/<desc>;release/vX.Y.Zfor release stabilization. Open PRs withgh pr create; CI runs build + tests.- Commit messages: conventional commits scoped by rule ID —
feat(LC0095): …,fix(PC0021): …,test(FC0002): …,docs: …,chore: …. - Code comments and XML docs must be self-contained: explain the mechanism, never cite issue or PR numbers. Deep context and issue links belong in the rule's
.claude/rules/diagnostics/{id}-{slug}.md. Docs and config describe current state only — no PR numbers anywhere. - Bug fixes start with a failing regression fixture (
NoDiagnostic/for false positives,HasDiagnostic/for false negatives) before touching the analyzer. - Run
/code-reviewbefore opening a PR. It readsREVIEW.md(house rules plus the NAV SDK checklist); fix or justify every correctness finding. - Releases use GitVersion with alpha/beta/stable channels. See
.claude/rules/release-strategy.md; use/release.
- New rule → create
.claude/rules/diagnostics/{id}-{slug}.mdfrom.claude/skills/new-analyzer/references/rule-doc.md. New CodeFix → add a## CodeFixsection to that file. Changed or added design decision, deliberate non-report, non-obvious workaround, or accepted limitation → update the matching section. Deferred ideas go to a GitHub issue, not a Roadmap section. - New shared component or convention → new
.claude/rules/<area>.mdwith apaths:frontmatter scoped as narrowly as possible. Never add a rules file withoutpaths:(it would load in every session). - Rules files document why, not what: no diagnostic-property tables, test-case lists, or file inventories — the code is the source of truth for those.
- Knowledge needed whenever you edit matching files lives in
.claude/rules/; procedural templates and checklists used only while running a skill live in.claude/skills/*/references/. Never keep the same content in both — leave a pointer. - A new house convention that reviewers must enforce also gets a row in
REVIEW.md(house section); the SDK section there is a copy of nav-sdk-docstemplates/REVIEW.mdand is updated from that repository, not edited here. - Before opening a PR that touches
.claude/, runpwsh .claude/scripts/Validate-Rules.ps1: it checks frontmatter, livepaths:globs, rule-doc sections, link targets, and stale-fact patterns in the general guides. - If none of this applies to a change, say "No
.claudedoc changes needed" in the plan.
.claude/rules/*.md— path-scoped guides, auto-loaded when you touch matching files: analyzer development (core rules), SDK analysis scope (how callbacks run), symbol resolution, record receiver forms, analyzer performance, CodeFixes, testing, Common library, exception harness, record-method classification, settings schema, netstandard2.1, code analysis, release strategy, BC DevTools action..claude/rules/diagnostics/{id}-{slug}.md— one file per rule: purpose, design decisions, deliberate non-reports, known issues, SDK facts, CodeFix decisions.- Skills:
/new-analyzer <ID> <ClassName> <Cop>,/new-codefix <ID>,/fix-false-positive <issue-or-description>,/release. From thenav-sdk-docsplugin (auto-enabled by.claude/settings.jsonafter trusting the folder):/nav-sdk-docs:sdk-lookup <question>,/nav-sdk-docs:write-sdk-doc <folder>/<page>. REVIEW.md— what the built-in/code-reviewchecks on this repository: house rules with pointers into.claude/rules/, then the NAV SDK checklist copied from nav-sdk-docs.