code-auditor-mcp 3.1.1 → 3.4.0
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.
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +665 -1
- package/README.md +85 -11
- package/dist/__test_tmp__/spec18_test.d.ts +2 -0
- package/dist/__test_tmp__/spec18_test.d.ts.map +1 -0
- package/dist/__test_tmp__/spec18_test.js +5 -0
- package/dist/__test_tmp__/spec18_test.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts +6 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js +12 -0
- package/dist/__tests__/fixtures/spec-20/scripts/tool.js.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts +7 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.d.ts.map +1 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js +13 -0
- package/dist/__tests__/fixtures/spec-20/src/demo-module.js.map +1 -0
- package/dist/analyzers/analyzerUtils.js +2 -2
- package/dist/analyzers/analyzerUtils.js.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/APIContractAnalyzer.js +2 -2
- package/dist/analyzers/cross-language/APIContractAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts +3 -0
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js +3 -3
- package/dist/analyzers/cross-language/CrossLanguageSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.d.ts.map +1 -1
- package/dist/analyzers/cross-language/SchemaValidator.js +2 -2
- package/dist/analyzers/cross-language/SchemaValidator.js.map +1 -1
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts +83 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js +611 -0
- package/dist/analyzers/crossDomain/CrossDomainAnalyzer.js.map +1 -0
- package/dist/analyzers/documentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/documentationAnalyzer.js +8 -4
- package/dist/analyzers/documentationAnalyzer.js.map +1 -1
- package/dist/analyzers/orm/adapterRegistry.d.ts +41 -0
- package/dist/analyzers/orm/adapterRegistry.d.ts.map +1 -0
- package/dist/analyzers/orm/adapterRegistry.js +73 -0
- package/dist/analyzers/orm/adapterRegistry.js.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts +42 -0
- package/dist/analyzers/orm/drizzleAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/drizzleAdapter.js +166 -0
- package/dist/analyzers/orm/drizzleAdapter.js.map +1 -0
- package/dist/analyzers/orm/index.d.ts +14 -0
- package/dist/analyzers/orm/index.d.ts.map +1 -0
- package/dist/analyzers/orm/index.js +24 -0
- package/dist/analyzers/orm/index.js.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts +45 -0
- package/dist/analyzers/orm/prismaAdapter.d.ts.map +1 -0
- package/dist/analyzers/orm/prismaAdapter.js +170 -0
- package/dist/analyzers/orm/prismaAdapter.js.map +1 -0
- package/dist/analyzers/orm/types.d.ts +63 -0
- package/dist/analyzers/orm/types.d.ts.map +1 -0
- package/dist/analyzers/orm/types.js +10 -0
- package/dist/analyzers/orm/types.js.map +1 -0
- package/dist/analyzers/provenance.d.ts +133 -0
- package/dist/analyzers/provenance.d.ts.map +1 -0
- package/dist/analyzers/provenance.js +1041 -0
- package/dist/analyzers/provenance.js.map +1 -0
- package/dist/analyzers/reactAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/reactAnalyzer.js +117 -1
- package/dist/analyzers/reactAnalyzer.js.map +1 -1
- package/dist/analyzers/ruleRegistry.d.ts +38 -0
- package/dist/analyzers/ruleRegistry.d.ts.map +1 -0
- package/dist/analyzers/ruleRegistry.js +123 -0
- package/dist/analyzers/ruleRegistry.js.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts +58 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js +445 -0
- package/dist/analyzers/universal/UniversalConventionsAnalyzer.js.map +1 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts +52 -0
- package/dist/analyzers/universal/UniversalDRYAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js +108 -26
- package/dist/analyzers/universal/UniversalDRYAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts +64 -0
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js +259 -53
- package/dist/analyzers/universal/UniversalDataAccessAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js +5 -15
- package/dist/analyzers/universal/UniversalDocumentationAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js +10 -10
- package/dist/analyzers/universal/UniversalSOLIDAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts +25 -0
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.d.ts.map +1 -1
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js +250 -59
- package/dist/analyzers/universal/UniversalSchemaAnalyzer.js.map +1 -1
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts +103 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.d.ts.map +1 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js +750 -0
- package/dist/analyzers/universal/UniversalStylesAnalyzer.js.map +1 -0
- package/dist/auditRunner.d.ts.map +1 -1
- package/dist/auditRunner.js +483 -3
- package/dist/auditRunner.js.map +1 -1
- package/dist/baseline.d.ts +98 -0
- package/dist/baseline.d.ts.map +1 -0
- package/dist/baseline.js +184 -0
- package/dist/baseline.js.map +1 -0
- package/dist/churn/churnExtractor.d.ts +32 -0
- package/dist/churn/churnExtractor.d.ts.map +1 -0
- package/dist/churn/churnExtractor.js +261 -0
- package/dist/churn/churnExtractor.js.map +1 -0
- package/dist/cli.js +1215 -11
- package/dist/cli.js.map +1 -1
- package/dist/codeIndexDB-enhanced.js +1 -1
- package/dist/codeIndexDB-enhanced.js.map +1 -1
- package/dist/codeIndexDB.d.ts +101 -0
- package/dist/codeIndexDB.d.ts.map +1 -1
- package/dist/codeIndexDB.js +661 -5
- package/dist/codeIndexDB.js.map +1 -1
- package/dist/config/configLoader.d.ts.map +1 -1
- package/dist/config/configLoader.js +95 -1
- package/dist/config/configLoader.js.map +1 -1
- package/dist/config/defaults.d.ts +72 -1
- package/dist/config/defaults.d.ts.map +1 -1
- package/dist/config/defaults.js +146 -15
- package/dist/config/defaults.js.map +1 -1
- package/dist/config/pathProfiles.d.ts +38 -0
- package/dist/config/pathProfiles.d.ts.map +1 -0
- package/dist/config/pathProfiles.js +47 -0
- package/dist/config/pathProfiles.js.map +1 -0
- package/dist/conventions/conventionMiner.d.ts +63 -0
- package/dist/conventions/conventionMiner.d.ts.map +1 -0
- package/dist/conventions/conventionMiner.js +696 -0
- package/dist/conventions/conventionMiner.js.map +1 -0
- package/dist/coverage/coverageService.d.ts +49 -0
- package/dist/coverage/coverageService.d.ts.map +1 -0
- package/dist/coverage/coverageService.js +117 -0
- package/dist/coverage/coverageService.js.map +1 -0
- package/dist/coverage/istanbulParser.d.ts +84 -0
- package/dist/coverage/istanbulParser.d.ts.map +1 -0
- package/dist/coverage/istanbulParser.js +120 -0
- package/dist/coverage/istanbulParser.js.map +1 -0
- package/dist/coverage/lcovParser.d.ts +40 -0
- package/dist/coverage/lcovParser.d.ts.map +1 -0
- package/dist/coverage/lcovParser.js +168 -0
- package/dist/coverage/lcovParser.js.map +1 -0
- package/dist/fingerprint.d.ts +17 -0
- package/dist/fingerprint.d.ts.map +1 -1
- package/dist/fingerprint.js +42 -0
- package/dist/fingerprint.js.map +1 -1
- package/dist/grammars/tree-sitter-css.wasm +0 -0
- package/dist/graph/blastRadius.d.ts +27 -0
- package/dist/graph/blastRadius.d.ts.map +1 -0
- package/dist/graph/blastRadius.js +150 -0
- package/dist/graph/blastRadius.js.map +1 -0
- package/dist/graph/callGraph.d.ts +78 -0
- package/dist/graph/callGraph.d.ts.map +1 -0
- package/dist/graph/callGraph.js +484 -0
- package/dist/graph/callGraph.js.map +1 -0
- package/dist/graph/importGraph.d.ts +91 -0
- package/dist/graph/importGraph.d.ts.map +1 -0
- package/dist/graph/importGraph.js +602 -0
- package/dist/graph/importGraph.js.map +1 -0
- package/dist/graph/outputFormatter.d.ts +55 -0
- package/dist/graph/outputFormatter.d.ts.map +1 -0
- package/dist/graph/outputFormatter.js +283 -0
- package/dist/graph/outputFormatter.js.map +1 -0
- package/dist/hooks/codex.d.ts +36 -1
- package/dist/hooks/codex.d.ts.map +1 -1
- package/dist/hooks/codex.js +88 -54
- package/dist/hooks/codex.js.map +1 -1
- package/dist/hotspots/hotspotScorer.d.ts +20 -0
- package/dist/hotspots/hotspotScorer.d.ts.map +1 -0
- package/dist/hotspots/hotspotScorer.js +202 -0
- package/dist/hotspots/hotspotScorer.js.map +1 -0
- package/dist/invariants/invariant-rules.schema.json +42 -1
- package/dist/invariants/ruleEngine.d.ts +4 -1
- package/dist/invariants/ruleEngine.d.ts.map +1 -1
- package/dist/invariants/ruleEngine.js +141 -5
- package/dist/invariants/ruleEngine.js.map +1 -1
- package/dist/invariants/ruleValidator.d.ts.map +1 -1
- package/dist/invariants/ruleValidator.js +62 -1
- package/dist/invariants/ruleValidator.js.map +1 -1
- package/dist/invariants/types.d.ts +23 -2
- package/dist/invariants/types.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts +1 -1
- package/dist/languages/UniversalAnalyzer.d.ts.map +1 -1
- package/dist/languages/UniversalAnalyzer.js +61 -4
- package/dist/languages/UniversalAnalyzer.js.map +1 -1
- package/dist/languages/index.d.ts +1 -0
- package/dist/languages/index.d.ts.map +1 -1
- package/dist/languages/index.js +3 -0
- package/dist/languages/index.js.map +1 -1
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts +59 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.d.ts.map +1 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js +236 -0
- package/dist/languages/tree-sitter/TreeSitterCssAdapter.js.map +1 -0
- package/dist/languages/tree-sitter/parser.d.ts.map +1 -1
- package/dist/languages/tree-sitter/parser.js +2 -0
- package/dist/languages/tree-sitter/parser.js.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.d.ts.map +1 -1
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js +61 -10
- package/dist/languages/typescript/TreeSitterTypeScriptAdapter.js.map +1 -1
- package/dist/ledger.d.ts +128 -0
- package/dist/ledger.d.ts.map +1 -0
- package/dist/ledger.js +345 -0
- package/dist/ledger.js.map +1 -0
- package/dist/mcp-tools/projectTasks.d.ts.map +1 -1
- package/dist/mcp-tools/projectTasks.js +9 -14
- package/dist/mcp-tools/projectTasks.js.map +1 -1
- package/dist/mcp.js +0 -0
- package/dist/mcpAuditJobs.d.ts.map +1 -1
- package/dist/mcpAuditJobs.js +3 -0
- package/dist/mcpAuditJobs.js.map +1 -1
- package/dist/reporting/csvReportGenerator.d.ts.map +1 -1
- package/dist/reporting/csvReportGenerator.js +7 -1
- package/dist/reporting/csvReportGenerator.js.map +1 -1
- package/dist/reporting/htmlReportGenerator.d.ts.map +1 -1
- package/dist/reporting/htmlReportGenerator.js +5 -1
- package/dist/reporting/htmlReportGenerator.js.map +1 -1
- package/dist/reporting/jsonReportGenerator.d.ts.map +1 -1
- package/dist/reporting/jsonReportGenerator.js +10 -2
- package/dist/reporting/jsonReportGenerator.js.map +1 -1
- package/dist/reporting/sarifReportGenerator.d.ts.map +1 -1
- package/dist/reporting/sarifReportGenerator.js +17 -17
- package/dist/reporting/sarifReportGenerator.js.map +1 -1
- package/dist/scripts/runBench.d.ts +94 -0
- package/dist/scripts/runBench.d.ts.map +1 -0
- package/dist/scripts/runBench.js +1559 -0
- package/dist/scripts/runBench.js.map +1 -0
- package/dist/search/QueryParser.d.ts.map +1 -1
- package/dist/search/QueryParser.js +51 -4
- package/dist/search/QueryParser.js.map +1 -1
- package/dist/services/CodeMapGenerator.d.ts +33 -0
- package/dist/services/CodeMapGenerator.d.ts.map +1 -1
- package/dist/services/CodeMapGenerator.js +404 -0
- package/dist/services/CodeMapGenerator.js.map +1 -1
- package/dist/styles/normalizer.d.ts +37 -0
- package/dist/styles/normalizer.d.ts.map +1 -0
- package/dist/styles/normalizer.js +301 -0
- package/dist/styles/normalizer.js.map +1 -0
- package/dist/styles/styleExtractor.d.ts +38 -0
- package/dist/styles/styleExtractor.d.ts.map +1 -0
- package/dist/styles/styleExtractor.js +630 -0
- package/dist/styles/styleExtractor.js.map +1 -0
- package/dist/styles/styleIndexer.d.ts +35 -0
- package/dist/styles/styleIndexer.d.ts.map +1 -0
- package/dist/styles/styleIndexer.js +266 -0
- package/dist/styles/styleIndexer.js.map +1 -0
- package/dist/styles/tailwindConfigLoader.d.ts +38 -0
- package/dist/styles/tailwindConfigLoader.d.ts.map +1 -0
- package/dist/styles/tailwindConfigLoader.js +374 -0
- package/dist/styles/tailwindConfigLoader.js.map +1 -0
- package/dist/styles/tailwindExpander.d.ts +38 -0
- package/dist/styles/tailwindExpander.d.ts.map +1 -0
- package/dist/styles/tailwindExpander.js +637 -0
- package/dist/styles/tailwindExpander.js.map +1 -0
- package/dist/styles/types.d.ts +92 -0
- package/dist/styles/types.d.ts.map +1 -0
- package/dist/styles/types.js +10 -0
- package/dist/styles/types.js.map +1 -0
- package/dist/symbols.d.ts +21 -0
- package/dist/symbols.d.ts.map +1 -0
- package/dist/symbols.js +31 -0
- package/dist/symbols.js.map +1 -0
- package/dist/types.d.ts +326 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -5
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/scripts/hook-audit.sh +5 -1
- package/plugin/skills/code-auditor/SKILL.md +51 -0
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js +0 -21
- package/dist/analyzers/__tests__/fixtures/spec-17/anonymous-callback-map.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/barrel-test-migration.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/direct-access-allow-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts +0 -9
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/exported-undocumented.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/for-loop-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js +0 -16
- package/dist/analyzers/__tests__/fixtures/spec-17/import-node-builtin.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts +0 -14
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/jsx-event-handler.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js +0 -22
- package/dist/analyzers/__tests__/fixtures/spec-17/nested-loops-query.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js +0 -20
- package/dist/analyzers/__tests__/fixtures/spec-17/nine-line-repeated.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js +0 -28
- package/dist/analyzers/__tests__/fixtures/spec-17/private-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js +0 -31
- package/dist/analyzers/__tests__/fixtures/spec-17/scope-all-config.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js +0 -18
- package/dist/analyzers/__tests__/fixtures/spec-17/sql-tagged-template.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts +0 -13
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js +0 -61
- package/dist/analyzers/__tests__/fixtures/spec-17/structural-similar-methods.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/template-prefix-table.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts +0 -11
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js +0 -49
- package/dist/analyzers/__tests__/fixtures/spec-17/token-identical-20-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js +0 -19
- package/dist/analyzers/__tests__/fixtures/spec-17/tsx-no-db-usage.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts +0 -37
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js +0 -73
- package/dist/analyzers/__tests__/fixtures/spec-17/twenty-methods-vs-complex.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts +0 -12
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js +0 -82
- package/dist/analyzers/__tests__/fixtures/spec-17/unique-method-74-lines.js.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts +0 -10
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.d.ts.map +0 -1
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js +0 -15
- package/dist/analyzers/__tests__/fixtures/spec-17/word-the-in-comment.js.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.d.ts +0 -46
- package/dist/analyzers/dataAccessAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dataAccessAnalyzer.js +0 -474
- package/dist/analyzers/dataAccessAnalyzer.js.map +0 -1
- package/dist/analyzers/dryAnalyzer.d.ts +0 -31
- package/dist/analyzers/dryAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/dryAnalyzer.js +0 -734
- package/dist/analyzers/dryAnalyzer.js.map +0 -1
- package/dist/analyzers/schemaAnalyzer.d.ts +0 -23
- package/dist/analyzers/schemaAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/schemaAnalyzer.js +0 -564
- package/dist/analyzers/schemaAnalyzer.js.map +0 -1
- package/dist/analyzers/solidAnalyzer.d.ts +0 -33
- package/dist/analyzers/solidAnalyzer.d.ts.map +0 -1
- package/dist/analyzers/solidAnalyzer.js +0 -847
- package/dist/analyzers/solidAnalyzer.js.map +0 -1
|
@@ -0,0 +1,750 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Universal Styles Analyzer — Spec 10 R3.
|
|
3
|
+
*
|
|
4
|
+
* Detects style fragmentation, value drift, token bypass, dead classes,
|
|
5
|
+
* off-scale values, mechanism fragmentation, declaration-set similarity,
|
|
6
|
+
* and z-index sprawl by querying the full style index (SQLite).
|
|
7
|
+
*
|
|
8
|
+
* Unlike other analyzers that process one AST at a time via analyzeAST(),
|
|
9
|
+
* this analyzer queries the cross-file style_declarations table so it can
|
|
10
|
+
* compute histograms, clusters, and distributions across the entire codebase.
|
|
11
|
+
*/
|
|
12
|
+
import { UniversalAnalyzer } from '../../languages/UniversalAnalyzer.js';
|
|
13
|
+
import { CodeIndexDB } from '../../codeIndexDB.js';
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Default configuration
|
|
16
|
+
// ---------------------------------------------------------------------------
|
|
17
|
+
export const DEFAULT_STYLES_CONFIG = {
|
|
18
|
+
minCorpus: 20,
|
|
19
|
+
colorDeltaE: 2.0,
|
|
20
|
+
outlierMaxShare: 0.05,
|
|
21
|
+
modeMinCount: 10,
|
|
22
|
+
scaleProperties: [
|
|
23
|
+
'margin', 'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
|
|
24
|
+
'padding', 'padding-top', 'padding-right', 'padding-bottom', 'padding-left',
|
|
25
|
+
'gap', 'row-gap', 'column-gap', 'font-size',
|
|
26
|
+
],
|
|
27
|
+
zIndexMaxDistinct: 6,
|
|
28
|
+
mechanismFragmentationMinMechanisms: 3,
|
|
29
|
+
declarationSetMinDeclarations: 5,
|
|
30
|
+
declarationSetSimilarityThreshold: 0.9,
|
|
31
|
+
};
|
|
32
|
+
// ---------------------------------------------------------------------------
|
|
33
|
+
// Tailwind v4 default spacing scale (px equivalents)
|
|
34
|
+
// ---------------------------------------------------------------------------
|
|
35
|
+
const TAILWIND_SPACING_PX = {
|
|
36
|
+
'0': 0, 'px': 1, '0.5': 2,
|
|
37
|
+
'1': 4, '2': 8, '3': 12, '4': 16,
|
|
38
|
+
'5': 20, '6': 24, '7': 28, '8': 32,
|
|
39
|
+
'9': 36, '10': 40, '11': 44, '12': 48,
|
|
40
|
+
'14': 56, '16': 64, '20': 80, '24': 96,
|
|
41
|
+
'28': 112, '32': 128, '36': 144, '40': 160,
|
|
42
|
+
'44': 176, '48': 192, '52': 208, '56': 224,
|
|
43
|
+
'60': 240, '64': 256, '72': 288, '80': 320,
|
|
44
|
+
'96': 384,
|
|
45
|
+
};
|
|
46
|
+
const TAILWIND_SCALE_VALUES = Object.values(TAILWIND_SPACING_PX).sort((a, b) => a - b);
|
|
47
|
+
// ---------------------------------------------------------------------------
|
|
48
|
+
// Analyzer
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
export class UniversalStylesAnalyzer extends UniversalAnalyzer {
|
|
51
|
+
name = 'styles';
|
|
52
|
+
description = 'Detects style fragmentation, value drift, token bypass, dead classes, ' +
|
|
53
|
+
'off-scale values, mechanism mixing, declaration-set similarity, and z-index sprawl';
|
|
54
|
+
category = 'style';
|
|
55
|
+
/**
|
|
56
|
+
* Override analyze() to query the full style index in one pass instead
|
|
57
|
+
* of per-file AST processing. The base class analyze() loop is bypassed.
|
|
58
|
+
*/
|
|
59
|
+
async analyze(files, config = {}, options = {}) {
|
|
60
|
+
const startTime = Date.now();
|
|
61
|
+
const cfg = { ...DEFAULT_STYLES_CONFIG, ...config };
|
|
62
|
+
const violations = [];
|
|
63
|
+
// Open DB from the project root
|
|
64
|
+
let rawDb = null;
|
|
65
|
+
try {
|
|
66
|
+
const db = CodeIndexDB.getInstance();
|
|
67
|
+
await db.initialize();
|
|
68
|
+
rawDb = db.rawDb;
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
return {
|
|
72
|
+
violations: [],
|
|
73
|
+
errors: [{ file: '', error: 'Failed to open style index database' }],
|
|
74
|
+
filesProcessed: 0,
|
|
75
|
+
executionTime: Date.now() - startTime,
|
|
76
|
+
metrics: { filesAnalyzed: 0, totalViolations: 0, executionTime: Date.now() - startTime },
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
if (!rawDb) {
|
|
80
|
+
return {
|
|
81
|
+
violations: [],
|
|
82
|
+
errors: [],
|
|
83
|
+
filesProcessed: 0,
|
|
84
|
+
executionTime: Date.now() - startTime,
|
|
85
|
+
metrics: { filesAnalyzed: 0, totalViolations: 0, executionTime: Date.now() - startTime },
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
// Query all declarations, tokens, and class usage
|
|
89
|
+
const declarations = this.queryDeclarations(rawDb);
|
|
90
|
+
const tokens = this.queryTokens(rawDb);
|
|
91
|
+
const classUsage = this.queryClassUsage(rawDb);
|
|
92
|
+
if (declarations.length === 0) {
|
|
93
|
+
return {
|
|
94
|
+
violations: [],
|
|
95
|
+
errors: [],
|
|
96
|
+
filesProcessed: files.length,
|
|
97
|
+
executionTime: Date.now() - startTime,
|
|
98
|
+
metrics: { filesAnalyzed: files.length, totalViolations: 0, executionTime: Date.now() - startTime },
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
// Build helpers
|
|
102
|
+
const tokenValueMap = new Map(); // normalized value → token name
|
|
103
|
+
for (const t of tokens) {
|
|
104
|
+
tokenValueMap.set(t.value, t.name);
|
|
105
|
+
}
|
|
106
|
+
// Declarations by property
|
|
107
|
+
const byProperty = new Map();
|
|
108
|
+
for (const d of declarations) {
|
|
109
|
+
const list = byProperty.get(d.property) || [];
|
|
110
|
+
list.push(d);
|
|
111
|
+
byProperty.set(d.property, list);
|
|
112
|
+
}
|
|
113
|
+
// Run detectors
|
|
114
|
+
violations.push(...this.detectValueDrift(byProperty, cfg, declarations));
|
|
115
|
+
violations.push(...this.detectOffScaleValues(byProperty, cfg));
|
|
116
|
+
violations.push(...this.detectUndefinedClasses(classUsage, declarations, byProperty));
|
|
117
|
+
violations.push(...this.detectTokenBypass(declarations, tokenValueMap, cfg));
|
|
118
|
+
violations.push(...this.detectMechanismFragmentation(declarations, cfg));
|
|
119
|
+
violations.push(...this.detectDeclarationSetSimilarity(declarations, cfg));
|
|
120
|
+
violations.push(...this.detectZIndexInventory(byProperty, cfg));
|
|
121
|
+
// Apply severity overrides from config
|
|
122
|
+
const severityOverrides = config.severityOverrides ?? {};
|
|
123
|
+
if (Object.keys(severityOverrides).length > 0) {
|
|
124
|
+
for (const v of violations) {
|
|
125
|
+
const override = severityOverrides[v.rule];
|
|
126
|
+
if (override) {
|
|
127
|
+
v.severity = override;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
const filtered = violations.filter(v => v.severity !== 'off');
|
|
132
|
+
return {
|
|
133
|
+
violations: filtered,
|
|
134
|
+
errors: [],
|
|
135
|
+
filesProcessed: files.length,
|
|
136
|
+
executionTime: Date.now() - startTime,
|
|
137
|
+
metrics: {
|
|
138
|
+
filesAnalyzed: files.length,
|
|
139
|
+
totalViolations: filtered.length,
|
|
140
|
+
executionTime: Date.now() - startTime,
|
|
141
|
+
},
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
/** Not used — we override analyze() directly. */
|
|
145
|
+
async analyzeAST(_ast, _adapter, _config, _sourceCode) {
|
|
146
|
+
return [];
|
|
147
|
+
}
|
|
148
|
+
// -----------------------------------------------------------------------
|
|
149
|
+
// Database queries
|
|
150
|
+
// -----------------------------------------------------------------------
|
|
151
|
+
queryDeclarations(rawDb) {
|
|
152
|
+
try {
|
|
153
|
+
return rawDb.prepare('SELECT * FROM style_declarations ORDER BY property, file_path, line').all();
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return [];
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
queryTokens(rawDb) {
|
|
160
|
+
try {
|
|
161
|
+
return rawDb.prepare('SELECT * FROM style_tokens').all();
|
|
162
|
+
}
|
|
163
|
+
catch {
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
queryClassUsage(rawDb) {
|
|
168
|
+
try {
|
|
169
|
+
return rawDb.prepare('SELECT * FROM style_class_usage').all();
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
// -----------------------------------------------------------------------
|
|
176
|
+
// Detector 1: Value Drift
|
|
177
|
+
// -----------------------------------------------------------------------
|
|
178
|
+
/**
|
|
179
|
+
* For each property with enough declarations, cluster values and flag
|
|
180
|
+
* low-share stragglers (outliers) as style drift.
|
|
181
|
+
*
|
|
182
|
+
* - Colors: cluster by delta-E distance (< colorDeltaE).
|
|
183
|
+
* - Non-colors: exact-value histogram; flag share < outlierMaxShare
|
|
184
|
+
* when the mode count ≥ modeMinCount.
|
|
185
|
+
*/
|
|
186
|
+
detectValueDrift(byProperty, cfg, allDecls) {
|
|
187
|
+
const violations = [];
|
|
188
|
+
for (const [property, decls] of byProperty) {
|
|
189
|
+
if (decls.length < cfg.minCorpus)
|
|
190
|
+
continue;
|
|
191
|
+
// Determine if this property holds color values
|
|
192
|
+
const isColorProp = this.isColorProperty(property);
|
|
193
|
+
if (isColorProp) {
|
|
194
|
+
violations.push(...this.detectColorDrift(property, decls, cfg));
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
violations.push(...this.detectExactValueDrift(property, decls, cfg));
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return violations;
|
|
201
|
+
}
|
|
202
|
+
isColorProperty(property) {
|
|
203
|
+
const colorProps = new Set([
|
|
204
|
+
'color', 'background-color', 'background', 'border-color',
|
|
205
|
+
'border-top-color', 'border-right-color', 'border-bottom-color',
|
|
206
|
+
'border-left-color', 'outline-color', 'fill', 'stroke',
|
|
207
|
+
'text-decoration-color', 'caret-color', 'column-rule-color',
|
|
208
|
+
'accent-color', 'scrollbar-color',
|
|
209
|
+
]);
|
|
210
|
+
return colorProps.has(property);
|
|
211
|
+
}
|
|
212
|
+
/**
|
|
213
|
+
* Color drift: cluster values by delta-E, flag stragglers.
|
|
214
|
+
*/
|
|
215
|
+
detectColorDrift(property, decls, cfg) {
|
|
216
|
+
const violations = [];
|
|
217
|
+
// Parse all color values
|
|
218
|
+
const colors = [];
|
|
219
|
+
for (const d of decls) {
|
|
220
|
+
const rgb = this.parseColorToRGB(d.raw_value);
|
|
221
|
+
if (rgb) {
|
|
222
|
+
colors.push({ decl: d, rgb });
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (colors.length < cfg.minCorpus)
|
|
226
|
+
return violations;
|
|
227
|
+
// Cluster by delta-E
|
|
228
|
+
const clusters = this.clusterByDeltaE(colors, cfg.colorDeltaE);
|
|
229
|
+
// Find the dominant cluster (largest)
|
|
230
|
+
clusters.sort((a, b) => b.length - a.length);
|
|
231
|
+
const dominant = clusters[0];
|
|
232
|
+
if (!dominant || dominant.length < cfg.modeMinCount)
|
|
233
|
+
return violations;
|
|
234
|
+
const dominantSize = dominant.length;
|
|
235
|
+
// Flag stragglers in non-dominant clusters
|
|
236
|
+
for (let i = 1; i < clusters.length; i++) {
|
|
237
|
+
const cluster = clusters[i];
|
|
238
|
+
const share = cluster.length / colors.length;
|
|
239
|
+
if (share < cfg.outlierMaxShare && dominantSize >= cfg.modeMinCount) {
|
|
240
|
+
// Report once per distinct color value in the straggler cluster
|
|
241
|
+
const seenValues = new Set();
|
|
242
|
+
for (const item of cluster) {
|
|
243
|
+
const normVal = item.decl.raw_value.toLowerCase();
|
|
244
|
+
if (seenValues.has(normVal))
|
|
245
|
+
continue;
|
|
246
|
+
seenValues.add(normVal);
|
|
247
|
+
violations.push(this.makeViolation(item.decl.file_path, item.decl.line, `Color drift in "${property}": "${item.decl.raw_value}" is a rare value ` +
|
|
248
|
+
`(used ${cluster.length} time${cluster.length === 1 ? '' : 's'}, ` +
|
|
249
|
+
`${(share * 100).toFixed(1)}% of ${colors.length} usages). ` +
|
|
250
|
+
`Dominant cluster has ${dominantSize} values. Consider using a design token.`, 'warning', 'styles/value-drift', 'color'));
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
return violations;
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Exact-value drift for non-color properties.
|
|
258
|
+
*/
|
|
259
|
+
detectExactValueDrift(property, decls, cfg) {
|
|
260
|
+
const violations = [];
|
|
261
|
+
// Build value histogram
|
|
262
|
+
const histogram = new Map();
|
|
263
|
+
for (const d of decls) {
|
|
264
|
+
const key = d.normalized_value ?? d.raw_value;
|
|
265
|
+
const list = histogram.get(key) || [];
|
|
266
|
+
list.push(d);
|
|
267
|
+
histogram.set(key, list);
|
|
268
|
+
}
|
|
269
|
+
// Find the mode (most frequent value)
|
|
270
|
+
let modeKey = '';
|
|
271
|
+
let modeCount = 0;
|
|
272
|
+
for (const [key, list] of histogram) {
|
|
273
|
+
if (list.length > modeCount) {
|
|
274
|
+
modeCount = list.length;
|
|
275
|
+
modeKey = key;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
if (modeCount < cfg.modeMinCount)
|
|
279
|
+
return violations;
|
|
280
|
+
// Flag low-share values
|
|
281
|
+
const total = decls.length;
|
|
282
|
+
for (const [key, list] of histogram) {
|
|
283
|
+
if (key === modeKey)
|
|
284
|
+
continue;
|
|
285
|
+
const share = list.length / total;
|
|
286
|
+
if (share < cfg.outlierMaxShare) {
|
|
287
|
+
const sample = list[0];
|
|
288
|
+
violations.push(this.makeViolation(sample.file_path, sample.line, `Value drift in "${property}": "${sample.raw_value}" is rare ` +
|
|
289
|
+
`(${list.length} of ${total} usages, ${(share * 100).toFixed(1)}%). ` +
|
|
290
|
+
`The dominant value "${modeKey}" is used ${modeCount} times. ` +
|
|
291
|
+
`Consider using a consistent value or design token.`, 'warning', 'styles/value-drift', 'exact'));
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return violations;
|
|
295
|
+
}
|
|
296
|
+
// -----------------------------------------------------------------------
|
|
297
|
+
// Detector 2: Off-Scale Values
|
|
298
|
+
// -----------------------------------------------------------------------
|
|
299
|
+
/**
|
|
300
|
+
* For scale-family properties (margin, padding, gap, font-size),
|
|
301
|
+
* infer the project scale from modal values + Tailwind defaults,
|
|
302
|
+
* and flag values that don't fit the scale.
|
|
303
|
+
*/
|
|
304
|
+
detectOffScaleValues(byProperty, cfg) {
|
|
305
|
+
const violations = [];
|
|
306
|
+
for (const property of cfg.scaleProperties) {
|
|
307
|
+
const decls = byProperty.get(property);
|
|
308
|
+
if (!decls || decls.length < cfg.minCorpus)
|
|
309
|
+
continue;
|
|
310
|
+
// Parse values to px-equivalent numbers
|
|
311
|
+
const parsed = [];
|
|
312
|
+
for (const d of decls) {
|
|
313
|
+
const px = this.parseLengthToPx(d.raw_value);
|
|
314
|
+
if (px !== null) {
|
|
315
|
+
parsed.push({ decl: d, px });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
if (parsed.length < cfg.minCorpus)
|
|
319
|
+
continue;
|
|
320
|
+
// Infer the project scale step
|
|
321
|
+
const step = this.inferScaleStep(parsed.map(p => p.px));
|
|
322
|
+
if (step === null || step === 0)
|
|
323
|
+
continue;
|
|
324
|
+
// Flag off-scale values
|
|
325
|
+
for (const { decl, px } of parsed) {
|
|
326
|
+
const remainder = px % step;
|
|
327
|
+
// Allow near-zero remainders (floating point tolerance: < 1px)
|
|
328
|
+
if (Math.abs(remainder) > 1 && Math.abs(remainder - step) > 1) {
|
|
329
|
+
violations.push(this.makeViolation(decl.file_path, decl.line, `Off-scale "${property}" value: "${decl.raw_value}" (${px}px) ` +
|
|
330
|
+
`does not align with the inferred ${step}px scale step. ` +
|
|
331
|
+
`Nearby scale values: ${Math.floor(px / step) * step}px or ` +
|
|
332
|
+
`${Math.ceil(px / step) * step}px.`, 'warning', 'styles/off-scale'));
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
return violations;
|
|
337
|
+
}
|
|
338
|
+
/**
|
|
339
|
+
* Infer the dominant scale step from a set of px values.
|
|
340
|
+
* Uses the Tailwind scale as candidate steps.
|
|
341
|
+
*/
|
|
342
|
+
inferScaleStep(values) {
|
|
343
|
+
if (values.length < 3)
|
|
344
|
+
return null;
|
|
345
|
+
// Count how many values align with each tailwind step
|
|
346
|
+
const candidates = [2, 4, 8, 16];
|
|
347
|
+
let bestStep = 4;
|
|
348
|
+
let bestScore = 0;
|
|
349
|
+
for (const step of candidates) {
|
|
350
|
+
let score = 0;
|
|
351
|
+
for (const v of values) {
|
|
352
|
+
if (v % step === 0)
|
|
353
|
+
score++;
|
|
354
|
+
}
|
|
355
|
+
if (score > bestScore) {
|
|
356
|
+
bestScore = score;
|
|
357
|
+
bestStep = step;
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
// Require at least 60% alignment
|
|
361
|
+
if (bestScore / values.length < 0.6)
|
|
362
|
+
return null;
|
|
363
|
+
return bestStep;
|
|
364
|
+
}
|
|
365
|
+
// -----------------------------------------------------------------------
|
|
366
|
+
// Detector 3: Undefined Classes
|
|
367
|
+
// -----------------------------------------------------------------------
|
|
368
|
+
/**
|
|
369
|
+
* Flag CSS classes used in markup that have no matching definition in
|
|
370
|
+
* any CSS/SCSS file. Files with unresolvable class usage are exempted.
|
|
371
|
+
*/
|
|
372
|
+
detectUndefinedClasses(classUsage, _declarations, byProperty) {
|
|
373
|
+
const violations = [];
|
|
374
|
+
// Collect all defined class names from CSS declarations
|
|
375
|
+
// (context field contains selectors like ".btn-primary")
|
|
376
|
+
const definedClasses = new Set();
|
|
377
|
+
for (const [, decls] of byProperty) {
|
|
378
|
+
for (const d of decls) {
|
|
379
|
+
const ctx = d.context;
|
|
380
|
+
if (!ctx)
|
|
381
|
+
continue;
|
|
382
|
+
// Extract class selectors from context
|
|
383
|
+
const matches = ctx.matchAll(/\.([a-zA-Z0-9_-]+)/g);
|
|
384
|
+
for (const m of matches) {
|
|
385
|
+
definedClasses.add(m[1]);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
// Also add common Tailwind utilities
|
|
390
|
+
const tailwindUtils = new Set([
|
|
391
|
+
'flex', 'grid', 'block', 'inline', 'hidden', 'relative', 'absolute',
|
|
392
|
+
'fixed', 'sticky', 'static', 'container', 'w-full', 'h-full',
|
|
393
|
+
'text-sm', 'text-base', 'text-lg', 'text-xl', 'font-bold', 'font-normal',
|
|
394
|
+
'bg-white', 'bg-black', 'rounded', 'rounded-lg', 'shadow', 'shadow-md',
|
|
395
|
+
'p-4', 'm-4', 'gap-4', 'border', 'cursor-pointer', 'hover',
|
|
396
|
+
'text-center', 'text-left', 'items-center', 'justify-between',
|
|
397
|
+
]);
|
|
398
|
+
// Track files with unresolvable class usage (dynamic classes)
|
|
399
|
+
const unresolvableFiles = new Set();
|
|
400
|
+
for (const u of classUsage) {
|
|
401
|
+
if (u.unresolvable) {
|
|
402
|
+
unresolvableFiles.add(u.file_path);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
// Check each class usage
|
|
406
|
+
const seen = new Set(); // deduplicate by (className, filePath)
|
|
407
|
+
for (const u of classUsage) {
|
|
408
|
+
const key = `${u.class_name}::${u.file_path}`;
|
|
409
|
+
if (seen.has(key))
|
|
410
|
+
continue;
|
|
411
|
+
seen.add(key);
|
|
412
|
+
// Skip unresolvable files
|
|
413
|
+
if (unresolvableFiles.has(u.file_path))
|
|
414
|
+
continue;
|
|
415
|
+
// Skip known classes
|
|
416
|
+
if (definedClasses.has(u.class_name))
|
|
417
|
+
continue;
|
|
418
|
+
if (tailwindUtils.has(u.class_name))
|
|
419
|
+
continue;
|
|
420
|
+
// Skip pseudo-class/variant selectors (hover:, focus:, sm:, etc.)
|
|
421
|
+
if (u.class_name.includes(':'))
|
|
422
|
+
continue;
|
|
423
|
+
// Skip common HTML attributes and dynamic-looking classes
|
|
424
|
+
if (/^[A-Z]/.test(u.class_name))
|
|
425
|
+
continue; // PascalCase — likely a component
|
|
426
|
+
if (u.class_name.includes('[') || u.class_name.includes(']'))
|
|
427
|
+
continue; // arbitrary values
|
|
428
|
+
if (u.class_name.includes('('))
|
|
429
|
+
continue; // function-like
|
|
430
|
+
if (/^\d/.test(u.class_name))
|
|
431
|
+
continue; // numeric
|
|
432
|
+
violations.push(this.makeViolation(u.file_path, u.line, `Undefined CSS class: "${u.class_name}" has no matching definition ` +
|
|
433
|
+
`in any stylesheet or Tailwind utility set.`, 'warning', 'styles/undefined-class'));
|
|
434
|
+
}
|
|
435
|
+
return violations;
|
|
436
|
+
}
|
|
437
|
+
// -----------------------------------------------------------------------
|
|
438
|
+
// Detector 4: Token Bypass
|
|
439
|
+
// -----------------------------------------------------------------------
|
|
440
|
+
/**
|
|
441
|
+
* Flag raw values that match a known design token's value but don't
|
|
442
|
+
* reference the token via tokenRef.
|
|
443
|
+
*/
|
|
444
|
+
detectTokenBypass(declarations, tokenValueMap, cfg) {
|
|
445
|
+
const violations = [];
|
|
446
|
+
if (tokenValueMap.size === 0)
|
|
447
|
+
return violations;
|
|
448
|
+
for (const d of declarations) {
|
|
449
|
+
// Skip if already referencing a token
|
|
450
|
+
if (d.token_ref)
|
|
451
|
+
continue;
|
|
452
|
+
// Normalize the raw value for comparison
|
|
453
|
+
const normalized = this.normalizeForTokenMatch(d.raw_value);
|
|
454
|
+
const tokenName = tokenValueMap.get(normalized);
|
|
455
|
+
if (!tokenName)
|
|
456
|
+
continue;
|
|
457
|
+
violations.push(this.makeViolation(d.file_path, d.line, `Token bypass: "${d.raw_value}" for "${d.property}" matches design ` +
|
|
458
|
+
`token "${tokenName}" but was used as a raw value. ` +
|
|
459
|
+
`Use the token reference instead to keep styles consistent.`, 'warning', 'styles/token-bypass'));
|
|
460
|
+
}
|
|
461
|
+
return violations;
|
|
462
|
+
}
|
|
463
|
+
/** Normalize a raw value for token-value comparison. */
|
|
464
|
+
normalizeForTokenMatch(raw) {
|
|
465
|
+
let v = raw.toLowerCase().trim();
|
|
466
|
+
// Remove spaces after commas in functional notation
|
|
467
|
+
v = v.replace(/,\s+/g, ',');
|
|
468
|
+
// Expand shorthand hex: #fff → #ffffff
|
|
469
|
+
if (/^#[0-9a-f]{3}$/.test(v)) {
|
|
470
|
+
v = '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3];
|
|
471
|
+
}
|
|
472
|
+
return v;
|
|
473
|
+
}
|
|
474
|
+
// -----------------------------------------------------------------------
|
|
475
|
+
// Detector 5: Mechanism Fragmentation
|
|
476
|
+
// -----------------------------------------------------------------------
|
|
477
|
+
/**
|
|
478
|
+
* Flag when the same (property, value) is applied via ≥3 different
|
|
479
|
+
* mechanisms across the codebase, or when a single file/component
|
|
480
|
+
* mixes ≥3 different mechanisms.
|
|
481
|
+
*/
|
|
482
|
+
detectMechanismFragmentation(declarations, cfg) {
|
|
483
|
+
const violations = [];
|
|
484
|
+
// (A) Cross-file: same (property, raw_value) via ≥3 mechanisms
|
|
485
|
+
const pvMap = new Map(); // "property::value" → set of mechanisms
|
|
486
|
+
const pvSample = new Map(); // keep one sample for reporting
|
|
487
|
+
for (const d of declarations) {
|
|
488
|
+
const key = `${d.property}::${d.normalized_value ?? d.raw_value}`;
|
|
489
|
+
const mechs = pvMap.get(key) || new Set();
|
|
490
|
+
mechs.add(d.mechanism);
|
|
491
|
+
pvMap.set(key, mechs);
|
|
492
|
+
if (!pvSample.has(key)) {
|
|
493
|
+
pvSample.set(key, d);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
for (const [key, mechs] of pvMap) {
|
|
497
|
+
if (mechs.size < cfg.mechanismFragmentationMinMechanisms)
|
|
498
|
+
continue;
|
|
499
|
+
const sample = pvSample.get(key);
|
|
500
|
+
const [prop, value] = key.split('::');
|
|
501
|
+
violations.push(this.makeViolation(sample.file_path, sample.line, `Mechanism fragmentation: "${prop}: ${value}" is applied via ` +
|
|
502
|
+
`${mechs.size} different mechanisms (${[...mechs].sort().join(', ')}). ` +
|
|
503
|
+
`Consolidate to a single mechanism or design token.`, 'warning', 'styles/mechanism-fragmentation'));
|
|
504
|
+
}
|
|
505
|
+
// (B) Per-file: a single file mixing ≥3 mechanisms
|
|
506
|
+
const fileMechs = new Map();
|
|
507
|
+
for (const d of declarations) {
|
|
508
|
+
const mechs = fileMechs.get(d.file_path) || new Set();
|
|
509
|
+
mechs.add(d.mechanism);
|
|
510
|
+
fileMechs.set(d.file_path, mechs);
|
|
511
|
+
}
|
|
512
|
+
for (const [file, mechs] of fileMechs) {
|
|
513
|
+
if (mechs.size < cfg.mechanismFragmentationMinMechanisms)
|
|
514
|
+
continue;
|
|
515
|
+
violations.push(this.makeViolation(file, 1, `Mechanism mixing: ${file} uses ${mechs.size} different style ` +
|
|
516
|
+
`mechanisms (${[...mechs].sort().join(', ')}). ` +
|
|
517
|
+
`Consolidate to fewer mechanisms for maintainability.`, 'suggestion', 'styles/mechanism-mixing'));
|
|
518
|
+
}
|
|
519
|
+
return violations;
|
|
520
|
+
}
|
|
521
|
+
// -----------------------------------------------------------------------
|
|
522
|
+
// Detector 6: Declaration-Set Similarity
|
|
523
|
+
// -----------------------------------------------------------------------
|
|
524
|
+
/**
|
|
525
|
+
* Detect two rule blocks (contexts) whose declaration sets overlap at
|
|
526
|
+
* ≥ similarityThreshold (default 0.9) and that each have ≥ minDeclarations.
|
|
527
|
+
* This catches near-duplicate CSS rules that share most declarations.
|
|
528
|
+
*/
|
|
529
|
+
detectDeclarationSetSimilarity(declarations, cfg) {
|
|
530
|
+
const violations = [];
|
|
531
|
+
// Group declarations by (file, context) to form rule blocks
|
|
532
|
+
const blocks = new Map();
|
|
533
|
+
for (const d of declarations) {
|
|
534
|
+
if (!d.context)
|
|
535
|
+
continue;
|
|
536
|
+
const key = `${d.file_path}::${d.context}`;
|
|
537
|
+
const list = blocks.get(key) || [];
|
|
538
|
+
list.push(d);
|
|
539
|
+
blocks.set(key, list);
|
|
540
|
+
}
|
|
541
|
+
// Convert blocks to declaration-value sets
|
|
542
|
+
const blockEntries = [...blocks.entries()]
|
|
543
|
+
.map(([key, decls]) => {
|
|
544
|
+
const valueSet = new Set(decls.map(d => `${d.property}:${d.normalized_value ?? d.raw_value}`));
|
|
545
|
+
return {
|
|
546
|
+
key,
|
|
547
|
+
filePath: decls[0].file_path,
|
|
548
|
+
context: decls[0].context,
|
|
549
|
+
line: decls[0].line,
|
|
550
|
+
declCount: decls.length,
|
|
551
|
+
valueSet,
|
|
552
|
+
};
|
|
553
|
+
})
|
|
554
|
+
.filter(b => b.declCount >= cfg.declarationSetMinDeclarations);
|
|
555
|
+
if (blockEntries.length < 2)
|
|
556
|
+
return violations;
|
|
557
|
+
// Compare pairs
|
|
558
|
+
const reported = new Set(); // "keyA::keyB"
|
|
559
|
+
for (let i = 0; i < blockEntries.length; i++) {
|
|
560
|
+
for (let j = i + 1; j < blockEntries.length; j++) {
|
|
561
|
+
const a = blockEntries[i];
|
|
562
|
+
const b = blockEntries[j];
|
|
563
|
+
// Skip same-file same-context (already the same block) or same key
|
|
564
|
+
if (a.key === b.key)
|
|
565
|
+
continue;
|
|
566
|
+
const pairKey = [a.key, b.key].sort().join('::');
|
|
567
|
+
if (reported.has(pairKey))
|
|
568
|
+
continue;
|
|
569
|
+
reported.add(pairKey);
|
|
570
|
+
// Compute Jaccard similarity
|
|
571
|
+
const intersection = new Set([...a.valueSet].filter(x => b.valueSet.has(x)));
|
|
572
|
+
const union = new Set([...a.valueSet, ...b.valueSet]);
|
|
573
|
+
const similarity = intersection.size / union.size;
|
|
574
|
+
if (similarity >= cfg.declarationSetSimilarityThreshold) {
|
|
575
|
+
violations.push(this.makeViolation(a.filePath, a.line, `Declaration-set similarity: "${a.context}" and "${b.context}" ` +
|
|
576
|
+
`in ${b.filePath} share ${intersection.size} of ${union.size} ` +
|
|
577
|
+
`declarations (${(similarity * 100).toFixed(0)}%). ` +
|
|
578
|
+
`Consider consolidating these rules or extracting a shared mixin.`, 'suggestion', 'styles/declaration-set-similarity'));
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
return violations;
|
|
583
|
+
}
|
|
584
|
+
// -----------------------------------------------------------------------
|
|
585
|
+
// Detector 7: Z-Index Inventory
|
|
586
|
+
// -----------------------------------------------------------------------
|
|
587
|
+
/**
|
|
588
|
+
* Z-index sprawl: flag when there are too many distinct z-index values,
|
|
589
|
+
* suggesting a lack of a z-index scale/system.
|
|
590
|
+
*/
|
|
591
|
+
detectZIndexInventory(byProperty, cfg) {
|
|
592
|
+
const violations = [];
|
|
593
|
+
const decls = byProperty.get('z-index');
|
|
594
|
+
if (!decls || decls.length === 0)
|
|
595
|
+
return violations;
|
|
596
|
+
// Collect distinct numeric z-index values
|
|
597
|
+
const values = new Map();
|
|
598
|
+
for (const d of decls) {
|
|
599
|
+
const num = parseInt(d.raw_value, 10);
|
|
600
|
+
if (isNaN(num))
|
|
601
|
+
continue;
|
|
602
|
+
const list = values.get(num) || [];
|
|
603
|
+
list.push(d);
|
|
604
|
+
values.set(num, list);
|
|
605
|
+
}
|
|
606
|
+
if (values.size === 0)
|
|
607
|
+
return violations;
|
|
608
|
+
// Flag if too many distinct values
|
|
609
|
+
if (values.size > cfg.zIndexMaxDistinct) {
|
|
610
|
+
const sortedVals = [...values.keys()].sort((a, b) => a - b);
|
|
611
|
+
const sample = values.get(sortedVals[0])[0];
|
|
612
|
+
violations.push(this.makeViolation(sample.file_path, sample.line, `Z-index sprawl: ${values.size} distinct z-index values ` +
|
|
613
|
+
`(${sortedVals.join(', ')}). Consider defining a z-index scale ` +
|
|
614
|
+
`(e.g., $z-layers: (dropdown: 100, modal: 200, toast: 300)).`, 'warning', 'styles/z-index-sprawl'));
|
|
615
|
+
}
|
|
616
|
+
// Flag singletons (z-index values used only once)
|
|
617
|
+
for (const [val, list] of values) {
|
|
618
|
+
if (list.length === 1 && values.size > 2) {
|
|
619
|
+
const d = list[0];
|
|
620
|
+
violations.push(this.makeViolation(d.file_path, d.line, `Singleton z-index: z-index: ${val} is used only once. ` +
|
|
621
|
+
`Consider whether this value belongs in a shared z-index scale.`, 'suggestion', 'styles/z-index-singleton'));
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return violations;
|
|
625
|
+
}
|
|
626
|
+
// -----------------------------------------------------------------------
|
|
627
|
+
// Helpers
|
|
628
|
+
// -----------------------------------------------------------------------
|
|
629
|
+
makeViolation(filePath, line, message, severity, rule, symbol) {
|
|
630
|
+
const v = {
|
|
631
|
+
file: filePath,
|
|
632
|
+
line,
|
|
633
|
+
column: 1,
|
|
634
|
+
severity,
|
|
635
|
+
message,
|
|
636
|
+
rule,
|
|
637
|
+
analyzer: this.name,
|
|
638
|
+
};
|
|
639
|
+
if (symbol) {
|
|
640
|
+
v.functionName = symbol;
|
|
641
|
+
}
|
|
642
|
+
return v;
|
|
643
|
+
}
|
|
644
|
+
/** Parse a CSS color string to [R, G, B] or null. */
|
|
645
|
+
parseColorToRGB(raw) {
|
|
646
|
+
try {
|
|
647
|
+
let v = raw.toLowerCase().trim();
|
|
648
|
+
// Hex
|
|
649
|
+
if (v.startsWith('#')) {
|
|
650
|
+
if (v.length === 4) {
|
|
651
|
+
// #rgb → #rrggbb
|
|
652
|
+
v = '#' + v[1] + v[1] + v[2] + v[2] + v[3] + v[3];
|
|
653
|
+
}
|
|
654
|
+
if (v.length === 7) {
|
|
655
|
+
return [
|
|
656
|
+
parseInt(v.slice(1, 3), 16),
|
|
657
|
+
parseInt(v.slice(3, 5), 16),
|
|
658
|
+
parseInt(v.slice(5, 7), 16),
|
|
659
|
+
];
|
|
660
|
+
}
|
|
661
|
+
if (v.length === 9) {
|
|
662
|
+
return [
|
|
663
|
+
parseInt(v.slice(1, 3), 16),
|
|
664
|
+
parseInt(v.slice(3, 5), 16),
|
|
665
|
+
parseInt(v.slice(5, 7), 16),
|
|
666
|
+
];
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
// rgb(r, g, b) or rgb(r g b)
|
|
670
|
+
const rgbMatch = v.match(/rgb\(\s*(\d+)\s*,?\s*(\d+)\s*,?\s*(\d+)\s*\)/);
|
|
671
|
+
if (rgbMatch) {
|
|
672
|
+
return [
|
|
673
|
+
parseInt(rgbMatch[1]),
|
|
674
|
+
parseInt(rgbMatch[2]),
|
|
675
|
+
parseInt(rgbMatch[3]),
|
|
676
|
+
];
|
|
677
|
+
}
|
|
678
|
+
// Named colors — minimal set for common use
|
|
679
|
+
const named = {
|
|
680
|
+
'white': [255, 255, 255], 'black': [0, 0, 0],
|
|
681
|
+
'red': [255, 0, 0], 'blue': [0, 0, 255], 'green': [0, 128, 0],
|
|
682
|
+
'transparent': [0, 0, 0],
|
|
683
|
+
};
|
|
684
|
+
if (named[v])
|
|
685
|
+
return named[v];
|
|
686
|
+
return null;
|
|
687
|
+
}
|
|
688
|
+
catch {
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
/** Compute delta-E (CIE76) between two RGB colors. */
|
|
693
|
+
deltaE(a, b) {
|
|
694
|
+
const dr = a[0] - b[0];
|
|
695
|
+
const dg = a[1] - b[1];
|
|
696
|
+
const db = a[2] - b[2];
|
|
697
|
+
return Math.sqrt(dr * dr + dg * dg + db * db);
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Cluster colors by delta-E distance.
|
|
701
|
+
* Simple greedy algorithm: each item joins the first cluster it's close enough to,
|
|
702
|
+
* or starts a new cluster.
|
|
703
|
+
*/
|
|
704
|
+
clusterByDeltaE(items, threshold) {
|
|
705
|
+
const clusters = [];
|
|
706
|
+
for (const item of items) {
|
|
707
|
+
let placed = false;
|
|
708
|
+
for (const cluster of clusters) {
|
|
709
|
+
// Use the first item's RGB as cluster centroid
|
|
710
|
+
const centroid = cluster[0].rgb;
|
|
711
|
+
if (this.deltaE(item.rgb, centroid) < threshold) {
|
|
712
|
+
cluster.push(item);
|
|
713
|
+
placed = true;
|
|
714
|
+
break;
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
if (!placed) {
|
|
718
|
+
clusters.push([item]);
|
|
719
|
+
}
|
|
720
|
+
}
|
|
721
|
+
return clusters;
|
|
722
|
+
}
|
|
723
|
+
/** Parse a CSS length value to px-equivalent, or null if not parseable. */
|
|
724
|
+
parseLengthToPx(raw) {
|
|
725
|
+
try {
|
|
726
|
+
const v = raw.trim().toLowerCase();
|
|
727
|
+
if (v === '0' || v === '0px')
|
|
728
|
+
return 0;
|
|
729
|
+
const match = v.match(/^(-?\d+(?:\.\d+)?)\s*(px|rem|em|%|vh|vw|pt|cm|mm)?$/);
|
|
730
|
+
if (!match)
|
|
731
|
+
return null;
|
|
732
|
+
const num = parseFloat(match[1]);
|
|
733
|
+
const unit = match[2] || 'px';
|
|
734
|
+
// Approximate conversions (assuming 16px base for rem/em)
|
|
735
|
+
switch (unit) {
|
|
736
|
+
case 'px': return num;
|
|
737
|
+
case 'rem': return num * 16;
|
|
738
|
+
case 'em': return num * 16;
|
|
739
|
+
case 'pt': return num * 1.333;
|
|
740
|
+
case 'cm': return num * 37.795;
|
|
741
|
+
case 'mm': return num * 3.7795;
|
|
742
|
+
default: return null; // can't convert %/vh/vw without context
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
catch {
|
|
746
|
+
return null;
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
//# sourceMappingURL=UniversalStylesAnalyzer.js.map
|