code-auditor-mcp 3.4.9 → 3.4.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (130) hide show
  1. package/.claude-plugin/marketplace.json +10 -3
  2. package/CHANGELOG.md +81 -0
  3. package/dist/analyzers/analyzerUtils.d.ts.map +1 -1
  4. package/dist/analyzers/analyzerUtils.js +2 -1
  5. package/dist/analyzers/analyzerUtils.js.map +1 -1
  6. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +0 -2
  7. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -1
  8. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +74 -99
  9. package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -1
  10. package/dist/analyzers/documentationAnalyzer.d.ts +1 -5
  11. package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
  12. package/dist/analyzers/documentationAnalyzer.js +13 -69
  13. package/dist/analyzers/documentationAnalyzer.js.map +1 -1
  14. package/dist/analyzers/invariantsAnalyzer.d.ts +1 -6
  15. package/dist/analyzers/invariantsAnalyzer.d.ts.map +1 -1
  16. package/dist/analyzers/invariantsAnalyzer.js +16 -41
  17. package/dist/analyzers/invariantsAnalyzer.js.map +1 -1
  18. package/dist/analyzers/reactAnalyzer.d.ts +20 -3
  19. package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
  20. package/dist/analyzers/reactAnalyzer.js +4 -165
  21. package/dist/analyzers/reactAnalyzer.js.map +1 -1
  22. package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -1
  23. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +43 -69
  24. package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -1
  25. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +62 -0
  26. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
  27. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +357 -3
  28. package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
  29. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +81 -69
  30. package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
  31. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +319 -325
  32. package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
  33. package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -1
  34. package/dist/analyzers/universal/UniversalStylesAnalyzer.js +32 -37
  35. package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -1
  36. package/dist/auditRunner.d.ts +2 -7
  37. package/dist/auditRunner.d.ts.map +1 -1
  38. package/dist/auditRunner.js +306 -434
  39. package/dist/auditRunner.js.map +1 -1
  40. package/dist/cli.js +27 -5
  41. package/dist/cli.js.map +1 -1
  42. package/dist/codeIndexDB.d.ts +22 -0
  43. package/dist/codeIndexDB.d.ts.map +1 -1
  44. package/dist/codeIndexDB.js +92 -44
  45. package/dist/codeIndexDB.js.map +1 -1
  46. package/dist/config/defaults.js +3 -3
  47. package/dist/config/defaults.js.map +1 -1
  48. package/dist/conventions/conventionMiner.d.ts +5 -4
  49. package/dist/conventions/conventionMiner.d.ts.map +1 -1
  50. package/dist/conventions/conventionMiner.js +65 -25
  51. package/dist/conventions/conventionMiner.js.map +1 -1
  52. package/dist/dataPaths.js +1 -1
  53. package/dist/dataPaths.js.map +1 -1
  54. package/dist/invariants/index.d.ts +1 -1
  55. package/dist/invariants/index.d.ts.map +1 -1
  56. package/dist/invariants/index.js +1 -1
  57. package/dist/invariants/index.js.map +1 -1
  58. package/dist/invariants/ruleEngine.d.ts +38 -3
  59. package/dist/invariants/ruleEngine.d.ts.map +1 -1
  60. package/dist/invariants/ruleEngine.js +42 -174
  61. package/dist/invariants/ruleEngine.js.map +1 -1
  62. package/dist/languages/UniversalAnalyzer.d.ts +2 -2
  63. package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
  64. package/dist/languages/UniversalAnalyzer.js +3 -1
  65. package/dist/languages/UniversalAnalyzer.js.map +1 -1
  66. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts +3 -0
  67. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
  68. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +60 -16
  69. package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
  70. package/dist/mcp-tools-shared.d.ts.map +1 -1
  71. package/dist/mcp-tools-shared.js +10 -6
  72. package/dist/mcp-tools-shared.js.map +1 -1
  73. package/dist/mcp.js +0 -0
  74. package/dist/mcpAuditJobs.d.ts.map +1 -1
  75. package/dist/mcpAuditJobs.js +4 -1
  76. package/dist/mcpAuditJobs.js.map +1 -1
  77. package/dist/pipeline.d.ts +74 -0
  78. package/dist/pipeline.d.ts.map +1 -0
  79. package/dist/pipeline.js +620 -0
  80. package/dist/pipeline.js.bak +465 -0
  81. package/dist/pipeline.js.bak2 +465 -0
  82. package/dist/pipeline.js.map +1 -0
  83. package/dist/pipelineAdapters.d.ts +116 -0
  84. package/dist/pipelineAdapters.d.ts.map +1 -0
  85. package/dist/pipelineAdapters.js +1146 -0
  86. package/dist/pipelineAdapters.js.map +1 -0
  87. package/dist/pipelineTypes.d.ts +44 -0
  88. package/dist/pipelineTypes.d.ts.map +1 -0
  89. package/dist/pipelineTypes.js +118 -0
  90. package/dist/pipelineTypes.js.map +1 -0
  91. package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
  92. package/dist/reporting/csvReportGenerator.js +2 -1
  93. package/dist/reporting/csvReportGenerator.js.map +1 -1
  94. package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
  95. package/dist/reporting/jsonReportGenerator.js +4 -2
  96. package/dist/reporting/jsonReportGenerator.js.map +1 -1
  97. package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
  98. package/dist/reporting/sarifReportGenerator.js +2 -1
  99. package/dist/reporting/sarifReportGenerator.js.map +1 -1
  100. package/dist/styles/cssAstExtractor.d.ts +46 -0
  101. package/dist/styles/cssAstExtractor.d.ts.map +1 -0
  102. package/dist/styles/cssAstExtractor.js +439 -0
  103. package/dist/styles/cssAstExtractor.js.map +1 -0
  104. package/dist/styles/styleExtractor.d.ts +2 -2
  105. package/dist/styles/styleExtractor.d.ts.map +1 -1
  106. package/dist/styles/styleExtractor.js +69 -16
  107. package/dist/styles/styleExtractor.js.map +1 -1
  108. package/dist/styles/styleIndexer.d.ts.map +1 -1
  109. package/dist/styles/styleIndexer.js +4 -0
  110. package/dist/styles/styleIndexer.js.map +1 -1
  111. package/dist/styles/tailwindProbe.d.ts +2 -0
  112. package/dist/styles/tailwindProbe.d.ts.map +1 -1
  113. package/dist/styles/tailwindProbe.js +5 -5
  114. package/dist/styles/tailwindProbe.js.map +1 -1
  115. package/dist/styles/tailwindUtilityExpander.d.ts +3 -0
  116. package/dist/styles/tailwindUtilityExpander.d.ts.map +1 -1
  117. package/dist/styles/tailwindUtilityExpander.js +7 -0
  118. package/dist/styles/tailwindUtilityExpander.js.map +1 -1
  119. package/dist/types.d.ts +216 -11
  120. package/dist/types.d.ts.map +1 -1
  121. package/dist/types.js.map +1 -1
  122. package/dist/utils/fileDiscovery.d.ts +4 -0
  123. package/dist/utils/fileDiscovery.d.ts.map +1 -1
  124. package/dist/utils/fileDiscovery.js +7 -1
  125. package/dist/utils/fileDiscovery.js.map +1 -1
  126. package/dist/utils/reactDetection.d.ts.map +1 -1
  127. package/dist/utils/reactDetection.js +32 -0
  128. package/dist/utils/reactDetection.js.map +1 -1
  129. package/package.json +2 -4
  130. package/plugin/.claude-plugin/plugin.json +1 -1
@@ -0,0 +1,439 @@
1
+ /**
2
+ * CSS AST Extractor — Spec 26 Phase 2.
3
+ *
4
+ * Replaces regex-based CSS parsing (styleExtractor.ts extractFromCSS) with
5
+ * AST-based extraction using tree-sitter-css parsed structures.
6
+ *
7
+ * Three pure functions mirror the output of the regex extractors:
8
+ * 1. extractDeclarationsFromCSSAst — NormalizedDeclaration[]
9
+ * 2. extractTokensFromCSSAst — StyleToken[]
10
+ * 3. extractClassUsageFromCSSAst — StyleClassUsage[]
11
+ *
12
+ * SCSS is NOT handled here — tree-sitter-css is not an SCSS grammar.
13
+ * SCSS files stay on the regex path in styleExtractor.ts.
14
+ */
15
+ import { normalizeValue, expandShorthand } from './normalizer.js';
16
+ // ---------------------------------------------------------------------------
17
+ // Helpers
18
+ // ---------------------------------------------------------------------------
19
+ /**
20
+ * Extract a CSS custom property reference from a value like `var(--token)`.
21
+ * Token-level classification (not structure discovery) — the regex reads a
22
+ * known token name from a single value string.
23
+ */
24
+ function extractTokenRef(rawValue) {
25
+ const match = rawValue.match(/var\(\s*(--[a-zA-Z0-9_-]+)/);
26
+ return match ? match[1] : null;
27
+ }
28
+ /**
29
+ * Get the text content of a selectors node, stripping block comments.
30
+ * Bug 5 fix: CSS comments are not treated as class names (tree-sitter types
31
+ * comment nodes explicitly, so we filter by type).
32
+ */
33
+ function getSelectorText(selectorsNode, sourceCode) {
34
+ const raw = sourceCode.slice(selectorsNode.range[0], selectorsNode.range[1]);
35
+ // Strip block comments — token-level text munging on AST-discovered content
36
+ return raw.replace(/\/\*[\s\S]*?\*\//g, '').trim();
37
+ }
38
+ /**
39
+ * CSS at-rule node types in tree-sitter-css.
40
+ *
41
+ * tree-sitter-css uses distinct named types for some at-rules (media_statement,
42
+ * supports_statement, keyframes_statement) and generic `at_rule` for everything
43
+ * else (@layer, @import, @apply, etc.).
44
+ */
45
+ const AT_RULE_TYPES = new Set([
46
+ 'at_rule',
47
+ 'media_statement',
48
+ 'supports_statement',
49
+ 'keyframes_statement',
50
+ ]);
51
+ /**
52
+ * Walk ancestor chain for at-rule nodes and build variant context string.
53
+ */
54
+ function getVariantContext(node, adapter, sourceCode) {
55
+ const atRules = [];
56
+ let current = adapter.getParent(node);
57
+ while (current) {
58
+ if (AT_RULE_TYPES.has(current.type)) {
59
+ const text = sourceCode.slice(current.range[0], current.range[1]);
60
+ const braceIdx = text.indexOf('{');
61
+ const atRuleText = braceIdx !== -1 ? text.slice(0, braceIdx).trim() : text.trim();
62
+ atRules.unshift(atRuleText);
63
+ }
64
+ current = adapter.getParent(current);
65
+ }
66
+ return atRules.length > 0 ? atRules.join(', ') : null;
67
+ }
68
+ /**
69
+ * Extract @apply directives inside a rule_set block.
70
+ *
71
+ * @apply is a Tailwind extension, not standard CSS. tree-sitter-css may emit
72
+ * either ERROR nodes (when the grammar can't make sense of the token),
73
+ * postcss_statement nodes (when the grammar recognizes it as a PostCSS-style
74
+ * at-rule inside a rule set), or at_rule nodes (when parsed structurally as an
75
+ * at-rule with @apply keyword). All three paths produce the same
76
+ * NormalizedDeclaration shape.
77
+ *
78
+ * Bug 2: @apply in rule sets — both `@layer`-wrapped and bare. The old regex
79
+ * parser handled @apply in block text; AST requires explicit child-traversal.
80
+ */
81
+ function extractApplyDirectives(blockNode, sourceCode, selector, variantContext, filePath, declarations) {
82
+ for (const child of blockNode.children ?? []) {
83
+ let applyText = null;
84
+ let line;
85
+ if (child.type === 'ERROR') {
86
+ // Legacy path: @apply unrecognised by grammar → ERROR node.
87
+ const errText = sourceCode.slice(child.range[0], child.range[1]).trim();
88
+ const match = errText.match(/^@apply\s+(.+)$/);
89
+ applyText = match ? match[1].trim() : null;
90
+ line = child.location.start.line;
91
+ }
92
+ else if (child.type === 'postcss_statement') {
93
+ // Modern path: tree-sitter-css recognises @apply as a PostCSS statement.
94
+ // The full text is "@apply <utilities>;". Strip the keyword and semicolon.
95
+ const fullText = sourceCode.slice(child.range[0], child.range[1]).trim();
96
+ applyText = fullText.replace(/^@apply\s+/, '').replace(/;?\s*$/, '');
97
+ line = child.location.start.line;
98
+ }
99
+ else if (child.type === 'at_rule') {
100
+ // tree-sitter-css v0.x: @apply parsed as at_rule with at_keyword child.
101
+ const atKeyword = child.children?.find(c => c.type === 'at_keyword');
102
+ if (!atKeyword)
103
+ continue;
104
+ const kwText = sourceCode
105
+ .slice(atKeyword.range[0], atKeyword.range[1])
106
+ .trim();
107
+ if (kwText !== '@apply')
108
+ continue;
109
+ // The keyword_query child holds the utility names.
110
+ const queryChild = child.children?.find(c => c.type === 'keyword_query');
111
+ applyText = queryChild
112
+ ? sourceCode.slice(queryChild.range[0], queryChild.range[1]).trim()
113
+ : null;
114
+ line = child.location.start.line;
115
+ }
116
+ else {
117
+ continue;
118
+ }
119
+ if (applyText && applyText.length > 0 && !applyText.includes('{')) {
120
+ declarations.push({
121
+ property: 'apply',
122
+ rawValue: applyText,
123
+ normalizedValue: { type: 'literal', value: applyText },
124
+ mechanism: 'css',
125
+ filePath,
126
+ line,
127
+ context: selector,
128
+ variantContext,
129
+ tokenRef: null,
130
+ });
131
+ }
132
+ }
133
+ }
134
+ // ---------------------------------------------------------------------------
135
+ // Tree-Sitter CSS Node Helpers
136
+ // ---------------------------------------------------------------------------
137
+ /**
138
+ * tree-sitter-css does NOT use field names (childForFieldName). Children are
139
+ * discovered by type via namedChild iteration on the raw TreeSitterNode.
140
+ * This grammar is distinct from the tree-sitter-typescript one.
141
+ */
142
+ /**
143
+ * Find the first named child of a TreeSitterNode with the given type.
144
+ */
145
+ function findNamedChild(node, type) {
146
+ for (let i = 0; i < node.namedChildCount; i++) {
147
+ const child = node.namedChild(i);
148
+ if (child?.type === type)
149
+ return child;
150
+ }
151
+ return null;
152
+ }
153
+ /**
154
+ * Get property and raw value from a declaration's raw TreeSitterNode.
155
+ * tree-sitter-css uses `property_name` for the property and various typed
156
+ * children for the value (color_value, integer_value, plain_value,
157
+ * call_expression, etc.). We extract the property from the property_name
158
+ * child and the raw value from the full declaration text after the colon.
159
+ */
160
+ function getPropertyAndValue(declRaw) {
161
+ const propNode = findNamedChild(declRaw, 'property_name');
162
+ if (!propNode)
163
+ return null;
164
+ const property = propNode.text.trim();
165
+ if (!property)
166
+ return null;
167
+ // Extract raw value from the full declaration text after the property + colon
168
+ const fullText = declRaw.text;
169
+ // Find the colon that separates property from value.
170
+ // Use indexOf on the full text — the property_name child's text tells us
171
+ // where the property ends, but the colon might be outside the child span
172
+ // (tree-sitter may or may not include it in property_name).
173
+ // Safe approach: scan from after the property_name child's end offset.
174
+ const propEndInDecl = propNode.endIndex - declRaw.startIndex;
175
+ const afterProp = fullText.slice(propEndInDecl);
176
+ const colonIdx = afterProp.indexOf(':');
177
+ const rawValue = colonIdx !== -1 ? afterProp.slice(colonIdx + 1).replace(/;\s*$/, '').trim() : '';
178
+ return { property, rawValue };
179
+ }
180
+ // ---------------------------------------------------------------------------
181
+ // AST Extraction Helpers
182
+ // ---------------------------------------------------------------------------
183
+ /**
184
+ * Create a synthetic AST from a subtree node for use with findNodes.
185
+ * findNodes needs an AST object, but we find nodes within subtrees.
186
+ */
187
+ function subtreeAST(ast, root) {
188
+ return { root, language: ast.language, filePath: ast.filePath, errors: [] };
189
+ }
190
+ /**
191
+ * Wrap a TreeSitterNode as an ASTNode for use with adapter methods that
192
+ * expect ASTNode input.
193
+ */
194
+ function wrapAsASTNode(raw) {
195
+ return {
196
+ type: raw.type,
197
+ range: [raw.startIndex, raw.endIndex],
198
+ location: {
199
+ start: {
200
+ line: raw.startPosition.row + 1,
201
+ column: raw.startPosition.column + 1,
202
+ },
203
+ end: {
204
+ line: raw.endPosition.row + 1,
205
+ column: raw.endPosition.column + 1,
206
+ },
207
+ },
208
+ children: raw.namedChildren.map((c) => wrapAsASTNode(c)),
209
+ raw,
210
+ };
211
+ }
212
+ // ---------------------------------------------------------------------------
213
+ // Public API
214
+ // ---------------------------------------------------------------------------
215
+ /**
216
+ * Extract normalized CSS declarations from a parsed CSS AST.
217
+ *
218
+ * Replaces the regex-based extractFromCSS() + extractRuleSets() + the
219
+ * declaration-extraction part of extractDeclarationsFromBlock() for .css files.
220
+ *
221
+ * Parser bugs eliminated by AST-based approach:
222
+ * Bug 1: Whitespace before @-rules — @-rules are at_rule nodes, always correct
223
+ * Bug 3: Nested @-rule closing brace — tree-sitter handles brace matching
224
+ * Bug 4: findSelectorStart walking past boundaries — no backward scanning needed
225
+ * Bug 5: CSS comments as class names — comment nodes are typed, filtered out
226
+ */
227
+ export function extractDeclarationsFromCSSAst(ast, adapter, filePath, sourceCode) {
228
+ const declarations = [];
229
+ // Find all rule_sets
230
+ const ruleSets = adapter.findNodes(ast, { type: 'rule_set' });
231
+ for (const ruleSet of ruleSets) {
232
+ const rawNode = ruleSet.raw;
233
+ // tree-sitter-css: rule_set has named children 'selectors' and 'block'
234
+ const selectorsRaw = findNamedChild(rawNode, 'selectors');
235
+ if (!selectorsRaw)
236
+ continue;
237
+ const selectorsAst = wrapAsASTNode(selectorsRaw);
238
+ const selector = getSelectorText(selectorsAst, sourceCode);
239
+ if (!selector)
240
+ continue;
241
+ // Get variant context from at_rule ancestors
242
+ const variantContext = getVariantContext(ruleSet, adapter, sourceCode);
243
+ // Find declarations inside this rule_set
244
+ const subAST = subtreeAST(ast, ruleSet);
245
+ const declNodes = adapter.findNodes(subAST, { type: 'declaration' });
246
+ for (const decl of declNodes) {
247
+ const raw = decl.raw;
248
+ const pv = getPropertyAndValue(raw);
249
+ if (!pv)
250
+ continue;
251
+ const { property, rawValue } = pv;
252
+ if (!property || !rawValue)
253
+ continue;
254
+ const normalizedValue = normalizeValue(rawValue, property);
255
+ const expanded = expandShorthand(property, rawValue, normalizedValue);
256
+ const line = decl.location.start.line;
257
+ // Extract token ref from var() values or custom property names
258
+ const tokenRef = property.startsWith('--')
259
+ ? property
260
+ : (rawValue.trim().startsWith('var(') ? extractTokenRef(rawValue) : null);
261
+ for (const exp of expanded) {
262
+ declarations.push({
263
+ property: exp.property,
264
+ rawValue: exp.rawValue,
265
+ normalizedValue: exp.normalizedValue,
266
+ mechanism: 'css',
267
+ filePath,
268
+ line,
269
+ context: selector,
270
+ variantContext,
271
+ tokenRef,
272
+ });
273
+ }
274
+ }
275
+ // Handle @apply directives (Tailwind extension)
276
+ const blockRaw = findNamedChild(rawNode, 'block');
277
+ if (blockRaw) {
278
+ const blockAst = wrapAsASTNode(blockRaw);
279
+ extractApplyDirectives(blockAst, sourceCode, selector, variantContext, filePath, declarations);
280
+ }
281
+ }
282
+ // Handle @keyframes blocks — tree-sitter-css uses keyframes_statement with
283
+ // keyframe_block_list children, not rule_set.
284
+ const keyframeStmts = adapter.findNodes(ast, { type: 'keyframes_statement' });
285
+ for (const kf of keyframeStmts) {
286
+ const kfRaw = kf.raw;
287
+ const kfName = findNamedChild(kfRaw, 'keyframes_name')?.text ?? 'unknown';
288
+ const kfSubAST = subtreeAST(ast, kf);
289
+ const kfDeclNodes = adapter.findNodes(kfSubAST, { type: 'declaration' });
290
+ for (const decl of kfDeclNodes) {
291
+ // Find which keyframe_block this declaration belongs to
292
+ let parent = adapter.getParent(decl);
293
+ let kfSelector = '(unknown)';
294
+ while (parent && parent.type !== 'keyframes_statement') {
295
+ if (parent.type === 'keyframe_block') {
296
+ const selChild = parent.raw.namedChildren.find((c) => c.type === 'from' || c.type === 'to' || c.type === 'integer_value');
297
+ kfSelector = selChild?.text ?? '(unknown)';
298
+ break;
299
+ }
300
+ parent = adapter.getParent(parent);
301
+ }
302
+ const raw = decl.raw;
303
+ const pv = getPropertyAndValue(raw);
304
+ if (!pv)
305
+ continue;
306
+ const { property, rawValue } = pv;
307
+ if (!property || !rawValue)
308
+ continue;
309
+ const normalizedValue = normalizeValue(rawValue, property);
310
+ const expanded = expandShorthand(property, rawValue, normalizedValue);
311
+ const line = decl.location.start.line;
312
+ const tokenRef = property.startsWith('--')
313
+ ? property
314
+ : (rawValue.trim().startsWith('var(') ? extractTokenRef(rawValue) : null);
315
+ for (const exp of expanded) {
316
+ declarations.push({
317
+ property: exp.property,
318
+ rawValue: exp.rawValue,
319
+ normalizedValue: exp.normalizedValue,
320
+ mechanism: 'css',
321
+ filePath,
322
+ line,
323
+ context: kfSelector,
324
+ variantContext: `@keyframes ${kfName}`,
325
+ tokenRef,
326
+ });
327
+ }
328
+ }
329
+ }
330
+ // Handle top-level declarations (not inside a rule_set or keyframe block — valid CSS)
331
+ const allDecls = adapter.findNodes(ast, { type: 'declaration' });
332
+ for (const decl of allDecls) {
333
+ // Skip declarations that are inside a rule_set or keyframe block (already processed above)
334
+ const parent = adapter.getParent(decl);
335
+ let ancestor = parent;
336
+ let inKeyframe = false;
337
+ while (ancestor) {
338
+ if (ancestor.type === 'keyframe_block') {
339
+ inKeyframe = true;
340
+ break;
341
+ }
342
+ ancestor = adapter.getParent(ancestor);
343
+ }
344
+ if (parent?.type === 'rule_set' || parent?.type === 'block' || inKeyframe)
345
+ continue;
346
+ const raw = decl.raw;
347
+ const pv = getPropertyAndValue(raw);
348
+ if (!pv)
349
+ continue;
350
+ const { property, rawValue } = pv;
351
+ if (!property || !rawValue)
352
+ continue;
353
+ const normalizedValue = normalizeValue(rawValue, property);
354
+ const expanded = expandShorthand(property, rawValue, normalizedValue);
355
+ const line = decl.location.start.line;
356
+ for (const exp of expanded) {
357
+ declarations.push({
358
+ property: exp.property,
359
+ rawValue: exp.rawValue,
360
+ normalizedValue: exp.normalizedValue,
361
+ mechanism: 'css',
362
+ filePath,
363
+ line,
364
+ context: '(top-level)',
365
+ variantContext: null,
366
+ tokenRef: property.startsWith('--')
367
+ ? property
368
+ : (rawValue.trim().startsWith('var(') ? extractTokenRef(rawValue) : null),
369
+ });
370
+ }
371
+ }
372
+ return declarations;
373
+ }
374
+ /**
375
+ * Extract CSS custom property tokens from a parsed CSS AST.
376
+ *
377
+ * Replaces the regex-based extractTokens() for .css files.
378
+ * Finds declarations where the property starts with -- and extracts
379
+ * the token name and value.
380
+ */
381
+ export function extractTokensFromCSSAst(ast, adapter, filePath) {
382
+ const tokens = [];
383
+ const allDecls = adapter.findNodes(ast, { type: 'declaration' });
384
+ for (const decl of allDecls) {
385
+ const raw = decl.raw;
386
+ const pv = getPropertyAndValue(raw);
387
+ if (!pv)
388
+ continue;
389
+ const { property, rawValue } = pv;
390
+ if (!property.startsWith('--'))
391
+ continue;
392
+ const name = property.trim();
393
+ const value = rawValue.trim();
394
+ if (name && value) {
395
+ tokens.push({
396
+ name,
397
+ value,
398
+ filePath,
399
+ mechanism: 'css-custom-property',
400
+ });
401
+ }
402
+ }
403
+ return tokens;
404
+ }
405
+ /**
406
+ * Extract CSS class usage from a parsed CSS AST.
407
+ *
408
+ * Replaces the regex-based class name extraction from selectors for .css files.
409
+ * Finds class_name nodes anywhere in the AST — each represents one CSS class
410
+ * definition. comment nodes are typed and naturally excluded (Bug 5 fix).
411
+ */
412
+ export function extractClassUsageFromCSSAst(ast, adapter, filePath) {
413
+ const usage = [];
414
+ // class_name nodes represent CSS class selectors like .my-class
415
+ // They can appear inside selectors, pseudo-class arguments, etc.
416
+ const classNodes = adapter.findNodes(ast, { type: 'class_name' });
417
+ for (const node of classNodes) {
418
+ // Only extract class_name nodes that are children of class_selector.
419
+ // pseudo_class_selector (:hover, :focus, :nth-child) and
420
+ // pseudo_element_selector (::before, ::after) also contain class_name
421
+ // children — those are pseudo-classes, not CSS class definitions.
422
+ const parent = adapter.getParent(node);
423
+ if (!parent || parent.type !== 'class_selector')
424
+ continue;
425
+ const raw = node.raw;
426
+ const className = raw.text;
427
+ if (className) {
428
+ usage.push({
429
+ className,
430
+ filePath,
431
+ line: node.location.start.line,
432
+ mechanism: 'class',
433
+ unresolvable: false,
434
+ });
435
+ }
436
+ }
437
+ return usage;
438
+ }
439
+ //# sourceMappingURL=cssAstExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cssAstExtractor.js","sourceRoot":"","sources":["../../src/styles/cssAstExtractor.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Style declaration extractor — Spec 10.
3
3
  *
4
- * Extracts normalized declarations from all five style mechanisms:
5
- * 1. CSS/SCSS files — tree-sitter-css parsed rule sets
4
+ * Extracts normalized declarations from style mechanisms:
5
+ * 1. SCSS files — regex-based rule set extraction (CSS handled by cssAstExtractor.ts)
6
6
  * 2. Tailwind — className/class attributes in JSX/HTML
7
7
  * 3. Inline styles — style={{...}} object expressions
8
8
  * 4. CSS-in-JS — styled-components / emotion tagged templates
@@ -1 +1 @@
1
- {"version":3,"file":"styleExtractor.d.ts","sourceRoot":"","sources":["../../src/styles/styleExtractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,eAAe,EAAW,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAMrE;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,GAAG,EACT,cAAc,CAAC,EAAE,mBAAmB,GACnC,qBAAqB,EAAE,CAqBzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,UAAU,EAAE,CAsBd;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,mBAAmB,CAGrB;AAkMD,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,YAAY,EAAE,qBAAqB,EAAE,EACrC,QAAQ,EAAE,MAAM,GACf,IAAI,CAgEN"}
1
+ {"version":3,"file":"styleExtractor.d.ts","sourceRoot":"","sources":["../../src/styles/styleExtractor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,GAAG,EAAE,eAAe,EAAW,MAAM,uBAAuB,CAAC;AAC3E,OAAO,KAAK,EAAE,qBAAqB,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAIpF,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAMrE;;;;;;;;;;;GAWG;AACH,wBAAgB,mBAAmB,CACjC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,eAAe,EACxB,UAAU,EAAE,MAAM,EAClB,GAAG,CAAC,EAAE,GAAG,EACT,cAAc,CAAC,EAAE,mBAAmB,GACnC,qBAAqB,EAAE,CAsBzB;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAC3B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,UAAU,EAAE,CAwBd;AAED;;GAEG;AACH,wBAAgB,uBAAuB,CACrC,WAAW,EAAE,MAAM,EACnB,QAAQ,CAAC,EAAE,mBAAmB,GAC7B,mBAAmB,CAGrB;AA0ND,wBAAgB,4BAA4B,CAC1C,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,EAChB,SAAS,EAAE,cAAc,EACzB,QAAQ,EAAE,MAAM,EAChB,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,YAAY,EAAE,qBAAqB,EAAE,EACrC,QAAQ,EAAE,MAAM,GACf,IAAI,CAuFN"}
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Style declaration extractor — Spec 10.
3
3
  *
4
- * Extracts normalized declarations from all five style mechanisms:
5
- * 1. CSS/SCSS files — tree-sitter-css parsed rule sets
4
+ * Extracts normalized declarations from style mechanisms:
5
+ * 1. SCSS files — regex-based rule set extraction (CSS handled by cssAstExtractor.ts)
6
6
  * 2. Tailwind — className/class attributes in JSX/HTML
7
7
  * 3. Inline styles — style={{...}} object expressions
8
8
  * 4. CSS-in-JS — styled-components / emotion tagged templates
@@ -31,10 +31,11 @@ import { loadTailwindConfig } from './tailwindConfigLoader.js';
31
31
  export function extractDeclarations(filePath, adapter, sourceCode, ast, tailwindTokens) {
32
32
  const ext = filePath.includes('.') ? filePath.slice(filePath.lastIndexOf('.')) : '';
33
33
  switch (ext) {
34
- case '.css':
35
- return extractFromCSS(filePath, sourceCode, 'css');
34
+ // .css files are handled by the AST pipeline (cssAstExtractor.ts +
35
+ // createStylesCssVisitor in pipelineAdapters.ts). SCSS stays on the
36
+ // regex path because tree-sitter-css is not an SCSS grammar.
36
37
  case '.scss':
37
- return extractFromCSS(filePath, sourceCode, 'scss');
38
+ return extractRuleSetsFromSCSS(filePath, sourceCode);
38
39
  case '.tsx':
39
40
  case '.jsx':
40
41
  case '.ts':
@@ -54,8 +55,10 @@ export function extractDeclarations(filePath, adapter, sourceCode, ast, tailwind
54
55
  * and produce design tokens.
55
56
  */
56
57
  export function extractTokens(filePath, sourceCode) {
58
+ // .css tokens are extracted via the AST pipeline (extractTokensFromCSSAst).
59
+ // SCSS stays on the regex path since tree-sitter-css is not an SCSS grammar.
57
60
  const ext = filePath.includes('.') ? filePath.slice(filePath.lastIndexOf('.')) : '';
58
- if (ext !== '.css' && ext !== '.scss')
61
+ if (ext !== '.scss')
59
62
  return [];
60
63
  const tokens = [];
61
64
  const varRegex = /--([a-zA-Z0-9_-]+)\s*:\s*([^;};]+)/g;
@@ -85,12 +88,9 @@ export function getOrLoadTailwindTokens(projectRoot, existing) {
85
88
  // ---------------------------------------------------------------------------
86
89
  // Sub-extractor: CSS / SCSS
87
90
  // ---------------------------------------------------------------------------
88
- function extractFromCSS(filePath, sourceCode, mechanism) {
91
+ function extractRuleSetsFromSCSS(filePath, sourceCode) {
89
92
  const declarations = [];
90
- // Extract rule set contents
91
- // We use regex for extraction because the CSS adapter's AST walk is heavy
92
- // and we only need declaration extraction, not structural analysis.
93
- extractRuleSets(sourceCode, filePath, mechanism, declarations);
93
+ extractRuleSets(sourceCode, filePath, declarations);
94
94
  return declarations;
95
95
  }
96
96
  /**
@@ -100,14 +100,20 @@ function extractFromCSS(filePath, sourceCode, mechanism) {
100
100
  * parsing for declaration extraction. The tree-sitter adapter handles structural
101
101
  * analysis when needed.
102
102
  */
103
- function extractRuleSets(css, filePath, mechanism, declarations) {
103
+ function extractRuleSets(css, filePath, declarations) {
104
104
  // Track current context (selector, at-rule)
105
105
  const lines = css.split('\n');
106
106
  // Regex approach: find rule blocks with their selectors
107
107
  let i = 0;
108
108
  let atRuleStack = [];
109
109
  while (i < css.length) {
110
- // Skip whitespace and comments
110
+ // Skip whitespace
111
+ while (i < css.length && (css[i] === ' ' || css[i] === '\t' || css[i] === '\n' || css[i] === '\r')) {
112
+ i++;
113
+ }
114
+ if (i >= css.length)
115
+ break;
116
+ // Skip comments
111
117
  if (css[i] === '/' && css[i + 1] === '*') {
112
118
  const end = css.indexOf('*/', i + 2);
113
119
  if (end === -1)
@@ -143,13 +149,27 @@ function extractRuleSets(css, filePath, mechanism, declarations) {
143
149
  continue;
144
150
  }
145
151
  }
152
+ // Handle closing brace of enclosing @-rule before the next opening brace.
153
+ // Without this, the parser never knows when to exit an @-rule block and
154
+ // stays inside it forever, corrupting all subsequent selector extraction.
155
+ const nextClose = css.indexOf('}', i);
156
+ if (nextClose !== -1) {
157
+ const nextOpen = css.indexOf('{', i);
158
+ if (nextOpen === -1 || nextClose < nextOpen) {
159
+ if (atRuleStack.length > 0) {
160
+ atRuleStack.pop();
161
+ }
162
+ i = nextClose + 1;
163
+ continue;
164
+ }
165
+ }
146
166
  // Find the start of a rule set: selector { ... }
147
167
  const braceOpen = css.indexOf('{', i);
148
168
  if (braceOpen === -1)
149
169
  break;
150
170
  // Extract the selector (everything between last } and {)
151
171
  const selectorStart = findSelectorStart(css, braceOpen);
152
- const selector = css.slice(selectorStart, braceOpen).trim();
172
+ const selector = stripAllBlockComments(css.slice(selectorStart, braceOpen)).trim();
153
173
  if (!selector || selector === '}' || selector.startsWith('@')) {
154
174
  i = braceOpen + 1;
155
175
  continue;
@@ -162,7 +182,7 @@ function extractRuleSets(css, filePath, mechanism, declarations) {
162
182
  }
163
183
  // Extract declarations inside this block
164
184
  const block = css.slice(braceOpen + 1, braceClose);
165
- extractDeclarationsFromBlock(block, filePath, mechanism, selector, atRuleStack.length > 0 ? atRuleStack.join(', ') : null, declarations, css.slice(0, braceOpen).split('\n').length);
185
+ extractDeclarationsFromBlock(block, filePath, 'scss', selector, atRuleStack.length > 0 ? atRuleStack.join(', ') : null, declarations, css.slice(0, braceOpen).split('\n').length);
166
186
  i = braceClose + 1;
167
187
  }
168
188
  }
@@ -179,6 +199,16 @@ function findSelectorStart(css, braceOpen) {
179
199
  let depth = 0;
180
200
  while (i >= 0) {
181
201
  if (css[i] === '}') {
202
+ // When we find a } at depth 0, it's the closing brace of the
203
+ // preceding rule block — the selector starts right after it.
204
+ if (depth === 0) {
205
+ // Skip whitespace after the }
206
+ let j = i + 1;
207
+ while (j < css.length && (css[j] === ' ' || css[j] === '\t' || css[j] === '\n' || css[j] === '\r')) {
208
+ j++;
209
+ }
210
+ return j;
211
+ }
182
212
  depth++;
183
213
  i--;
184
214
  }
@@ -270,8 +300,31 @@ export function extractDeclarationsFromBlock(block, filePath, mechanism, selecto
270
300
  // Accumulate the line into the buffer
271
301
  if (trimmed)
272
302
  buffer += (buffer ? ' ' : '') + trimmed;
303
+ // Handle @apply directives — collect as synthetic declarations so CSS
304
+ // selector context is available for class-name discovery. @apply uses no
305
+ // colon, so it would be silently skipped by the colon-based branch below.
306
+ if (buffer.startsWith('@apply ')) {
307
+ const semiIdx = buffer.indexOf(';');
308
+ if (semiIdx !== -1) {
309
+ const applyValue = buffer.slice(7, semiIdx).trim();
310
+ buffer = buffer.slice(semiIdx + 1).trim();
311
+ if (applyValue && !applyValue.includes('{')) {
312
+ declarations.push({
313
+ property: 'apply',
314
+ rawValue: applyValue,
315
+ normalizedValue: { type: 'literal', value: applyValue },
316
+ mechanism,
317
+ filePath,
318
+ line: baseLine + lineInBlock,
319
+ context: selector,
320
+ variantContext,
321
+ tokenRef: null,
322
+ });
323
+ }
324
+ }
325
+ }
273
326
  // Check if this buffer contains a complete declaration
274
- if (buffer.includes(':')) {
327
+ else if (buffer.includes(':')) {
275
328
  const semiIdx = buffer.indexOf(';');
276
329
  if (semiIdx !== -1) {
277
330
  const decl = buffer.slice(0, semiIdx).trim();