|
| 1 | +--- |
| 2 | +title: C# Preprocessor Block Attribute Detection Limitation |
| 3 | +description: ADR for attribute detection limitation inside conditional compilation blocks due to tree-sitter-c-sharp grammar |
| 4 | +--- |
| 5 | + |
| 6 | +# ADR-15: C# Preprocessor Block Attribute Detection Limitation |
| 7 | + |
| 8 | +> 🇰🇷 [Korean Version](/ko/adr/core/15-csharp-preprocessor-attribute-limitation.md) |
| 9 | +
|
| 10 | +| Date | Author | Repository | |
| 11 | +| ---------- | ------------ | ---------- | |
| 12 | +| 2026-01-04 | @KubrickCode | core | |
| 13 | + |
| 14 | +**Status**: Accepted |
| 15 | + |
| 16 | +## Background |
| 17 | + |
| 18 | +### Problem Definition |
| 19 | + |
| 20 | +The tree-sitter-c-sharp grammar parses preprocessor directives (`#if`, `#else`, `#elif`) between attributes as `ERROR` nodes instead of `preproc_if` nodes. |
| 21 | + |
| 22 | +### Discovery |
| 23 | + |
| 24 | +Validation of `fluentassertions/fluentassertions` repository: |
| 25 | + |
| 26 | +- Ground Truth (AI Manual Analysis): 5,995 tests |
| 27 | +- Parser Result: 6,009 tests |
| 28 | +- Delta: +14 (+0.23%) |
| 29 | + |
| 30 | +The delta is positive because GT analysis errors outnumber parser bugs. The actual parser bug caused 2 tests to be missed in `AssertionExtensionsSpecs.cs`. |
| 31 | + |
| 32 | +### Technical Analysis |
| 33 | + |
| 34 | +```csharp |
| 35 | +// InlineData(2) is not detected in this pattern |
| 36 | +[Theory] |
| 37 | +[InlineData(1)] |
| 38 | +#if NET6_0_OR_GREATER |
| 39 | +[InlineData(2)] // ← Parser misses this |
| 40 | +#endif |
| 41 | +public void Test(int x) { } |
| 42 | +``` |
| 43 | + |
| 44 | +Actual tree-sitter parsing result: |
| 45 | + |
| 46 | +``` |
| 47 | +method_declaration |
| 48 | +├── attribute_list [Theory] |
| 49 | +├── attribute_list [InlineData(1)] |
| 50 | +├── ERROR ← Not preproc_if! |
| 51 | +│ └── #if NET6_0_OR_GREATER |
| 52 | +│ └── (InlineData(2) incorrectly parsed) |
| 53 | +└── public void Test() |
| 54 | +``` |
| 55 | + |
| 56 | +**Note**: Class-level `#if` (wrapping entire methods) works correctly: |
| 57 | + |
| 58 | +```csharp |
| 59 | +// This pattern is detected correctly |
| 60 | +#if NET6_0_OR_GREATER |
| 61 | +[Fact] |
| 62 | +public void Net6OnlyTest() { } |
| 63 | +#endif |
| 64 | +``` |
| 65 | + |
| 66 | +## Decision |
| 67 | + |
| 68 | +**Test attribute detection inside preprocessor blocks between attributes is not supported.** |
| 69 | + |
| 70 | +This is a tree-sitter-c-sharp grammar-level issue that cannot be fixed in the SpecVital Core parser. |
| 71 | + |
| 72 | +### Rationale |
| 73 | + |
| 74 | +1. **Grammar-level limitation**: tree-sitter-c-sharp generates incorrect AST, making parser-level workaround impossible |
| 75 | +2. **Upstream dependency**: Fix requires modifying the tree-sitter-c-sharp grammar itself |
| 76 | +3. **Limited impact**: Most C# projects don't use preprocessors between attributes |
| 77 | + |
| 78 | +## Options Considered |
| 79 | + |
| 80 | +### Option A: Accept Limitation and Document (Selected) |
| 81 | + |
| 82 | +Document the limitation and verify behavior with tests. |
| 83 | + |
| 84 | +**Pros:** |
| 85 | + |
| 86 | +- Honest representation of limitations |
| 87 | +- Automatically resolved if tree-sitter-c-sharp is fixed |
| 88 | + |
| 89 | +**Cons:** |
| 90 | + |
| 91 | +- Test under-count in certain codebases |
| 92 | + |
| 93 | +### Option B: Text-based Preprocessor Expansion |
| 94 | + |
| 95 | +Process preprocessor directives at text level before AST parsing. |
| 96 | + |
| 97 | +**Pros:** |
| 98 | + |
| 99 | +- Can detect attributes inside preprocessor blocks |
| 100 | + |
| 101 | +**Cons:** |
| 102 | + |
| 103 | +- **Complexity explosion**: Requires condition evaluation, nesting handling, multiple branch processing |
| 104 | +- **Accuracy degradation**: Cannot know which conditions are active |
| 105 | +- **Architecture violation**: Conflicts with tree-sitter-based parsing principles |
| 106 | + |
| 107 | +### Option C: Fork tree-sitter-c-sharp |
| 108 | + |
| 109 | +Directly modify the grammar to support preprocessors between attributes. |
| 110 | + |
| 111 | +**Pros:** |
| 112 | + |
| 113 | +- Fundamental solution |
| 114 | + |
| 115 | +**Cons:** |
| 116 | + |
| 117 | +- **Maintenance burden**: Must continuously merge upstream changes |
| 118 | +- **Scope creep**: Forking entire grammar for single issue |
| 119 | +- **Uncertainty**: Difficult to predict side effects of grammar modification |
| 120 | + |
| 121 | +## Consequences |
| 122 | + |
| 123 | +### Positive |
| 124 | + |
| 125 | +1. **Architecture integrity**: Maintains tree-sitter-based parsing model |
| 126 | +2. **Clear limitations**: Documented in code comments and tests |
| 127 | +3. **Maintainability**: No complex workarounds |
| 128 | + |
| 129 | +### Negative |
| 130 | + |
| 131 | +1. **Accuracy gap**: Projects using preprocessors between attributes will have under-counted tests |
| 132 | +2. **FluentAssertions impact**: Under-count due to `[InlineData]` usage in `#if` blocks |
| 133 | + |
| 134 | +### Mitigation |
| 135 | + |
| 136 | +1. **Minimal impact**: Most projects don't use this pattern |
| 137 | +2. **Class-level works**: `#if` wrapping entire methods works correctly |
| 138 | +3. **Documentation**: Limitation documented in `GetAttributeLists()` function comment |
| 139 | + |
| 140 | +## Framework Impact |
| 141 | + |
| 142 | +| Framework | Affected Pattern | Severity | |
| 143 | +| --------- | ----------------------- | -------- | |
| 144 | +| xUnit | `[InlineData]` in `#if` | Low | |
| 145 | +| NUnit | `[TestCase]` in `#if` | Low | |
| 146 | +| MSTest | `[DataRow]` in `#if` | Low | |
| 147 | + |
| 148 | +Most C# test projects use class-level or method-level conditional compilation. The pattern of inserting preprocessors between attributes is rare. |
| 149 | + |
| 150 | +## Related ADRs |
| 151 | + |
| 152 | +- [ADR-02: Dynamic Test Counting Policy](./02-dynamic-test-counting-policy.md) - Another accuracy limitation |
| 153 | +- [ADR-03: Tree-sitter AST Parsing Engine](./03-tree-sitter-ast-parsing-engine.md) - Tree-sitter-based parsing principles |
| 154 | +- [ADR-14: Indirect Import Alias Detection Unsupported](./14-indirect-import-unsupported.md) - Similar limitation documentation pattern |
| 155 | + |
| 156 | +## References |
| 157 | + |
| 158 | +- [tree-sitter-c-sharp GitHub](https://github.com/tree-sitter/tree-sitter-c-sharp) |
| 159 | +- Validation Report: `realworld-test-report.md` |
| 160 | +- Limitation Test: `pkg/parser/strategies/shared/dotnetast/ast_test.go:TestGetAttributeLists_PreprocessorLimitation` |
0 commit comments