Conversation
There was a problem hiding this comment.
Pull request overview
This PR targets build consistency and analyzer runtime performance by pinning C# language versions per target framework, optimizing rule/suppression handling, and adding/expanding tests to lock in override/suppression behavior.
Changes:
- Build: set explicit
LangVersionper TFM for multi-target projects (net8/net9/net10). - Perf/refactor: reduce analyzer rule parsing overhead and speed up suppression checks via precomputed structures.
- Tests: add coverage for case-insensitive deploy property names and case-insensitive wildcard rule overrides/suppressions.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| test/DacpacTool.Tests/PropertyParserTest.cs | Adds test coverage for case-insensitive deploy property names. |
| test/DacpacTool.Tests/PackageDeployerTests.cs | Adds test coverage for case-insensitive deploy property names through PackageDeployer. |
| test/DacpacTool.Tests/PackageAnalyzerTests.cs | Adds coverage for case-insensitive wildcard override/suppression behavior and asserts suppressor processing is exercised. |
| test/DacpacTool.Tests/ExtensionsTest.cs | Adds coverage for reference external-parts precedence (literal vs variable). |
| test/DacpacTool.Tests/DacpacTool.Tests.csproj | Pins LangVersion per TFM for the test project. |
| src/DacpacTool/SqlRuleProblemExtensions.cs | Refactors wildcard severity override handling to use pre-parsed prefixes. |
| src/DacpacTool/PropertyParser.cs | Caches DacDeployOptions properties in a case-insensitive dictionary to avoid repeated reflection scans. |
| src/DacpacTool/PackageAnalyzer.cs | Single-pass rule parsing, pre-normalized wildcard prefixes, and indexed suppression checks (dictionary + hash sets). |
| src/DacpacTool/Extensions.cs | Caches regex instance; reduces reflection inside loops for SQLCMD variable metadata setting. |
| src/DacpacTool/DacpacTool.csproj | Pins LangVersion per TFM for the tool project. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
1943896 to
557cce3
Compare
|
@ErikEJ Would you mind taking another look, and fire off a new Copilot review? Not sure how to do that. |
6edf6e2 to
09bbdee
Compare
|
I'm removing the LangVersion change from this PR, and created a separate focused PR for that: #877 This here PR remains for performance refactors. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
39035d8 to
e4e27d7
Compare
…e tests
This improves parser/runtime internals that are invoked repeatedly and
adds a focused test for multi-rule warning-to-error override parsing.
Why
- Avoid repeated allocation/lookup overhead in hot paths (reflection + regex reuse)
- Keep behavior unchanged while making intent explicit and maintainable
- Make analyzer override behavior easier to validate and reason about
What changed
- src/DacpacTool/PropertyParser.cs
- Cache `DacDeployOptions` `PropertyInfo` objects in a dictionary keyed by name.
- Replace per-call LINQ property scan with O(1) dictionary lookup.
- This removes repeated reflection work when setting many deploy properties.
- src/DacpacTool/Extensions.cs
- Promote external parts parsing regex to a static readonly compiled instance.
- Reuse it directly in `ParseExternalParts`.
- Move `SetMetadata` method resolution outside the loop so we only fetch it once per call.
- This reduces repeated regex construction and reflection churn.
- src/DacpacTool/SqlRuleProblemExtensions.cs
- Rewrite wildcard rule handling as an explicit loop over rules with a clear comment.
- Preserve behavior while making wildcard `*` prefix matching easier to read and cheaper to maintain.
- test/DacpacTool.Tests/PackageAnalyzerTests.cs
- Create data-driven override test
- Add dedicated test `RunsAnalyzer_WithMultipleWarningToErrorOverridesInSingleExpression` to verify multiple `+!` overrides in a single rule string all promote to error.
Validation note
- The new test runs under all target frameworks in `test/DacpacTool.Tests.csproj` (`net8.0`, `net9.0`, `net10.0`) because the test project is multi-targeted, so this is cross-target coverage.
- Add tests for case-insensitive wildcard rule/suppression behavior. - Parse rule expression once in PackageAnalyzer.BuildRuleLists, splitting +! entries into exact vs wildcard prefix buckets. - Pre-normalize wildcard overrides - Update SqlRuleProblemExtensions to evaluate wildcard prefixes from pre-normalized list. - Preserve existing behavior while lowering per-SqlRuleProblem work in hot diagnostic formatting path.
This refactor changes suppression matching in `PackageAnalyzer` from an O(problems × suppressions)
scan to an indexed lookup.
Before:
- For each diagnostic (rule id + source file), the code checked every suppression entry in
`StaticCodeAnalysis.SuppressMessages.xml`.
After:
- Build once per run:
`Dictionary<RuleId, HashSet<SourceName>>` from suppression entries.
- For each diagnostic:
- lookup by `RuleId`
- check `SourceName` in that set.
What this means:
- Example 2,000 diagnostics + 500 suppressions:
- Old: ~1,000,000 comparisons
- New: ~2,000 lookups + ~500 index builds
Behavior:
- No user-facing behavior change.
- Same suppression decisions and logging semantics.
Changes:
- `src/DacpacTool/PackageAnalyzer.cs`
- Added suppression index and replaced linear `Any(...)` scan with indexed check.
- `test/DacpacTool.Tests/PackageAnalyzerTests.cs`
- Added assertion in `RunsAnalyzerWithSuppressionFile` to ensure suppression processing is
exercised (`"Suppressing rule:"`).
Validation:
- Focused analyzer tests pass on net8/net9/net10 for 9 tests.
Use the cached `SetMetadataMethod` in `AddSqlCmdVariables` instead of reflecting and null-checking the same method on every call. This keeps the method consistent with `AddReference`, removes duplicate reflection work, and preserves behavior.
Store wildcard warning-to-error prefixes in a case-insensitive `HashSet<string>` instead of a `List<string>` and update the consumer signature to match. This avoids redundant prefix scans when duplicate `+!...*` overrides are present.
since the cost of this optimization is not worth the gain, removing per PR review. Most invocations will have few parameters, and the cost of the optimization is not worth it.
provide a user-friendly error if a timeout occurs
PackageAnalyzer up to 94.8% coverage Extensions up to 85.6% coverage
8dc8ac0 to
a4939fc
Compare
|
Hmm, I'm wondering what the coverage looks like now, but I'm not seeing that yet. Perhaps this needs to be rebased on master after merging #885? |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 7 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@nmummau Could you look at the build error? |
My motivation for this pull request was to see if there was any low-hanging fruit changes that could be made to for performance gains.
each change explained
refactor: clarify analyzer override tests
I added tests around rule override behavior before changing code so the behavior contract is explicit and protected. The key gaps were in case-insensitive wildcard matching for both
+!overrides and-suppressions. Those cases were previously untested in this repo path, so I added focused coverage.perf(analyzer): reduce rule parsing overhead
I changed rule parsing to be single-pass and data-shape friendly:
BuildRuleListsnow parses once instead of doing multiple filteredSplitloops.+!rules are normalized up front into:+!SRD0006), and+!SRD*stored asSRD).SqlRuleProblemExtensions.GetOutputMessagenow checks those pre-normalized prefixes directlywhile formatting, instead of slicing wildcard patterns repeatedly during output formatting.
perf(analyzer): speed up suppressor checks
I replaced per-problem linear suppression scans with an index:
Dictionary<RuleId, HashSet<SourceName>>SetProblemSuppressor, suppress-check is now:instead of comparing each diagnostic against every suppression entry.
why this helps:
#suppressionscomparisons (O(diag_count × suppress_count)).Concrete example:
Validation:
RunsAnalyzerWithSuppressionFilenowverifies suppressor processing is exercised).
net8,net9,net10.Net effect: