diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index c09f8e0..8857d28 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -491,7 +491,7 @@ jobs: --sarif artifacts/csharp.sarif --report docs/code_quality/codeql-quality.md --heading "SarifMark CodeQL Analysis" - --report-depth 1 + --depth 1 - name: Display CodeQL Quality Report shell: bash diff --git a/README.md b/README.md index 8e753fe..378a869 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ Options: --log Write output to log file --sarif SARIF file to process --report Export analysis results to markdown file - --report-depth Markdown header depth for report (default: 1) + --depth Markdown header depth for report (default: 1) --heading Custom heading for report (default: [ToolName] Analysis) ``` diff --git a/docs/design/sarifmark/cli/cli.md b/docs/design/sarifmark/cli/cli.md index ae94a9e..cee174a 100644 --- a/docs/design/sarifmark/cli/cli.md +++ b/docs/design/sarifmark/cli/cli.md @@ -42,7 +42,7 @@ The `Cli` subsystem exposes the following interface to the rest of the tool: | `Enforce` | `bool` | `--enforce` | Enforcement mode flag | | `SarifFile` | `string?` | `--sarif ` | Path to the SARIF input file | | `ReportFile` | `string?` | `--report ` | Path for the markdown report output file | -| `ReportDepth` | `int` | `--report-depth ` | Markdown heading depth for the report | +| `Depth` | `int` | `--depth ` | Markdown heading depth for the report | | `Heading` | `string?` | `--heading ` | Custom heading text for the report | | `ResultsFile` | `string?` | `--results ` | Path for the self-validation results file | | `ExitCode` | `int` | *(derived)* | 0 until `WriteError` is called, then 1 | diff --git a/docs/design/sarifmark/cli/context.md b/docs/design/sarifmark/cli/context.md index 87248a4..e600962 100644 --- a/docs/design/sarifmark/cli/context.md +++ b/docs/design/sarifmark/cli/context.md @@ -35,13 +35,14 @@ This satisfies requirements `SarifMark-Context-Create` through `SarifMark-Contex | `Enforce` | `bool` | `false` | `--enforce` | Enforcement mode flag | | `SarifFile` | `string?` | `null` | `--sarif ` | Path to the SARIF file | | `ReportFile` | `string?` | `null` | `--report ` | Path to the markdown report output file | -| `ReportDepth` | `int` | `1` | `--report-depth ` | Markdown heading depth for the report | +| `Depth` | `int` | `1` | `--depth ` | Markdown heading depth for the report | | `Heading` | `string?` | `null` | `--heading ` | Custom heading text for the report | | `ResultsFile` | `string?` | `null` | `--results ` | Path for the self-validation results file | | `ExitCode` | `int` | `0`/`1` | *(derived)* | 0 until `WriteError` is called, then 1 | -The `--result` flag is accepted as a legacy alias for `--results`, preserving backwards compatibility -(see requirement `SarifMark-Context-ResultLegacyAlias`). +The `--report-depth` flag is accepted as a legacy alias for `--depth`, preserving backwards compatibility +(see requirement `SarifMark-Context-ReportDepthParam`). The `--result` flag is similarly accepted as a +legacy alias for `--results` (see requirement `SarifMark-Context-ResultLegacyAlias`). These properties satisfy requirements `SarifMark-Context-VersionFlag`, `SarifMark-Context-HelpFlag`, `SarifMark-Context-SilentFlag`, `SarifMark-Context-ValidateFlag`, `SarifMark-Context-EnforceFlag`, @@ -53,14 +54,15 @@ and `SarifMark-Context-ExitCode`. `ArgumentParser` is a private, sealed nested class responsible for token-by-token command-line parsing. Its `ParseArguments(string[] args)` method iterates through tokens in order and delegates -each to `ParseArgument`. Value-bearing flags (e.g. `--sarif`, `--report-depth`) consume the +each to `ParseArgument`. Value-bearing flags (e.g. `--sarif`, `--depth`) consume the following token as their argument value. Any unrecognized token causes `ParseArgument` to throw `ArgumentException` with a message identifying the unsupported argument. This satisfies requirement `SarifMark-Context-UnknownArgs`. -`--report-depth` requires a positive integer value; non-integer or non-positive values also throw -`ArgumentException`. This satisfies requirement `SarifMark-Context-ReportDepthParam`. +`--depth` requires a positive integer value; non-integer or non-positive values also throw +`ArgumentException`. The legacy alias `--report-depth` behaves identically. +This satisfies requirement `SarifMark-Context-ReportDepthParam`. ## WriteLine Method diff --git a/docs/design/sarifmark/program.md b/docs/design/sarifmark/program.md index 5f89232..433f469 100644 --- a/docs/design/sarifmark/program.md +++ b/docs/design/sarifmark/program.md @@ -61,7 +61,7 @@ to the context output, listing every supported option with its flag syntax and a - `--log ` — write output to a log file - `--sarif ` — SARIF file to process - `--report ` — export analysis results to a markdown file -- `--report-depth ` — markdown header depth for the report (default: 1) +- `--depth ` — markdown header depth for the report (default: 1) - `--heading ` — custom heading for the report (default: `[ToolName] Analysis`) This satisfies requirement `SarifMark-Program-Help`. diff --git a/docs/reqstream/sarifmark/cli/cli.yaml b/docs/reqstream/sarifmark/cli/cli.yaml index 87e2e92..3d47cb2 100644 --- a/docs/reqstream/sarifmark/cli/cli.yaml +++ b/docs/reqstream/sarifmark/cli/cli.yaml @@ -128,7 +128,7 @@ sections: - Cli_ReportParameter_SetsReportFilePath - id: SarifMark-Cli-ReportDepth - title: The CLI shall accept a heading depth via the --report-depth parameter. + title: The CLI shall accept a heading depth via the --depth parameter. justification: >- Exposing a validated heading depth parameter allows the application layer to control markdown report heading levels without reimplementing argument parsing or range checking. @@ -136,7 +136,8 @@ sections: children: - SarifMark-Context-ReportDepthParam tests: - - Cli_ReportDepthParameter_SetsReportDepth + - Cli_DepthParameter_SetsDepth + - SelfTest_DepthParameter_AffectsSelfValidationReport - id: SarifMark-Cli-Heading title: The CLI shall accept a custom heading via the --heading parameter. diff --git a/docs/reqstream/sarifmark/cli/context.yaml b/docs/reqstream/sarifmark/cli/context.yaml index 2c8a10c..46cf854 100644 --- a/docs/reqstream/sarifmark/cli/context.yaml +++ b/docs/reqstream/sarifmark/cli/context.yaml @@ -87,13 +87,19 @@ sections: - Context_Create_ReportParameter_SetsReportFile - id: SarifMark-Context-ReportDepthParam - title: The Create method shall parse --report-depth, validate it is a positive integer, and set ReportDepth. + title: >- + The Create method shall parse --depth (and legacy --report-depth), validate it is a positive + integer, and set Depth. justification: >- Validating the depth value at parse time prevents invalid heading levels from propagating into markdown generation, and defaulting to 1 ensures sensible behavior when the parameter is omitted. tags: [internal] tests: + - Context_Create_DepthParameter_SetsDepth + - Context_Create_DepthWithoutValue_ThrowsArgumentException + - Context_Create_DepthInvalidValue_ThrowsArgumentException + - Context_Create_DepthZero_ThrowsArgumentException - Context_Create_ReportDepthParameter_SetsReportDepth - Context_Create_ReportDepthWithoutValue_ThrowsArgumentException - Context_Create_ReportDepthInvalidValue_ThrowsArgumentException diff --git a/docs/user_guide/introduction.md b/docs/user_guide/introduction.md index cbe1081..baabadd 100644 --- a/docs/user_guide/introduction.md +++ b/docs/user_guide/introduction.md @@ -139,7 +139,7 @@ SarifMark supports the following command-line options: - `--log `: Write console output to log file - `--sarif `: SARIF file to process (required for analysis) - `--report `: Export analysis results to markdown file -- `--report-depth `: Markdown header depth for report (default: 1) +- `--depth `: Markdown header depth for report (default: 1) - `--heading `: Custom heading for report (default: [ToolName] Analysis) # Common Usage Patterns @@ -165,7 +165,7 @@ sarifmark --sarif analysis.sarif --report report.md --heading "Security Analysis Control the markdown header depth in the report: ```bash -sarifmark --sarif analysis.sarif --report report.md --report-depth 2 +sarifmark --sarif analysis.sarif --report report.md --depth 2 ``` This is useful when including the report in a larger document where you want the sections to be at a deeper level. @@ -349,7 +349,7 @@ This is useful in CI/CD pipelines to fail builds when quality issues are detecte Yes, you can customize: - **Heading**: Use `--heading "Custom Title"` to set a custom report heading -- **Header Depth**: Use `--report-depth 2` to adjust the markdown header level (useful when including the report in +- **Header Depth**: Use `--depth 2` to adjust the markdown header level (useful when including the report in a larger document) The report content format is standardized but these options allow you to integrate reports into different documentation diff --git a/src/DemaConsulting.SarifMark/Cli/Context.cs b/src/DemaConsulting.SarifMark/Cli/Context.cs index cda99d0..3df3f48 100644 --- a/src/DemaConsulting.SarifMark/Cli/Context.cs +++ b/src/DemaConsulting.SarifMark/Cli/Context.cs @@ -71,9 +71,9 @@ internal sealed class Context : IDisposable public string? ReportFile { get; private init; } /// - /// Gets the report markdown depth. + /// Gets the markdown depth. /// - public int ReportDepth { get; private init; } = 1; + public int Depth { get; private init; } = 1; /// /// Gets the custom heading for the report. @@ -120,7 +120,7 @@ public static Context Create(string[] args) Enforce = parser.Enforce, SarifFile = parser.SarifFile, ReportFile = parser.ReportFile, - ReportDepth = parser.ReportDepth, + Depth = parser.Depth, Heading = parser.Heading, ResultsFile = parser.ResultsFile }; @@ -196,9 +196,9 @@ private sealed class ArgumentParser public string? ReportFile { get; private set; } /// - /// Gets the report markdown depth. + /// Gets the markdown depth. /// - public int ReportDepth { get; private set; } = 1; + public int Depth { get; private set; } = 1; /// /// Gets the custom heading for the report. @@ -278,8 +278,9 @@ private int ParseArgument(string arg, string[] args, int index) ReportFile = GetRequiredStringArgument(arg, args, index, "a filename argument"); return index + 1; - case "--report-depth": - ReportDepth = GetRequiredIntArgument(arg, args, index); + case "--depth": + case "--report-depth": // Legacy alias for --depth + Depth = GetRequiredIntArgument(arg, args, index); return index + 1; case "--heading": diff --git a/src/DemaConsulting.SarifMark/Program.cs b/src/DemaConsulting.SarifMark/Program.cs index 03c9acb..36584ea 100644 --- a/src/DemaConsulting.SarifMark/Program.cs +++ b/src/DemaConsulting.SarifMark/Program.cs @@ -142,7 +142,7 @@ private static void PrintHelp(Context context) context.WriteLine(" --log Write output to log file"); context.WriteLine(" --sarif SARIF file to process"); context.WriteLine(" --report Export analysis results to markdown file"); - context.WriteLine(" --report-depth Markdown header depth for report (default: 1)"); + context.WriteLine(" --depth Markdown header depth for report (default: 1)"); context.WriteLine(" --heading Custom heading for report (default: [ToolName] Analysis)"); } @@ -193,7 +193,7 @@ private static void ProcessSarifAnalysis(Context context) context.WriteLine($"Writing report to {context.ReportFile}..."); try { - var markdown = sarifResults.ToMarkdown(context.ReportDepth, context.Heading); + var markdown = sarifResults.ToMarkdown(context.Depth, context.Heading); File.WriteAllText(context.ReportFile, markdown); context.WriteLine("Report generated successfully."); } diff --git a/src/DemaConsulting.SarifMark/SelfTest/Validation.cs b/src/DemaConsulting.SarifMark/SelfTest/Validation.cs index cf11034..87e4de6 100644 --- a/src/DemaConsulting.SarifMark/SelfTest/Validation.cs +++ b/src/DemaConsulting.SarifMark/SelfTest/Validation.cs @@ -130,6 +130,8 @@ private static void RunSarifReadingTest(Context context, DemaConsulting.TestResu /// The test results collection. private static void RunMarkdownReportGenerationTest(Context context, DemaConsulting.TestResults.TestResults testResults) { + var depthArgs = new[] { "--depth", context.Depth.ToString() }; + var headingPrefix = new string('#', context.Depth); RunValidationTest( context, testResults, @@ -147,14 +149,15 @@ private static void RunMarkdownReportGenerationTest(Context context, DemaConsult return "Report file not created"; } - if (reportContent.Contains("MockTool Analysis") && + if (reportContent.Contains($"{headingPrefix} MockTool Analysis") && reportContent.Contains("Found 2 issues")) { return null; } return "Report file missing expected content"; - }); + }, + depthArgs); } /// diff --git a/test/DemaConsulting.SarifMark.Tests/Cli/CliTests.cs b/test/DemaConsulting.SarifMark.Tests/Cli/CliTests.cs index b462e9c..dba4e93 100644 --- a/test/DemaConsulting.SarifMark.Tests/Cli/CliTests.cs +++ b/test/DemaConsulting.SarifMark.Tests/Cli/CliTests.cs @@ -219,7 +219,21 @@ public void Cli_ReportParameter_SetsReportFilePath() } /// - /// Test that report-depth parameter sets the report depth in context. + /// Test that depth parameter sets the depth in context. + /// + [TestMethod] + public void Cli_DepthParameter_SetsDepth() + { + // Act + using var context = Context.Create(["--depth", "3"]); + + // Assert + Assert.AreEqual(3, context.Depth); + Assert.AreEqual(0, context.ExitCode); + } + + /// + /// Test that legacy report-depth parameter sets the report depth in context. /// [TestMethod] public void Cli_ReportDepthParameter_SetsReportDepth() @@ -228,7 +242,7 @@ public void Cli_ReportDepthParameter_SetsReportDepth() using var context = Context.Create(["--report-depth", "3"]); // Assert - Assert.AreEqual(3, context.ReportDepth); + Assert.AreEqual(3, context.Depth); Assert.AreEqual(0, context.ExitCode); } diff --git a/test/DemaConsulting.SarifMark.Tests/Cli/ContextTests.cs b/test/DemaConsulting.SarifMark.Tests/Cli/ContextTests.cs index c1748f0..06e9359 100644 --- a/test/DemaConsulting.SarifMark.Tests/Cli/ContextTests.cs +++ b/test/DemaConsulting.SarifMark.Tests/Cli/ContextTests.cs @@ -424,7 +424,53 @@ public void Context_Create_ReportParameter_SetsReportFile() } /// - /// Test that creating a context with --report-depth parameter sets ReportDepth property. + /// Test that creating a context with --depth parameter sets Depth property. + /// + [TestMethod] + public void Context_Create_DepthParameter_SetsDepth() + { + // Act + using var context = Context.Create(["--depth", "3"]); + + // Assert + Assert.AreEqual(3, context.Depth); + } + + /// + /// Test that creating a context with --depth but no value throws exception. + /// + [TestMethod] + public void Context_Create_DepthWithoutValue_ThrowsArgumentException() + { + // Act & Assert + var exception = Assert.Throws(() => Context.Create(["--depth"])); + Assert.Contains("--depth requires", exception.Message); + } + + /// + /// Test that creating a context with --depth and invalid value throws exception. + /// + [TestMethod] + public void Context_Create_DepthInvalidValue_ThrowsArgumentException() + { + // Act & Assert + var exception = Assert.Throws(() => Context.Create(["--depth", "invalid"])); + Assert.Contains("--depth requires a positive integer", exception.Message); + } + + /// + /// Test that creating a context with --depth and zero value throws exception. + /// + [TestMethod] + public void Context_Create_DepthZero_ThrowsArgumentException() + { + // Act & Assert + var exception = Assert.Throws(() => Context.Create(["--depth", "0"])); + Assert.Contains("--depth requires a positive integer", exception.Message); + } + + /// + /// Test that creating a context with legacy --report-depth parameter sets Depth property. /// [TestMethod] public void Context_Create_ReportDepthParameter_SetsReportDepth() @@ -433,11 +479,11 @@ public void Context_Create_ReportDepthParameter_SetsReportDepth() using var context = Context.Create(["--report-depth", "3"]); // Assert - Assert.AreEqual(3, context.ReportDepth); + Assert.AreEqual(3, context.Depth); } /// - /// Test that creating a context with --report-depth but no value throws exception. + /// Test that creating a context with legacy --report-depth but no value throws exception. /// [TestMethod] public void Context_Create_ReportDepthWithoutValue_ThrowsArgumentException() @@ -448,7 +494,7 @@ public void Context_Create_ReportDepthWithoutValue_ThrowsArgumentException() } /// - /// Test that creating a context with --report-depth and invalid value throws exception. + /// Test that creating a context with legacy --report-depth and invalid value throws exception. /// [TestMethod] public void Context_Create_ReportDepthInvalidValue_ThrowsArgumentException() @@ -459,7 +505,7 @@ public void Context_Create_ReportDepthInvalidValue_ThrowsArgumentException() } /// - /// Test that creating a context with --report-depth and zero value throws exception. + /// Test that creating a context with legacy --report-depth and zero value throws exception. /// [TestMethod] public void Context_Create_ReportDepthZero_ThrowsArgumentException() diff --git a/test/DemaConsulting.SarifMark.Tests/IntegrationTests.cs b/test/DemaConsulting.SarifMark.Tests/IntegrationTests.cs index 8896cf0..3a247ce 100644 --- a/test/DemaConsulting.SarifMark.Tests/IntegrationTests.cs +++ b/test/DemaConsulting.SarifMark.Tests/IntegrationTests.cs @@ -328,7 +328,7 @@ public void IntegrationTest_UnknownArgument_ShowsError() } /// - /// Test that report depth parameter is configurable. + /// Test that depth parameter is configurable. /// [TestMethod] public void IntegrationTest_ReportDepth_IsConfigurable() @@ -339,6 +339,46 @@ public void IntegrationTest_ReportDepth_IsConfigurable() var reportFile = PathHelpers.SafePathCombine(Path.GetTempPath(), $"test-report-depth-{Guid.NewGuid()}.md"); + try + { + // Act + var exitCode = Runner.Run( + out _, + "dotnet", + _dllPath, + "--sarif", sarifFile, + "--report", reportFile, + "--depth", "3"); + + // Assert + Assert.AreEqual(0, exitCode); + Assert.IsTrue(File.Exists(reportFile), "Report file was not created"); + + var reportContent = File.ReadAllText(reportFile); + Assert.Contains("### TestTool Analysis", reportContent); + } + finally + { + // Clean up the temporary report file + if (File.Exists(reportFile)) + { + File.Delete(reportFile); + } + } + } + + /// + /// Test that legacy report-depth parameter is still accepted. + /// + [TestMethod] + public void IntegrationTest_LegacyReportDepth_IsAccepted() + { + // Arrange + var sarifFile = PathHelpers.SafePathCombine(_testDataPath, "sample.sarif"); + Assert.IsTrue(File.Exists(sarifFile), $"Test SARIF file not found at {sarifFile}"); + + var reportFile = PathHelpers.SafePathCombine(Path.GetTempPath(), $"test-legacy-report-depth-{Guid.NewGuid()}.md"); + try { // Act diff --git a/test/DemaConsulting.SarifMark.Tests/ProgramTests.cs b/test/DemaConsulting.SarifMark.Tests/ProgramTests.cs index ade2db8..182dcf6 100644 --- a/test/DemaConsulting.SarifMark.Tests/ProgramTests.cs +++ b/test/DemaConsulting.SarifMark.Tests/ProgramTests.cs @@ -115,7 +115,7 @@ public void Program_Main_HelpFlag_DisplaysHelp() Assert.Contains("--log", output); Assert.Contains("--sarif", output); Assert.MatchesRegex(@"--report(?!-)", output); - Assert.Contains("--report-depth", output); + Assert.Contains("--depth", output); Assert.Contains("--heading", output); } finally diff --git a/test/DemaConsulting.SarifMark.Tests/SelfTest/SelfTestTests.cs b/test/DemaConsulting.SarifMark.Tests/SelfTest/SelfTestTests.cs index fb68363..fe7908e 100644 --- a/test/DemaConsulting.SarifMark.Tests/SelfTest/SelfTestTests.cs +++ b/test/DemaConsulting.SarifMark.Tests/SelfTest/SelfTestTests.cs @@ -138,6 +138,34 @@ public void SelfTest_ResultsFile_WritesJUnitFile() } } + /// + /// Test that --depth affects the self-validation markdown report depth. + /// + [TestMethod] + public void SelfTest_DepthParameter_AffectsSelfValidationReport() + { + // Arrange + var originalOut = Console.Out; + try + { + using var outWriter = new StringWriter(); + Console.SetOut(outWriter); + + // Act - run validation with non-default depth of 2 + using var context = Context.Create(["--validate", "--depth", "2"]); + Validation.Run(context); + var output = outWriter.ToString(); + + // Assert - validation passes and the depth-sensitive report generation test passes + Assert.AreEqual(0, context.ExitCode); + Assert.Contains("SarifMark_MarkdownReportGeneration - Passed", output); + } + finally + { + Console.SetOut(originalOut); + } + } + /// /// Test that enforcement mode behavior is verified by the self-validation suite. ///