Add --required-files CLI argument with glob pattern matching and solution reference check#43
Conversation
…d README docs Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/a91fd106-0ab8-4a61-966c-d18d183152f2
--required-files CLI argument with glob pattern matching
|
@copilot it should check that these required file are in the slnx (as Correctly adapt relative paths |
…lnx solution Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/2cd8b089-f7a8-46ee-a2c4-ad527da73512
Done in 6e1405e. The check now runs in two stages:
Relative paths inside the |
--required-files CLI argument with glob pattern matching--required-files CLI argument with glob pattern matching and solution reference check
src/SLNX-validator/Program.cs
Outdated
| var sonarqubeReport = parseResult.GetValue(sonarqubeReportOption); | ||
| var continueOnError = parseResult.GetValue(continueOnErrorOption); | ||
| return await services.GetRequiredService<ValidatorRunner>().RunAsync(input!, sonarqubeReport, continueOnError, cancellationToken); | ||
| var runResult = await services.GetRequiredService<ValidatorRunner>().RunAsync(input!, sonarqubeReport, continueOnError, cancellationToken); |
There was a problem hiding this comment.
The checks should run in the runner. So it's also create errors that will be in the sonarqube report (of enabled)
There was a problem hiding this comment.
Done in a9600b4. ValidatorRunner resolves the glob patterns via IRequiredFilesChecker.ResolveMatchedPaths, then passes the matched paths to ValidationCollector.CollectAsync. The collector adds SLNX020/SLNX021 errors directly into FileValidationResult, so they appear in both the console output and the SonarQube report.
| } | ||
|
|
||
| [Test] | ||
| [NotInParallel("CurrentDirectory")] |
There was a problem hiding this comment.
Only one happy and alternative integration test. Others should tests to unit tests (with mocked file system)
There was a problem hiding this comment.
Done in a9600b4. ProgramIntegrationTests now has only 1 happy + 1 alt integration test for --required-files. The rest became unit tests: CheckInSlnx pure-string unit tests are in SLNX-validator.Core.Tests/RequiredFilesCheckerTests.cs, and ValidationCollectorTests.cs (new file) covers the collector logic with NSubstitute mocks for IRequiredFilesChecker and ISlnxValidator.
README.md
Outdated
| | `SLNX011` | `ReferencedFileNotFound` | A file referenced in `<File Path="...">` does not exist on disk. | | ||
| | `SLNX012` | `InvalidWildcardUsage` | A `<File Path="...">` contains a wildcard pattern (see [`examples/invalid-wildcard.slnx`](examples/invalid-wildcard.slnx)). | | ||
| | `SLNX013` | `XsdViolation` | The XML structure violates the schema, e.g. `<Folder>` inside `<Folder>` (see [`examples/invalid-xsd.slnx`](examples/invalid-xsd.slnx)). | | ||
| | `SLNX020` | `RequiredFilesNotFound` | A `--required-files` pattern produced no disk matches, or a matched file is not referenced as `<File>` in the solution (exits with code `2`). | |
There was a problem hiding this comment.
The special exit code is not needed for this. Also add the error requiredFileDoesntExistOnSystem (spelled correctly)
There was a problem hiding this comment.
Done in a9600b4. Removed the special exit code 2 — required-files failures use exit code 1 like all other validation errors. Added RequiredFileDoesntExistOnSystem = 20 (SLNX020) and RequiredFileNotReferencedInSolution = 21 (SLNX021) to ValidationErrorCode. README updated accordingly.
|
|
||
| var missing = requiredAbsolutePaths.Where(p => !slnxFileRefs.Contains(p)).ToList(); | ||
| foreach (var m in missing) | ||
| await Console.Error.WriteLineAsync($"[SLNX020] Required file not referenced in solution: {m}"); |
There was a problem hiding this comment.
Also print how it should look in the slnx (including html element)
There was a problem hiding this comment.
Done in a9600b4. The RequiredFileNotReferencedInSolution error message now includes the exact element to add, e.g.: Required file is not referenced in the solution: /path/to/readme.md — add: <File Path="doc/readme.md" />
…ator pipeline, fix error codes Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/b69c15c2-a945-4420-bc1c-b4542a60f632
|
|
||
| try | ||
| { | ||
| var doc = XDocument.Parse(slnxContent); |
There was a problem hiding this comment.
Deduplicate the parse of the slnx, create a dto
There was a problem hiding this comment.
Done in ec1f478. Created SlnxFileRefs DTO with a Parse(slnxContent, slnxDirectory) factory that owns the single XDocument.Parse call and resolves all <File> paths to absolute normalised strings. ValidationCollector calls Parse once and passes the result to CheckInSlnx.
| var fullPath = Path.IsPathRooted(path) | ||
| ? Path.GetFullPath(path) | ||
| : Path.GetFullPath(Path.Combine(slnxDirectory, path)); | ||
|
|
There was a problem hiding this comment.
Done in ec1f478 — both the XDocument.Parse and the Path.IsPathRooted / Path.GetFullPath path-resolution logic now live exclusively in SlnxFileRefs.Parse; CheckInSlnx just compares against slnxFileRefs.AbsoluteFilePaths.
| { | ||
| public async Task<IReadOnlyList<FileValidationResult>> CollectAsync(IReadOnlyList<string> files, CancellationToken cancellationToken) | ||
| public async Task<IReadOnlyList<FileValidationResult>> CollectAsync( | ||
| IReadOnlyList<string> files, |
There was a problem hiding this comment.
Create options type for the parameters
There was a problem hiding this comment.
Done in ec1f478. Created RequiredFilesOptions(IReadOnlyList<string>? MatchedPaths, string? Pattern) record. CollectAsync now takes RequiredFilesOptions? requiredFilesOptions instead of two separate nullable parameters.
| { | ||
| public async Task<int> RunAsync(string input, string? sonarqubeReportPath, bool continueOnError, CancellationToken cancellationToken) | ||
| public async Task<int> RunAsync(string input, string? sonarqubeReportPath, bool continueOnError, | ||
| string? requiredFilesPattern, string workingDirectory, CancellationToken cancellationToken) |
There was a problem hiding this comment.
Create options type for the parameters (record)
There was a problem hiding this comment.
Done in ec1f478. Created ValidatorRunnerOptions(string Input, string? SonarqubeReportPath, bool ContinueOnError, string? RequiredFilesPattern, string WorkingDirectory) record. RunAsync now takes ValidatorRunnerOptions options, CancellationToken cancellationToken.
| [Test] | ||
| public void ResolveMatchedPaths_SingleInclude_MatchesFiles_ReturnsNonEmpty() | ||
| { | ||
| var tempDir = CreateTempDir(); |
There was a problem hiding this comment.
There are too many integrations tests. I need two, one happy one alternative. Rest to unit tests with file system mock.
There was a problem hiding this comment.
Done in ec1f478. ResolveMatchedPaths integration tests reduced to 2 (happy path + exclude). All CheckInSlnx tests are pure unit tests using SlnxFileRefs.Parse on inline XML strings with no real filesystem access.
| [Test] | ||
| public async Task CollectAsync_RequiredFilesPatternNoMatch_AddsRequiredFileDoesntExistOnSystemError() | ||
| { | ||
| var tempDir = CreateTempDir(); |
There was a problem hiding this comment.
Too many integration tests. Move to unit tests
There was a problem hiding this comment.
Done in ec1f478. IFileSystem gained OpenRead and ReadAllTextAsync; ValidationCollector uses them instead of File.* directly. MockFileSystem now accepts a Dictionary<string,string> constructor for in-memory file content. All 4 ValidationCollectorTests are now true unit tests with zero real filesystem I/O.
| await File.WriteAllTextAsync(Path.Combine(docDir, "readme.md"), "# Readme"); | ||
| // The .slnx references doc/readme.md as a <File> so the last check passes. | ||
| await File.WriteAllTextAsync(slnxPath, """ | ||
| <Solution> |
There was a problem hiding this comment.
Too many integration tests. Move to unit tests
There was a problem hiding this comment.
Done in ec1f478. Removed both --required-files integration tests from ProgramIntegrationTests and replaced them with two unit tests in ValidatorRunnerTests (RunAsync_RequiredFiles_AllMatchedAndReferenced_ReturnsZero and RunAsync_RequiredFiles_NoMatchOnDisk_ReturnsOne) using mocked IRequiredFilesChecker and MockFileSystem.
README.md
Outdated
|
|
||
| Require all `.cs` files under `src/`, excluding the `bin` and `obj` folders: | ||
| ``` | ||
| slnx-validator MySolution.slnx --required-files "src/**/*.cs;!**/bin/**;!**/obj/**" |
There was a problem hiding this comment.
Change this example to: all yaml files except in src folder.
There was a problem hiding this comment.
Done in ec1f478. Changed the example to **/*.yaml;!src/** ("all yaml files except in src folder").
README.md
Outdated
| | Code | Description | | ||
| |------|-------------| | ||
| | `0` | All patterns matched and all matched files are referenced in the solution. | | ||
| | `1` | One or more required files don't exist on disk or are not referenced in the solution (same as any other validation error). | |
There was a problem hiding this comment.
1 should ne any validation error
There was a problem hiding this comment.
Done in ec1f478. Changed to: 1 — Any validation error — including required files not existing or not referenced.
…sts to unit tests; update README Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/b2fb8573-e2fd-4582-aa07-b67537345be2
Already implemented — |
304NotModified
left a comment
There was a problem hiding this comment.
Also check for duplication in code and tests.
| /// Represents the set of absolute file paths that are referenced as | ||
| /// <c><File Path="..."></c> elements inside a .slnx solution file. | ||
| /// </summary> | ||
| public sealed class SlnxFileRefs |
There was a problem hiding this comment.
Done in e956c34. SlnxFileRefs.cs renamed to SlnxFile.cs and the class renamed to SlnxFile.
|
|
||
| /// <summary>Absolute, normalised paths for every <c><File></c> entry in the solution.</summary> | ||
| public IReadOnlyList<string> AbsoluteFilePaths { get; } | ||
|
|
There was a problem hiding this comment.
Rename to Files (the xml docs are file)
There was a problem hiding this comment.
Done in e956c34. AbsoluteFilePaths renamed to Files.
| } | ||
| catch (Exception) | ||
| { | ||
| // Malformed XML is already reported by the XML validator. |
There was a problem hiding this comment.
Don't catch, Don't call this method if the xml is invalid OR return null
There was a problem hiding this comment.
Done in e956c34. SlnxFile.Parse now returns SlnxFile? — it tries to parse, returns null on XmlException (no swallowing). ValidationCollector guards with if (slnxFile is not null) before calling CheckInSlnx.
|
|
||
| private static RequiredFilesChecker CreateChecker() => new(); | ||
|
|
||
| // ── ResolveMatchedPaths (integration: 2 tests) ─────────────────────────── |
There was a problem hiding this comment.
Use #regions for these kind of comments
There was a problem hiding this comment.
Add this to copilot instructions
There was a problem hiding this comment.
Done in e956c34. All test classes now use #region blocks to group related tests. Convention stored to memory.
| File.WriteAllText(Path.Combine(docDir, "readme.md"), "# Readme"); | ||
| File.WriteAllText(Path.Combine(docDir, "contributing.md"), "# Contributing"); | ||
|
|
||
| var matched = CreateChecker().ResolveMatchedPaths("doc/*.md", tempDir); |
There was a problem hiding this comment.
Add
// Arrange
// Act
// Assert to all comments
There was a problem hiding this comment.
Add this to copilot instructions
There was a problem hiding this comment.
Done in e956c34. All test methods now have // Arrange, // Act, // Assert comments. Convention stored to memory.
|
|
||
| public class RequiredFilesCheckerTests | ||
| { | ||
| private static string CreateTempDir() |
There was a problem hiding this comment.
Move the integration tests to a separate test class, RequiredFilesCheckerIntegrationTests
The same for other test files.
Add this to copilot instructions
There was a problem hiding this comment.
Done in e956c34. ResolveMatchedPaths integration tests moved to new RequiredFilesCheckerIntegrationTests class. RequiredFilesCheckerTests now contains only unit tests. Convention stored to memory.
| "primaryLocation": { | ||
| "message": "Sample message for RequiredFileDoesntExistOnSystem", | ||
| "filePath": "Solution.slnx", | ||
| "textRange": { |
There was a problem hiding this comment.
This is not possible like this?
There was a problem hiding this comment.
The snapshot is valid. The AllErrorCodes test uses allCodes.Select((code, i) => new ValidationError(code, ..., Line: i + 1)) — it assigns an artificial Line: i+1 to every error to test that textRange is included in the output. SLNX020 is the 8th enum value (index 7), so Line: 8, giving "startLine": 8 in the snapshot. The snapshot was accepted after adding the two new error codes.
…it integration tests Co-authored-by: 304NotModified <5808377+304NotModified@users.noreply.github.com> Agent-Logs-Url: https://github.com/304NotModified/SLNX-validator/sessions/ceaf8ba9-6628-42a8-b534-1dfec61aafc3
Reviewed in e956c34. The main duplication was the double |
Adds a
--required-filesoption that verifies glob-matched files both exist on disk and are referenced as<File>entries in the validated.slnxsolution file(s). Uses semicolon-separated glob patterns with ordered include/exclude semantics. Failures are reported as standard validation errors (exit code1) and appear in SonarQube reports.Changes
Core logic
IRequiredFilesChecker.cs— public interface inSLNX-validator.Corewith two methods:ResolveMatchedPaths— resolves glob patterns to absolute disk paths usingMicrosoft.Extensions.FileSystemGlobbing(MatcherwithpreserveFilterOrder: true,StringComparison.OrdinalIgnoreCase)CheckInSlnx— accepts aSlnxFileDTO and checks that each required path is referenced; error message includes the exact<File Path="...">element to addRequiredFilesChecker.cs— internal sealed implementation in Core, registered via DI inServiceCollectionExtensionsSlnxFile.cs— new DTO in Core that owns the singleXDocument.Parsecall;SlnxFile.Parse(content, dir)returnsSlnxFile?(null on malformed XML) and normalises all<File>entries to absolute paths; storesSlnxDirectoryfor relative-path error messagesIFileSystem— extended withOpenReadandReadAllTextAsyncso all file I/O is routed through the abstractionNew error codes
SLNX020 RequiredFileDoesntExistOnSystem— no files matched the required-files glob patterns on diskSLNX021 RequiredFileNotReferencedInSolution— a matched file exists on disk but is not referenced as<File>in the solutionCLI
ValidatorRunnerOptions— new record grouping allValidatorRunner.RunAsyncparameters (Input,SonarqubeReportPath,ContinueOnError,RequiredFilesPattern,WorkingDirectory)RequiredFilesOptions— new record grouping the required-files parameters passed toValidationCollector.CollectAsync(MatchedPaths,Pattern)ValidatorRunner.cs— usesValidatorRunnerOptions; resolves glob patterns once and passesRequiredFilesOptionsto the collectorValidationCollector.cs— usesRequiredFilesOptions; callsSlnxFile.Parseonce per file and passes the DTO toCheckInSlnx; usesIFileSystemfor all file I/O; skips required-files check when XML is invalidProgram.cs— builds and passesValidatorRunnerOptionsto the runnerDocumentation
README.md— updated--required-filessection withSLNX020/SLNX021error codes, updated glob example to "all yaml files except in src folder" (**/*.yaml;!src/**), clarified exit code1as "any validation error"Tests
RequiredFilesCheckerTests.cs— pure unit tests forCheckInSlnxandSlnxFile.Parse(valid XML, invalid XML returns null, path normalisation); uses#regionsections and// Arrange / Act / AssertcommentsRequiredFilesCheckerIntegrationTests.cs— 2 integration tests forResolveMatchedPaths(happy + exclude) in a dedicated*IntegrationTestsclassValidationCollectorTests.cs— true unit tests usingMockFileSystemwith in-memory file content (no temp dirs); mockedIRequiredFilesCheckerandISlnxValidatorValidatorRunnerTests.cs— updated forValidatorRunnerOptions; includes 2 required-files unit tests with mockedIRequiredFilesCheckerProgramIntegrationTests.cs—--required-filesintegration tests removed (covered by unit tests above)MockFileSystem— extended with aDictionary<string,string>constructor for in-memory file content, andOpenRead/ReadAllTextAsyncimplementationsUsage
Pattern order is respected: a later
!excludecan suppress an earlier include, and a later include can re-include something previously excluded. All required-files failures use exit code1and appear in SonarQube reports alongside other validation errors.⌨️ Start Copilot coding agent tasks without leaving your editor — available in VS Code, Visual Studio, JetBrains IDEs and Eclipse.