feat: implement core linting logic, rule analyzers, and CLI tooling w…#2
Merged
Conversation
…hile removing deprecated project files.
There was a problem hiding this comment.
Pull request overview
This PR introduces a new SharpLinter architecture centered around a reusable SharpLinter.Core library (syntax-tree-based analyzers, configuration, providers, and output formatters) and a SharpLinter.Cli global tool, while removing the legacy LinterProject.* API/test artifacts.
Changes:
- Adds the core lint engine, built-in/custom rule analyzers, configuration model, rule providers (bundled/cache/Microsoft Learn), and output formatters (console/json/sarif/msbuild).
- Adds a System.CommandLine-based CLI with
analyze,format,init,rules, andsynccommands plus a CI workflow to build/test/pack. - Replaces old projects with new xUnit-based tests and a client verification project.
Reviewed changes
Copilot reviewed 77 out of 77 changed files in this pull request and generated 11 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/SharpLinter.Core.Tests/SharpLinter.Core.Tests.csproj | New xUnit test project for core + CLI integration tests. |
| tests/SharpLinter.Core.Tests/Rules/BuiltIn/AnalyzerTests.cs | Adds unit tests for selected analyzers and custom rules loader. |
| tests/SharpLinter.Core.Tests/CliTests.cs | Adds CLI command smoke tests via Program.Main. |
| tests/SharpLinter.ClientTest/SharpLinter.ClientTest.csproj | Adds a “consumer” project intended to validate usage/build-time integration. |
| tests/SharpLinter.ClientTest/Program.cs | Sample code with intentional violations for validation. |
| tests/SharpLinter.ClientTest/nuget.config | Custom NuGet feed config for the client test project. |
| src/SharpLinter.Core/SharpLinter.Core.csproj | New core library project with packaging metadata and dependencies. |
| src/SharpLinter.Core/Rules/RuleMetadata.cs | Introduces rule metadata model used across analyzers/providers. |
| src/SharpLinter.Core/Rules/RuleCategory.cs | Defines rule categories for filtering/grouping. |
| src/SharpLinter.Core/Rules/IRuleAnalyzer.cs | Defines analyzer interface contract for built-in/custom analyzers. |
| src/SharpLinter.Core/Rules/Custom/PatternRuleAnalyzer.cs | Implements regex-based custom “pattern” rules. |
| src/SharpLinter.Core/Rules/Custom/NamingRuleAnalyzer.cs | Implements regex-based custom “naming” rules. |
| src/SharpLinter.Core/Rules/Custom/MetricRuleAnalyzer.cs | Implements threshold-based custom “metric” rules. |
| src/SharpLinter.Core/Rules/Custom/CustomRuleLoader.cs | YAML parsing and analyzer creation for custom rules. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1012_StringConcatInLoopAnalyzer.cs | Adds SL1012 built-in analyzer for string concat in loops. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1011_PatternMatchingAnalyzer.cs | Adds SL1011 built-in analyzer for pattern matching suggestions. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1010_FileLengthAnalyzer.cs | Adds SL1010 built-in analyzer for file length. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1009_TrailingWhitespaceAnalyzer.cs | Adds SL1009 built-in analyzer for trailing whitespace. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1008_ConsistentBracePlacementAnalyzer | Adds SL1008 built-in analyzer for brace placement consistency. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1007_CyclomaticComplexityAnalyzer.cs | Adds SL1007 built-in analyzer for cyclomatic complexity. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1006_UnusedUsingAnalyzer.cs | Adds SL1006 built-in analyzer for unused using directives (heuristic). |
| src/SharpLinter.Core/Rules/BuiltIn/SL1005_NamingConventionAnalyzer.cs | Adds SL1005 built-in analyzer for naming conventions. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1004_MethodLengthAnalyzer.cs | Adds SL1004 built-in analyzer for method length. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1003_PublicFieldAnalyzer.cs | Adds SL1003 built-in analyzer for public fields. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1002_EmptyCatchBlockAnalyzer.cs | Adds SL1002 built-in analyzer for empty catch blocks. |
| src/SharpLinter.Core/Rules/BuiltIn/SL1001_AddBracesAnalyzer.cs | Adds SL1001 built-in analyzer for missing braces. |
| src/SharpLinter.Core/Providers/RuleCacheManager.cs | Adds JSON cache manager for provider-fetched rule metadata. |
| src/SharpLinter.Core/Providers/MicrosoftLearnRuleProvider.cs | Adds Microsoft Learn HTML table scraper to build CA/IDE catalog. |
| src/SharpLinter.Core/Providers/IRuleProvider.cs | Provider interface abstraction. |
| src/SharpLinter.Core/Providers/BundledRuleCatalog.cs | Adds embedded “offline-first” bundled rule catalog provider. |
| src/SharpLinter.Core/Providers/bundled-rules.json | Embedded bundled rules snapshot. |
| src/SharpLinter.Core/Output/SarifOutputFormatter.cs | SARIF v2.1.0 output formatter. |
| src/SharpLinter.Core/Output/MsBuildOutputFormatter.cs | MSBuild-style output formatter. |
| src/SharpLinter.Core/Output/JsonOutputFormatter.cs | JSON output formatter. |
| src/SharpLinter.Core/Output/IOutputFormatter.cs | Output formatter abstraction. |
| src/SharpLinter.Core/Output/ConsoleOutputFormatter.cs | Console (colored) output formatter. |
| src/SharpLinter.Core/Formatting/CodeFormatter.cs | Roslyn-based formatting wrapper. |
| src/SharpLinter.Core/Configuration/RuleOverride.cs | Rule override model (severity + options). |
| src/SharpLinter.Core/Configuration/Presets.cs | Built-in presets for rule severities/options. |
| src/SharpLinter.Core/Configuration/LintConfiguration.cs | Config model + load/discover + option accessors. |
| src/SharpLinter.Core/Analysis/LintSeverity.cs | Severity enum. |
| src/SharpLinter.Core/Analysis/LintResult.cs | Lint run result model (diagnostics + counts + duration). |
| src/SharpLinter.Core/Analysis/LintEngine.cs | Core orchestrator (loads analyzers, scans files, runs analysis/format). |
| src/SharpLinter.Core/Analysis/LintDiagnostic.cs | Diagnostic model (rule/message/location/severity). |
| src/SharpLinter.Cli/SharpLinter.Cli.csproj | CLI tool project with packaging/tooling metadata. |
| src/SharpLinter.Cli/Program.cs | CLI entrypoint wiring commands. |
| src/SharpLinter.Cli/Commands/SyncCommand.cs | Implements sync command. |
| src/SharpLinter.Cli/Commands/RulesCommand.cs | Implements rules command including cache/bundled display. |
| src/SharpLinter.Cli/Commands/InitCommand.cs | Implements init command to generate config templates. |
| src/SharpLinter.Cli/Commands/FormatCommand.cs | Implements format command using the formatter wrapper. |
| src/SharpLinter.Cli/Commands/AnalyzeCommand.cs | Implements analyze command and output selection. |
| SharpLinter.slnx | New solution definition including src + tests. |
| README.md | Adds product/usage documentation. |
| LICENSE | Adds MIT license. |
| docs/rules.md | Documents built-in rules catalog. |
| docs/custom-rules.md | Documents YAML-based custom rules schema. |
| .sharplinter.rules.yaml | Adds sample custom rules file. |
| .sharplinter.json | Adds default config pointing to custom rules file. |
| .github/workflows/ci.yml | Adds CI build/test/pack workflow. |
| LinterProject.Tests/Resources/Input/SampleCode_2.txt | Removes deprecated legacy test fixture input. |
| LinterProject.Tests/Resources/Input/SampleCode_1.txt | Removes deprecated legacy test fixture input. |
| LinterProject.Tests/Resources/Expected/SampleCode_2.txt | Removes deprecated legacy expected output fixture. |
| LinterProject.Tests/Resources/Expected/SampleCode_1.txt | Removes deprecated legacy expected output fixture. |
| LinterProject.Tests/LinterProject.Test.csproj | Removes legacy test project. |
| LinterProject.Tests/LinterProject - Backup.Test.csproj | Removes legacy backup test project file. |
| LinterProject.Tests/CodeAnalysisServiceTests.cs | Removes legacy NUnit test suite. |
| LinterProject.sln | Removes legacy solution. |
| LinterProject.API/SyntaxRewriters/AddBracesRewriter.cs | Removes legacy API implementation detail. |
| LinterProject.API/Services/CodeAnalysisService.cs | Removes legacy API formatter service. |
| LinterProject.API/Properties/launchSettings.json | Removes legacy API launch settings. |
| LinterProject.API/Program.cs | Removes legacy API host. |
| LinterProject.API/LinterProject.API.csproj | Removes legacy API project. |
| LinterProject.API/Interfaces/ICodeAnalysisService.cs | Removes legacy API interface. |
| LinterProject.API/ExceptionHandlingMiddleware.cs | Removes legacy API middleware. |
| LinterProject.API/Controllers/CodeAnalysisController.cs | Removes legacy API controller. |
| LinterProject.API/appsettings.json | Removes legacy API config. |
| LinterProject.API/appsettings.Development.json | Removes legacy API config. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+224
to
+231
| foreach (var pattern in _config.Exclude) | ||
| { | ||
| var normalizedPattern = pattern.Replace("**/", ""); | ||
| if (relativePath.Contains(normalizedPattern.Trim('*', '/'))) | ||
| { | ||
| return true; | ||
| } | ||
| } |
Comment on lines
+94
to
+117
| private void AnalyzeNumericLiterals(SyntaxNode root, string filePath, LintSeverity severity, List<LintDiagnostic> diagnostics) | ||
| { | ||
| var excludeSet = _config?.Exclude?.Select(e => e.ToString()).ToHashSet() ?? []; | ||
|
|
||
| foreach (var literal in root.DescendantNodes().OfType<LiteralExpressionSyntax>()) | ||
| { | ||
| if (literal.IsKind(SyntaxKind.NumericLiteralExpression)) | ||
| { | ||
| var text = literal.Token.ValueText; | ||
| if (!excludeSet.Contains(text)) | ||
| { | ||
| // Only check within method bodies if scope is method-body | ||
| if (_config?.Scope == "method-body") | ||
| { | ||
| var inMethod = literal.Ancestors().Any(a => | ||
| a is MethodDeclarationSyntax || a is ConstructorDeclarationSyntax); | ||
| if (!inMethod) continue; | ||
| } | ||
|
|
||
| AddDiagnostic(literal.GetLocation(), filePath, severity, diagnostics); | ||
| } | ||
| } | ||
| } | ||
| } |
Comment on lines
+72
to
+80
| // Check for: str += "something" or str += variable | ||
| if (assignment.IsKind(SyntaxKind.AddAssignmentExpression)) | ||
| { | ||
| // Heuristic: if the right side is a string literal, interpolated string, | ||
| // or the left side is commonly named like a string builder pattern | ||
| if (IsLikelyStringExpression(assignment.Right) || IsLikelyStringExpression(assignment.Left)) | ||
| { | ||
| ReportDiagnostic(assignment); | ||
| } |
Comment on lines
+80
to
+90
| public static LintConfiguration LoadFromFile(string filePath) | ||
| { | ||
| if (!File.Exists(filePath)) | ||
| { | ||
| return Presets.GetPreset("recommended"); | ||
| } | ||
|
|
||
| var json = File.ReadAllText(filePath); | ||
| var config = JsonSerializer.Deserialize<LintConfiguration>(json, JsonOptions); | ||
| return config ?? Presets.GetPreset("recommended"); | ||
| } |
Comment on lines
+177
to
+183
| "preset": "recommended", | ||
| "rules": { | ||
| "SL1001": "error", | ||
| "SL1004": { "severity": "warning", "maxLines": 60 }, | ||
| "SL1007": { "severity": "suggestion", "maxComplexity": 15 }, | ||
| "SL1010": "off" | ||
| }, |
|
|
||
| ### SL1006: Potentially unused using directives | ||
| - **Default Severity:** Suggestion | ||
| - **Auto-Fixable:** Yes (via format command) |
Comment on lines
+10
to
+13
| <ItemGroup> | ||
| <!-- Consume our packed NuGet package --> | ||
| <PackageReference Include="SharpLinter" Version="1.0.0" /> | ||
| </ItemGroup> |
Comment on lines
+60
to
+65
| // Act | ||
| var exitCode = await Program.Main(args); | ||
|
|
||
| // Assert | ||
| // Since we have a warning/error (SL1005 naming convention and SL1001 braces), exit code will depend on rules | ||
| var output = sw.ToString(); |
| diagnostics.Should().ContainSingle(); | ||
| diagnostics[0].RuleId.Should().Be("CUSTOM999"); | ||
| } | ||
| } |
Comment on lines
+59
to
+60
| ### 3. Automatically Format & Fix Issues | ||
| Auto-fix fixable issues (like adding missing control flow braces or stripping trailing whitespaces): |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
…hile removing deprecated project files.