@lyse-labs/lyse 0.1.0-alpha.2
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/LICENSE +45 -0
- package/PRIVACY.md +181 -0
- package/README.md +34 -0
- package/SECURITY.md +43 -0
- package/dist/bench/evidence-pack/anti-dummy.d.ts +11 -0
- package/dist/bench/evidence-pack/anti-dummy.js +20 -0
- package/dist/bench/evidence-pack/anti-dummy.js.map +1 -0
- package/dist/bench/evidence-pack/builder.d.ts +14 -0
- package/dist/bench/evidence-pack/builder.js +77 -0
- package/dist/bench/evidence-pack/builder.js.map +1 -0
- package/dist/bench/evidence-pack/histograms.d.ts +2 -0
- package/dist/bench/evidence-pack/histograms.js +131 -0
- package/dist/bench/evidence-pack/histograms.js.map +1 -0
- package/dist/bench/evidence-pack/manifest-detector.d.ts +2 -0
- package/dist/bench/evidence-pack/manifest-detector.js +65 -0
- package/dist/bench/evidence-pack/manifest-detector.js.map +1 -0
- package/dist/bench/evidence-pack/package-json-digest.d.ts +2 -0
- package/dist/bench/evidence-pack/package-json-digest.js +51 -0
- package/dist/bench/evidence-pack/package-json-digest.js.map +1 -0
- package/dist/bench/evidence-pack/sampler.d.ts +2 -0
- package/dist/bench/evidence-pack/sampler.js +140 -0
- package/dist/bench/evidence-pack/sampler.js.map +1 -0
- package/dist/bench/evidence-pack/types.d.ts +127 -0
- package/dist/bench/evidence-pack/types.js +2 -0
- package/dist/bench/evidence-pack/types.js.map +1 -0
- package/dist/bench/evidence-pack/verifier-corpus.d.ts +5 -0
- package/dist/bench/evidence-pack/verifier-corpus.js +13 -0
- package/dist/bench/evidence-pack/verifier-corpus.js.map +1 -0
- package/dist/bench/taxonomy-loader.d.ts +20 -0
- package/dist/bench/taxonomy-loader.js +28 -0
- package/dist/bench/taxonomy-loader.js.map +1 -0
- package/dist/bench/taxonomy.v3.json +20 -0
- package/dist/cli/__tests__/version-migration.test.d.ts +1 -0
- package/dist/cli/__tests__/version-migration.test.js +69 -0
- package/dist/cli/__tests__/version-migration.test.js.map +1 -0
- package/dist/cli/output/__tests__/eslint-style.test.d.ts +1 -0
- package/dist/cli/output/__tests__/eslint-style.test.js +205 -0
- package/dist/cli/output/__tests__/eslint-style.test.js.map +1 -0
- package/dist/cli/output/__tests__/limit.test.d.ts +1 -0
- package/dist/cli/output/__tests__/limit.test.js +65 -0
- package/dist/cli/output/__tests__/limit.test.js.map +1 -0
- package/dist/cli/output/eslint-style.d.ts +18 -0
- package/dist/cli/output/eslint-style.js +42 -0
- package/dist/cli/output/eslint-style.js.map +1 -0
- package/dist/cli/output/limit.d.ts +5 -0
- package/dist/cli/output/limit.js +32 -0
- package/dist/cli/output/limit.js.map +1 -0
- package/dist/cli/output/score-gauge.d.ts +4 -0
- package/dist/cli/output/score-gauge.js +15 -0
- package/dist/cli/output/score-gauge.js.map +1 -0
- package/dist/cli/version-migration.d.ts +18 -0
- package/dist/cli/version-migration.js +49 -0
- package/dist/cli/version-migration.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +856 -0
- package/dist/cli.js.map +1 -0
- package/dist/codemods/diff.d.ts +17 -0
- package/dist/codemods/diff.js +77 -0
- package/dist/codemods/diff.js.map +1 -0
- package/dist/codemods/git-helpers.d.ts +10 -0
- package/dist/codemods/git-helpers.js +75 -0
- package/dist/codemods/git-helpers.js.map +1 -0
- package/dist/codemods/index.d.ts +27 -0
- package/dist/codemods/index.js +40 -0
- package/dist/codemods/index.js.map +1 -0
- package/dist/codemods/naming-component-pascalcase.d.ts +13 -0
- package/dist/codemods/naming-component-pascalcase.js +106 -0
- package/dist/codemods/naming-component-pascalcase.js.map +1 -0
- package/dist/codemods/naming-hook-prefix.d.ts +13 -0
- package/dist/codemods/naming-hook-prefix.js +86 -0
- package/dist/codemods/naming-hook-prefix.js.map +1 -0
- package/dist/codemods/safety.d.ts +50 -0
- package/dist/codemods/safety.js +109 -0
- package/dist/codemods/safety.js.map +1 -0
- package/dist/codemods/safety.test.d.ts +1 -0
- package/dist/codemods/safety.test.js +154 -0
- package/dist/codemods/safety.test.js.map +1 -0
- package/dist/codemods/shadow-native.d.ts +2 -0
- package/dist/codemods/shadow-native.js +103 -0
- package/dist/codemods/shadow-native.js.map +1 -0
- package/dist/codemods/tokens-color.d.ts +2 -0
- package/dist/codemods/tokens-color.js +69 -0
- package/dist/codemods/tokens-color.js.map +1 -0
- package/dist/codemods/tokens-spacing.d.ts +2 -0
- package/dist/codemods/tokens-spacing.js +66 -0
- package/dist/codemods/tokens-spacing.js.map +1 -0
- package/dist/commands/__tests__/email-prompt.test.d.ts +1 -0
- package/dist/commands/__tests__/email-prompt.test.js +126 -0
- package/dist/commands/__tests__/email-prompt.test.js.map +1 -0
- package/dist/commands/__tests__/explain-score.test.d.ts +1 -0
- package/dist/commands/__tests__/explain-score.test.js +88 -0
- package/dist/commands/__tests__/explain-score.test.js.map +1 -0
- package/dist/commands/__tests__/feedback.test.d.ts +1 -0
- package/dist/commands/__tests__/feedback.test.js +186 -0
- package/dist/commands/__tests__/feedback.test.js.map +1 -0
- package/dist/commands/audit-flags.d.ts +49 -0
- package/dist/commands/audit-flags.js +17 -0
- package/dist/commands/audit-flags.js.map +1 -0
- package/dist/commands/audit-pipeline.d.ts +31 -0
- package/dist/commands/audit-pipeline.js +342 -0
- package/dist/commands/audit-pipeline.js.map +1 -0
- package/dist/commands/bench-pack.d.ts +5 -0
- package/dist/commands/bench-pack.js +61 -0
- package/dist/commands/bench-pack.js.map +1 -0
- package/dist/commands/ci-setup.d.ts +9 -0
- package/dist/commands/ci-setup.js +42 -0
- package/dist/commands/ci-setup.js.map +1 -0
- package/dist/commands/email-prompt.d.ts +36 -0
- package/dist/commands/email-prompt.js +160 -0
- package/dist/commands/email-prompt.js.map +1 -0
- package/dist/commands/explain-score.d.ts +35 -0
- package/dist/commands/explain-score.js +137 -0
- package/dist/commands/explain-score.js.map +1 -0
- package/dist/commands/explain.d.ts +6 -0
- package/dist/commands/explain.js +93 -0
- package/dist/commands/explain.js.map +1 -0
- package/dist/commands/feedback.d.ts +31 -0
- package/dist/commands/feedback.js +155 -0
- package/dist/commands/feedback.js.map +1 -0
- package/dist/commands/fix.d.ts +48 -0
- package/dist/commands/fix.js +191 -0
- package/dist/commands/fix.js.map +1 -0
- package/dist/commands/init-detect.d.ts +20 -0
- package/dist/commands/init-detect.js +124 -0
- package/dist/commands/init-detect.js.map +1 -0
- package/dist/commands/init-write-agents-md.d.ts +14 -0
- package/dist/commands/init-write-agents-md.js +71 -0
- package/dist/commands/init-write-agents-md.js.map +1 -0
- package/dist/commands/init-write-lyse-md.d.ts +14 -0
- package/dist/commands/init-write-lyse-md.js +253 -0
- package/dist/commands/init-write-lyse-md.js.map +1 -0
- package/dist/commands/init.d.ts +8 -0
- package/dist/commands/init.js +147 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/mcp-entry.d.ts +33 -0
- package/dist/commands/mcp-entry.js +47 -0
- package/dist/commands/mcp-entry.js.map +1 -0
- package/dist/commands/mcp-setup.d.ts +10 -0
- package/dist/commands/mcp-setup.js +74 -0
- package/dist/commands/mcp-setup.js.map +1 -0
- package/dist/commands/share.d.ts +4 -0
- package/dist/commands/share.js +60 -0
- package/dist/commands/share.js.map +1 -0
- package/dist/commands/telemetry.d.ts +4 -0
- package/dist/commands/telemetry.js +27 -0
- package/dist/commands/telemetry.js.map +1 -0
- package/dist/commands/templates/lyse-workflow.yml.template +30 -0
- package/dist/config/schema.d.ts +158 -0
- package/dist/config/schema.js +136 -0
- package/dist/config/schema.js.map +1 -0
- package/dist/credentials/keychain.d.ts +20 -0
- package/dist/credentials/keychain.js +35 -0
- package/dist/credentials/keychain.js.map +1 -0
- package/dist/credentials/keychain.test.d.ts +1 -0
- package/dist/credentials/keychain.test.js +32 -0
- package/dist/credentials/keychain.test.js.map +1 -0
- package/dist/credentials/paths.d.ts +1 -0
- package/dist/credentials/paths.js +6 -0
- package/dist/credentials/paths.js.map +1 -0
- package/dist/credentials/store.d.ts +17 -0
- package/dist/credentials/store.js +60 -0
- package/dist/credentials/store.js.map +1 -0
- package/dist/credentials/store.test.d.ts +1 -0
- package/dist/credentials/store.test.js +48 -0
- package/dist/credentials/store.test.js.map +1 -0
- package/dist/detection/from-filesystem.d.ts +2 -0
- package/dist/detection/from-filesystem.js +26 -0
- package/dist/detection/from-filesystem.js.map +1 -0
- package/dist/detection/from-git.d.ts +2 -0
- package/dist/detection/from-git.js +53 -0
- package/dist/detection/from-git.js.map +1 -0
- package/dist/detection/from-package-json.d.ts +2 -0
- package/dist/detection/from-package-json.js +194 -0
- package/dist/detection/from-package-json.js.map +1 -0
- package/dist/detection/pre-flight.d.ts +5 -0
- package/dist/detection/pre-flight.js +47 -0
- package/dist/detection/pre-flight.js.map +1 -0
- package/dist/detection/types.d.ts +27 -0
- package/dist/detection/types.js +2 -0
- package/dist/detection/types.js.map +1 -0
- package/dist/entitlement/check.d.ts +7 -0
- package/dist/entitlement/check.js +37 -0
- package/dist/entitlement/check.js.map +1 -0
- package/dist/entitlement/index.d.ts +1 -0
- package/dist/entitlement/index.js +2 -0
- package/dist/entitlement/index.js.map +1 -0
- package/dist/entitlement/keys.d.ts +10 -0
- package/dist/entitlement/keys.js +14 -0
- package/dist/entitlement/keys.js.map +1 -0
- package/dist/history/ndjson-store.d.ts +70 -0
- package/dist/history/ndjson-store.js +102 -0
- package/dist/history/ndjson-store.js.map +1 -0
- package/dist/identity/index.d.ts +1 -0
- package/dist/identity/index.js +2 -0
- package/dist/identity/index.js.map +1 -0
- package/dist/identity/repo-bucket.d.ts +22 -0
- package/dist/identity/repo-bucket.js +78 -0
- package/dist/identity/repo-bucket.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/llm/layer4-stage.d.ts +18 -0
- package/dist/llm/layer4-stage.js +12 -0
- package/dist/llm/layer4-stage.js.map +1 -0
- package/dist/loaders/components.d.ts +26 -0
- package/dist/loaders/components.js +285 -0
- package/dist/loaders/components.js.map +1 -0
- package/dist/loaders/stories.d.ts +2 -0
- package/dist/loaders/stories.js +231 -0
- package/dist/loaders/stories.js.map +1 -0
- package/dist/loaders/token-graph.d.ts +39 -0
- package/dist/loaders/token-graph.js +146 -0
- package/dist/loaders/token-graph.js.map +1 -0
- package/dist/loaders/tokens.d.ts +2 -0
- package/dist/loaders/tokens.js +521 -0
- package/dist/loaders/tokens.js.map +1 -0
- package/dist/mcp/_find-root.d.ts +12 -0
- package/dist/mcp/_find-root.js +28 -0
- package/dist/mcp/_find-root.js.map +1 -0
- package/dist/mcp/server.d.ts +1 -0
- package/dist/mcp/server.js +42 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/audit-file.d.ts +25 -0
- package/dist/mcp/tools/audit-file.js +147 -0
- package/dist/mcp/tools/audit-file.js.map +1 -0
- package/dist/mcp/tools/suggest-fix.d.ts +13 -0
- package/dist/mcp/tools/suggest-fix.js +100 -0
- package/dist/mcp/tools/suggest-fix.js.map +1 -0
- package/dist/menu/action-menu.d.ts +6 -0
- package/dist/menu/action-menu.js +15 -0
- package/dist/menu/action-menu.js.map +1 -0
- package/dist/menu/prompts.d.ts +7 -0
- package/dist/menu/prompts.js +30 -0
- package/dist/menu/prompts.js.map +1 -0
- package/dist/menu/repl.d.ts +17 -0
- package/dist/menu/repl.js +77 -0
- package/dist/menu/repl.js.map +1 -0
- package/dist/parsers/css-in-js.d.ts +2 -0
- package/dist/parsers/css-in-js.js +69 -0
- package/dist/parsers/css-in-js.js.map +1 -0
- package/dist/parsers/css.d.ts +2 -0
- package/dist/parsers/css.js +32 -0
- package/dist/parsers/css.js.map +1 -0
- package/dist/parsers/scss-transform.d.ts +25 -0
- package/dist/parsers/scss-transform.js +55 -0
- package/dist/parsers/scss-transform.js.map +1 -0
- package/dist/parsers/tailwind-v4.d.ts +8 -0
- package/dist/parsers/tailwind-v4.js +90 -0
- package/dist/parsers/tailwind-v4.js.map +1 -0
- package/dist/parsers/ts-morph-project.d.ts +16 -0
- package/dist/parsers/ts-morph-project.js +77 -0
- package/dist/parsers/ts-morph-project.js.map +1 -0
- package/dist/parsers/ts.d.ts +2 -0
- package/dist/parsers/ts.js +61 -0
- package/dist/parsers/ts.js.map +1 -0
- package/dist/reliability/__tests__/types.test.d.ts +1 -0
- package/dist/reliability/__tests__/types.test.js +14 -0
- package/dist/reliability/__tests__/types.test.js.map +1 -0
- package/dist/reliability/catalogue/__tests__/promotion.test.d.ts +1 -0
- package/dist/reliability/catalogue/__tests__/promotion.test.js +23 -0
- package/dist/reliability/catalogue/__tests__/promotion.test.js.map +1 -0
- package/dist/reliability/catalogue/__tests__/sub-axes.test.d.ts +1 -0
- package/dist/reliability/catalogue/__tests__/sub-axes.test.js +27 -0
- package/dist/reliability/catalogue/__tests__/sub-axes.test.js.map +1 -0
- package/dist/reliability/catalogue/promotion.d.ts +8 -0
- package/dist/reliability/catalogue/promotion.js +25 -0
- package/dist/reliability/catalogue/promotion.js.map +1 -0
- package/dist/reliability/catalogue/sub-axes.d.ts +3 -0
- package/dist/reliability/catalogue/sub-axes.js +18 -0
- package/dist/reliability/catalogue/sub-axes.js.map +1 -0
- package/dist/reliability/confidence/__tests__/manifest-loader.test.d.ts +1 -0
- package/dist/reliability/confidence/__tests__/manifest-loader.test.js +103 -0
- package/dist/reliability/confidence/__tests__/manifest-loader.test.js.map +1 -0
- package/dist/reliability/confidence/bundled-manifest.d.ts +2 -0
- package/dist/reliability/confidence/bundled-manifest.js +8 -0
- package/dist/reliability/confidence/bundled-manifest.js.map +1 -0
- package/dist/reliability/confidence/index.d.ts +3 -0
- package/dist/reliability/confidence/index.js +3 -0
- package/dist/reliability/confidence/index.js.map +1 -0
- package/dist/reliability/confidence/manifest-loader.d.ts +8 -0
- package/dist/reliability/confidence/manifest-loader.js +53 -0
- package/dist/reliability/confidence/manifest-loader.js.map +1 -0
- package/dist/reliability/feedback/__tests__/interactive.test.d.ts +1 -0
- package/dist/reliability/feedback/__tests__/interactive.test.js +85 -0
- package/dist/reliability/feedback/__tests__/interactive.test.js.map +1 -0
- package/dist/reliability/feedback/__tests__/repo-bucket.test.d.ts +1 -0
- package/dist/reliability/feedback/__tests__/repo-bucket.test.js +18 -0
- package/dist/reliability/feedback/__tests__/repo-bucket.test.js.map +1 -0
- package/dist/reliability/feedback/__tests__/sender.test.d.ts +1 -0
- package/dist/reliability/feedback/__tests__/sender.test.js +101 -0
- package/dist/reliability/feedback/__tests__/sender.test.js.map +1 -0
- package/dist/reliability/feedback/interactive.d.ts +20 -0
- package/dist/reliability/feedback/interactive.js +150 -0
- package/dist/reliability/feedback/interactive.js.map +1 -0
- package/dist/reliability/feedback/repo-bucket.d.ts +2 -0
- package/dist/reliability/feedback/repo-bucket.js +9 -0
- package/dist/reliability/feedback/repo-bucket.js.map +1 -0
- package/dist/reliability/feedback/sender.d.ts +16 -0
- package/dist/reliability/feedback/sender.js +28 -0
- package/dist/reliability/feedback/sender.js.map +1 -0
- package/dist/reliability/index.d.ts +1 -0
- package/dist/reliability/index.js +2 -0
- package/dist/reliability/index.js.map +1 -0
- package/dist/reliability/llm-eval/__tests__/budget.test.d.ts +1 -0
- package/dist/reliability/llm-eval/__tests__/budget.test.js +39 -0
- package/dist/reliability/llm-eval/__tests__/budget.test.js.map +1 -0
- package/dist/reliability/llm-eval/budget.d.ts +14 -0
- package/dist/reliability/llm-eval/budget.js +43 -0
- package/dist/reliability/llm-eval/budget.js.map +1 -0
- package/dist/reliability/score/__tests__/formula-v1.test.d.ts +1 -0
- package/dist/reliability/score/__tests__/formula-v1.test.js +29 -0
- package/dist/reliability/score/__tests__/formula-v1.test.js.map +1 -0
- package/dist/reliability/score/formula-v1.d.ts +13 -0
- package/dist/reliability/score/formula-v1.js +19 -0
- package/dist/reliability/score/formula-v1.js.map +1 -0
- package/dist/reliability/score/version-pin.d.ts +1 -0
- package/dist/reliability/score/version-pin.js +2 -0
- package/dist/reliability/score/version-pin.js.map +1 -0
- package/dist/reliability/score/weight.d.ts +1 -0
- package/dist/reliability/score/weight.js +5 -0
- package/dist/reliability/score/weight.js.map +1 -0
- package/dist/reliability/types.d.ts +39 -0
- package/dist/reliability/types.js +2 -0
- package/dist/reliability/types.js.map +1 -0
- package/dist/reporters/coverage-footer.d.ts +2 -0
- package/dist/reporters/coverage-footer.js +7 -0
- package/dist/reporters/coverage-footer.js.map +1 -0
- package/dist/reporters/json.d.ts +5 -0
- package/dist/reporters/json.js +51 -0
- package/dist/reporters/json.js.map +1 -0
- package/dist/reporters/markdown.d.ts +7 -0
- package/dist/reporters/markdown.js +35 -0
- package/dist/reporters/markdown.js.map +1 -0
- package/dist/reporters/sarif.d.ts +5 -0
- package/dist/reporters/sarif.js +109 -0
- package/dist/reporters/sarif.js.map +1 -0
- package/dist/reporters/terminal-format.d.ts +34 -0
- package/dist/reporters/terminal-format.js +97 -0
- package/dist/reporters/terminal-format.js.map +1 -0
- package/dist/reporters/terminal.d.ts +4 -0
- package/dist/reporters/terminal.js +201 -0
- package/dist/reporters/terminal.js.map +1 -0
- package/dist/rule-runner.d.ts +8 -0
- package/dist/rule-runner.js +22 -0
- package/dist/rule-runner.js.map +1 -0
- package/dist/rules/_codemod-adapter.d.ts +16 -0
- package/dist/rules/_codemod-adapter.js +49 -0
- package/dist/rules/_codemod-adapter.js.map +1 -0
- package/dist/rules/_exclude.d.ts +11 -0
- package/dist/rules/_exclude.js +17 -0
- package/dist/rules/_exclude.js.map +1 -0
- package/dist/rules/_function-body-analysis.d.ts +82 -0
- package/dist/rules/_function-body-analysis.js +379 -0
- package/dist/rules/_function-body-analysis.js.map +1 -0
- package/dist/rules/_rule-module.d.ts +31 -0
- package/dist/rules/_rule-module.js +32 -0
- package/dist/rules/_rule-module.js.map +1 -0
- package/dist/rules/_skip-context.d.ts +36 -0
- package/dist/rules/_skip-context.js +128 -0
- package/dist/rules/_skip-context.js.map +1 -0
- package/dist/rules/a11y-essentials.d.ts +1 -0
- package/dist/rules/a11y-essentials.js +140 -0
- package/dist/rules/a11y-essentials.js.map +1 -0
- package/dist/rules/ai-surface-agents-md-quality.d.ts +22 -0
- package/dist/rules/ai-surface-agents-md-quality.js +233 -0
- package/dist/rules/ai-surface-agents-md-quality.js.map +1 -0
- package/dist/rules/ai-surface-component-manifest-json.d.ts +19 -0
- package/dist/rules/ai-surface-component-manifest-json.js +232 -0
- package/dist/rules/ai-surface-component-manifest-json.js.map +1 -0
- package/dist/rules/ai-surface-ds-index-exported.d.ts +19 -0
- package/dist/rules/ai-surface-ds-index-exported.js +239 -0
- package/dist/rules/ai-surface-ds-index-exported.js.map +1 -0
- package/dist/rules/components-shadow-native.d.ts +2 -0
- package/dist/rules/components-shadow-native.js +118 -0
- package/dist/rules/components-shadow-native.js.map +1 -0
- package/dist/rules/manifest.d.ts +5 -0
- package/dist/rules/manifest.js +20 -0
- package/dist/rules/manifest.js.map +1 -0
- package/dist/rules/naming-component-pascalcase.d.ts +7 -0
- package/dist/rules/naming-component-pascalcase.js +197 -0
- package/dist/rules/naming-component-pascalcase.js.map +1 -0
- package/dist/rules/naming-hook-prefix.d.ts +6 -0
- package/dist/rules/naming-hook-prefix.js +185 -0
- package/dist/rules/naming-hook-prefix.js.map +1 -0
- package/dist/rules/pack-loader.d.ts +30 -0
- package/dist/rules/pack-loader.js +60 -0
- package/dist/rules/pack-loader.js.map +1 -0
- package/dist/rules/pack-validator.d.ts +8 -0
- package/dist/rules/pack-validator.js +97 -0
- package/dist/rules/pack-validator.js.map +1 -0
- package/dist/rules/registry.d.ts +3 -0
- package/dist/rules/registry.js +28 -0
- package/dist/rules/registry.js.map +1 -0
- package/dist/rules/storybook-coverage.d.ts +1 -0
- package/dist/rules/storybook-coverage.js +49 -0
- package/dist/rules/storybook-coverage.js.map +1 -0
- package/dist/rules/templates/_regex-utils.d.ts +5 -0
- package/dist/rules/templates/_regex-utils.js +8 -0
- package/dist/rules/templates/_regex-utils.js.map +1 -0
- package/dist/rules/templates/a11y-jsx-template.d.ts +15 -0
- package/dist/rules/templates/a11y-jsx-template.js +69 -0
- package/dist/rules/templates/a11y-jsx-template.js.map +1 -0
- package/dist/rules/templates/css-property-token-compliance.d.ts +18 -0
- package/dist/rules/templates/css-property-token-compliance.js +56 -0
- package/dist/rules/templates/css-property-token-compliance.js.map +1 -0
- package/dist/rules/templates/import-source-restriction.d.ts +18 -0
- package/dist/rules/templates/import-source-restriction.js +60 -0
- package/dist/rules/templates/import-source-restriction.js.map +1 -0
- package/dist/rules/templates/js-call-token-compliance.d.ts +18 -0
- package/dist/rules/templates/js-call-token-compliance.js +61 -0
- package/dist/rules/templates/js-call-token-compliance.js.map +1 -0
- package/dist/rules/templates/js-prop-token-compliance.d.ts +21 -0
- package/dist/rules/templates/js-prop-token-compliance.js +56 -0
- package/dist/rules/templates/js-prop-token-compliance.js.map +1 -0
- package/dist/rules/templates/naming-convention.d.ts +18 -0
- package/dist/rules/templates/naming-convention.js +76 -0
- package/dist/rules/templates/naming-convention.js.map +1 -0
- package/dist/rules/templates/registry.d.ts +5 -0
- package/dist/rules/templates/registry.js +30 -0
- package/dist/rules/templates/registry.js.map +1 -0
- package/dist/rules/templates/storybook-coverage-template.d.ts +15 -0
- package/dist/rules/templates/storybook-coverage-template.js +58 -0
- package/dist/rules/templates/storybook-coverage-template.js.map +1 -0
- package/dist/rules/templates/tailwind-utility-class-compliance.d.ts +15 -0
- package/dist/rules/templates/tailwind-utility-class-compliance.js +70 -0
- package/dist/rules/templates/tailwind-utility-class-compliance.js.map +1 -0
- package/dist/rules/templates/types.d.ts +16 -0
- package/dist/rules/templates/types.js +2 -0
- package/dist/rules/templates/types.js.map +1 -0
- package/dist/rules/tokens-description-coverage.d.ts +15 -0
- package/dist/rules/tokens-description-coverage.js +193 -0
- package/dist/rules/tokens-description-coverage.js.map +1 -0
- package/dist/rules/tokens-dtcg-conformance.d.ts +47 -0
- package/dist/rules/tokens-dtcg-conformance.js +363 -0
- package/dist/rules/tokens-dtcg-conformance.js.map +1 -0
- package/dist/rules/tokens-no-hardcoded-color.d.ts +18 -0
- package/dist/rules/tokens-no-hardcoded-color.js +436 -0
- package/dist/rules/tokens-no-hardcoded-color.js.map +1 -0
- package/dist/rules/tokens-no-hardcoded-spacing.d.ts +8 -0
- package/dist/rules/tokens-no-hardcoded-spacing.js +193 -0
- package/dist/rules/tokens-no-hardcoded-spacing.js.map +1 -0
- package/dist/scorer.d.ts +38 -0
- package/dist/scorer.js +126 -0
- package/dist/scorer.js.map +1 -0
- package/dist/share/clipboard.d.ts +26 -0
- package/dist/share/clipboard.js +76 -0
- package/dist/share/clipboard.js.map +1 -0
- package/dist/suppression/__tests__/inline.test.d.ts +1 -0
- package/dist/suppression/__tests__/inline.test.js +25 -0
- package/dist/suppression/__tests__/inline.test.js.map +1 -0
- package/dist/suppression/__tests__/lyseignore.test.d.ts +1 -0
- package/dist/suppression/__tests__/lyseignore.test.js +32 -0
- package/dist/suppression/__tests__/lyseignore.test.js.map +1 -0
- package/dist/suppression/defaults.d.ts +1 -0
- package/dist/suppression/defaults.js +14 -0
- package/dist/suppression/defaults.js.map +1 -0
- package/dist/suppression/inline.d.ts +7 -0
- package/dist/suppression/inline.js +32 -0
- package/dist/suppression/inline.js.map +1 -0
- package/dist/suppression/lyseignore.d.ts +2 -0
- package/dist/suppression/lyseignore.js +22 -0
- package/dist/suppression/lyseignore.js.map +1 -0
- package/dist/telemetry/consent.d.ts +48 -0
- package/dist/telemetry/consent.js +139 -0
- package/dist/telemetry/consent.js.map +1 -0
- package/dist/telemetry/index.d.ts +2 -0
- package/dist/telemetry/index.js +3 -0
- package/dist/telemetry/index.js.map +1 -0
- package/dist/telemetry/local-log.d.ts +64 -0
- package/dist/telemetry/local-log.js +123 -0
- package/dist/telemetry/local-log.js.map +1 -0
- package/dist/tokens/dtcg-model.d.ts +53 -0
- package/dist/tokens/dtcg-model.js +18 -0
- package/dist/tokens/dtcg-model.js.map +1 -0
- package/dist/tokens/normalizer.d.ts +17 -0
- package/dist/tokens/normalizer.js +360 -0
- package/dist/tokens/normalizer.js.map +1 -0
- package/dist/types.d.ts +300 -0
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -0
- package/dist/util/git.d.ts +6 -0
- package/dist/util/git.js +40 -0
- package/dist/util/git.js.map +1 -0
- package/dist/util/git.test.d.ts +5 -0
- package/dist/util/git.test.js +42 -0
- package/dist/util/git.test.js.map +1 -0
- package/dist/util/gitignore.d.ts +9 -0
- package/dist/util/gitignore.js +39 -0
- package/dist/util/gitignore.js.map +1 -0
- package/dist/util/hash-deps.d.ts +6 -0
- package/dist/util/hash-deps.js +24 -0
- package/dist/util/hash-deps.js.map +1 -0
- package/dist/util/spinner.d.ts +41 -0
- package/dist/util/spinner.js +126 -0
- package/dist/util/spinner.js.map +1 -0
- package/dist/util/with-spinner.d.ts +11 -0
- package/dist/util/with-spinner.js +19 -0
- package/dist/util/with-spinner.js.map +1 -0
- package/dist/walker.d.ts +14 -0
- package/dist/walker.js +91 -0
- package/dist/walker.js.map +1 -0
- package/package.json +83 -0
- package/rules-manifest.json +289 -0
- package/schemas/v1/lyse-config.json +82 -0
- package/schemas/v1/lyse-event.json +68 -0
- package/schemas/v1/lyse-license.json +19 -0
- package/schemas/v1/lyse-llm-payload.json +53 -0
- package/schemas/v1/lyse-llm-response.json +45 -0
- package/schemas/v1/lyse-result.json +98 -0
- package/schemas/v1/lyse-rules.json +42 -0
|
@@ -0,0 +1,436 @@
|
|
|
1
|
+
import { isAbsolute, join } from "node:path";
|
|
2
|
+
import { isInsideCodeDisplay, isCssCustomPropertyDeclaration } from "./_skip-context.js";
|
|
3
|
+
import { isPathExcluded } from "./_exclude.js";
|
|
4
|
+
import { fixHardcodedColor } from "../codemods/tokens-color.js";
|
|
5
|
+
import { adaptOldCodemodResult } from "./_codemod-adapter.js";
|
|
6
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
7
|
+
import { getTsMorphProject } from "../parsers/ts-morph-project.js";
|
|
8
|
+
// Allow one level of nested parens so hsl(var(--token)) is captured whole.
|
|
9
|
+
// Pattern: (?:[^)(]|\([^)]*\))* matches any mix of non-paren chars and
|
|
10
|
+
// single-level nested groups.
|
|
11
|
+
const COLOR_FUNC = /(#([0-9a-fA-F]{3,4}){1,2})\b|rgb[a]?\((?:[^)(]|\([^)]*\))*\)|hsl[a]?\((?:[^)(]|\([^)]*\))*\)|oklch\((?:[^)(]|\([^)]*\))*\)/g;
|
|
12
|
+
const TW_ARBITRARY = /\b(bg|text|border|fill|stroke|ring|shadow|from|to|via|outline|caret|accent|decoration|divide|placeholder)-\[#[0-9a-fA-F]{3,8}\]/g;
|
|
13
|
+
const ALLOWLIST = new Set(["currentColor", "transparent", "inherit", "initial", "unset", "none", "auto"]);
|
|
14
|
+
/**
|
|
15
|
+
* Matches color function calls whose ENTIRE argument is a CSS variable
|
|
16
|
+
* reference — e.g. hsl(var(--background)), rgba(var(--brand), 0.5),
|
|
17
|
+
* oklch(var(--c)). These are token USES (shadcn/radix theming pattern), not
|
|
18
|
+
* hardcoded colors.
|
|
19
|
+
*
|
|
20
|
+
* The regex allows an optional alpha comma-arg: var(--x), 0.5
|
|
21
|
+
*/
|
|
22
|
+
const COLOR_VAR_REF = /^(?:hsl[a]?|rgb[a]?|oklch)\(\s*var\(--[a-zA-Z0-9_-]+\)(?:\s*,\s*[^)]+)?\s*\)$/;
|
|
23
|
+
function shouldSkip(value) {
|
|
24
|
+
return ALLOWLIST.has(value.trim());
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Reads the function name immediately before the opening paren at `parenIdx`.
|
|
28
|
+
* E.g. for "var(" it returns "var", for "linear-gradient(" it returns "linear-gradient".
|
|
29
|
+
*/
|
|
30
|
+
function readFunctionNameBackwards(source, parenIdx) {
|
|
31
|
+
const end = parenIdx; // exclusive — the char at parenIdx is '('
|
|
32
|
+
// walk back over identifier chars (letters, digits, hyphens)
|
|
33
|
+
let i = end - 1;
|
|
34
|
+
while (i >= 0 && /[a-zA-Z0-9_-]/.test(source[i])) {
|
|
35
|
+
i--;
|
|
36
|
+
}
|
|
37
|
+
return source.slice(i + 1, end);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Returns true if the hit at `hitStart` is nested inside a `var(...)` call.
|
|
41
|
+
* Walks backwards counting unmatched parens; when the first unmatched opening
|
|
42
|
+
* paren is found, checks whether it belongs to a `var` call.
|
|
43
|
+
*
|
|
44
|
+
* Handles:
|
|
45
|
+
* var(--token, #hex) → true
|
|
46
|
+
* var(--a, var(--b, #hex)) → true (inner var is the unmatched paren)
|
|
47
|
+
* linear-gradient(red, #hex) → false (outer = linear-gradient)
|
|
48
|
+
* linear-gradient(r, var(--v, #h), b) → true (outer of #h is var)
|
|
49
|
+
*/
|
|
50
|
+
function isInsideVarCall(source, hitStart) {
|
|
51
|
+
let depth = 0;
|
|
52
|
+
for (let i = hitStart - 1; i >= 0; i--) {
|
|
53
|
+
const c = source[i];
|
|
54
|
+
if (c === ")") {
|
|
55
|
+
depth++;
|
|
56
|
+
}
|
|
57
|
+
else if (c === "(") {
|
|
58
|
+
if (depth === 0) {
|
|
59
|
+
// found the first unmatched opening paren — check its function name
|
|
60
|
+
const funcName = readFunctionNameBackwards(source, i);
|
|
61
|
+
return funcName === "var";
|
|
62
|
+
}
|
|
63
|
+
depth--;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Returns true if the hit at `hitStart` is on a comment line or inside a URL
|
|
70
|
+
* fragment, which means the "color" is not a real CSS value.
|
|
71
|
+
*
|
|
72
|
+
* Detects:
|
|
73
|
+
* // comment with #issue-ref
|
|
74
|
+
* /* block comment with #hex *\/
|
|
75
|
+
* * doc-comment line with #hex
|
|
76
|
+
* https://example.com#fragment or similar URL fragment
|
|
77
|
+
*/
|
|
78
|
+
function isInCommentOrUrl(source, hitStart) {
|
|
79
|
+
// Find the start of the current line
|
|
80
|
+
const lineStart = source.lastIndexOf("\n", hitStart - 1) + 1;
|
|
81
|
+
const linePrefix = source.slice(lineStart, hitStart).trimStart();
|
|
82
|
+
// // single-line comment
|
|
83
|
+
if (linePrefix.startsWith("//"))
|
|
84
|
+
return true;
|
|
85
|
+
// /* block comment or * continuation line
|
|
86
|
+
if (linePrefix.startsWith("/*") || linePrefix.startsWith("*"))
|
|
87
|
+
return true;
|
|
88
|
+
// URL fragment: look back ~60 chars for "://" then check we're after a "#"
|
|
89
|
+
// that follows something that looks like a URL (no whitespace between)
|
|
90
|
+
const lookback = source.slice(Math.max(0, hitStart - 60), hitStart);
|
|
91
|
+
// If there's "://" in the lookback and no whitespace between it and our position
|
|
92
|
+
if (lookback.includes("://") && !/\s/.test(lookback.split("://").pop() ?? "")) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
function matchCount(source, pattern) {
|
|
98
|
+
pattern.lastIndex = 0;
|
|
99
|
+
const matches = source.match(pattern);
|
|
100
|
+
return matches ? matches.length : 0;
|
|
101
|
+
}
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
// Tailwind default color palette — static list, no eval risk.
|
|
104
|
+
// Covers all named colors in Tailwind v3/v4 default theme.
|
|
105
|
+
// ---------------------------------------------------------------------------
|
|
106
|
+
const TW_COLOR_NAMES = [
|
|
107
|
+
"slate", "gray", "zinc", "neutral", "stone",
|
|
108
|
+
"red", "orange", "amber", "yellow", "lime",
|
|
109
|
+
"green", "emerald", "teal", "cyan", "sky",
|
|
110
|
+
"blue", "indigo", "violet", "purple", "fuchsia",
|
|
111
|
+
"pink", "rose",
|
|
112
|
+
];
|
|
113
|
+
const TW_COLOR_SHADES = ["50", "100", "200", "300", "400", "500", "600", "700", "800", "900", "950"];
|
|
114
|
+
// e.g. bg-slate-900, text-red-500, border-blue-200
|
|
115
|
+
const TW_COLOR_PREFIXES = "bg|text|border|ring|fill|stroke|divide|decoration|outline";
|
|
116
|
+
const TW_NAMED_SCALE = `(?:${TW_COLOR_NAMES.join("|")})-(?:${TW_COLOR_SHADES.join("|")})`;
|
|
117
|
+
// Build the regex string
|
|
118
|
+
const TW_COLOR_UTILITY_RE = new RegExp(`\\b(?:${TW_COLOR_PREFIXES})-(?:${TW_NAMED_SCALE}|white|black|transparent|current|inherit)\\b`, "g");
|
|
119
|
+
// Simple special cases: bg-white, text-transparent, etc. (already covered above via the trailing alternation)
|
|
120
|
+
/**
|
|
121
|
+
* Counts compliant (tokenized) color usages that should be counted as
|
|
122
|
+
* opportunities without being counted as findings. This gives the score
|
|
123
|
+
* formula a proper denominator so compliant repos score > 0.
|
|
124
|
+
*
|
|
125
|
+
* Counts:
|
|
126
|
+
* - hsl/hsla/rgb/rgba/oklch wrapping a var() ref (CSS variable theming pattern)
|
|
127
|
+
* - standalone var(--token) in CSS declaration position
|
|
128
|
+
* - theme.colors.X / theme.palette.X / palette.X.Y / tokens.color.X in TS/TSX/JSX
|
|
129
|
+
* - Tailwind utility color classes: bg-slate-900, text-white, border-red-500, etc.
|
|
130
|
+
*/
|
|
131
|
+
export function countCompliantColorUses(source, fileExt) {
|
|
132
|
+
let count = 0;
|
|
133
|
+
// Pattern 1: hsl(var(...)), rgba(var(...)), oklch(var(...)), etc.
|
|
134
|
+
count += matchCount(source, /\b(?:hsl[a]?|rgb[a]?|oklch)\s*\(\s*var\(/g);
|
|
135
|
+
// Pattern 2: standalone `var(--token)` in CSS declaration value position
|
|
136
|
+
// Matches `: var(--foo)` followed by ; or } (skips hsl(var()) which pattern 1 already handles)
|
|
137
|
+
if (fileExt === ".css" || fileExt === ".scss") {
|
|
138
|
+
count += matchCount(source, /:\s*var\(--[a-zA-Z][\w-]*\)\s*[;},]/g);
|
|
139
|
+
}
|
|
140
|
+
// Pattern 3: theme.colors.X / theme.palette.X / palette.X.Y / tokens.color.X in TS/JSX
|
|
141
|
+
if (fileExt === ".ts" || fileExt === ".tsx" || fileExt === ".jsx" || fileExt === ".js") {
|
|
142
|
+
count += matchCount(source, /\btheme\.(?:colors|palette)\.[\w.[\]'"`]+/g);
|
|
143
|
+
count += matchCount(source, /\btokens\.color\.[\w.[\]'"`]+/g);
|
|
144
|
+
count += matchCount(source, /\bpalette\.[\w]+\.\w+/g);
|
|
145
|
+
}
|
|
146
|
+
// Pattern 4: Tailwind utility color classes in TSX/JSX/TS/JS files
|
|
147
|
+
// bg-slate-900, text-white, border-red-500, ring-blue-200, etc.
|
|
148
|
+
if (fileExt === ".ts" || fileExt === ".tsx" || fileExt === ".jsx" || fileExt === ".js") {
|
|
149
|
+
TW_COLOR_UTILITY_RE.lastIndex = 0;
|
|
150
|
+
count += matchCount(source, TW_COLOR_UTILITY_RE);
|
|
151
|
+
}
|
|
152
|
+
return count;
|
|
153
|
+
}
|
|
154
|
+
export function detectInText(source, _path) {
|
|
155
|
+
const hits = [];
|
|
156
|
+
COLOR_FUNC.lastIndex = 0;
|
|
157
|
+
let m;
|
|
158
|
+
while ((m = COLOR_FUNC.exec(source)) !== null) {
|
|
159
|
+
if (shouldSkip(m[0]))
|
|
160
|
+
continue;
|
|
161
|
+
// Skip any color function whose entire argument is a CSS variable reference
|
|
162
|
+
// (canonical shadcn / radix-ui theming pattern — NOT hardcoded drift).
|
|
163
|
+
if (COLOR_VAR_REF.test(m[0]))
|
|
164
|
+
continue;
|
|
165
|
+
// Skip values inside <code>...</code> or <pre>...</pre> on the same line
|
|
166
|
+
// (display-only CSS examples — e.g. shadcn theme customizer).
|
|
167
|
+
// NOTE: multi-line code blocks are not detected here; V1 needs AST context.
|
|
168
|
+
if (isInsideCodeDisplay(source, m.index))
|
|
169
|
+
continue;
|
|
170
|
+
// Skip hex/color literals that are inside a var() fallback argument.
|
|
171
|
+
// e.g. var(--token, #8c959f) — the #hex is a safe CSS fallback, not drift.
|
|
172
|
+
if (isInsideVarCall(source, m.index))
|
|
173
|
+
continue;
|
|
174
|
+
// Skip color literals in comments (// /* *) and URL fragments (#anchor).
|
|
175
|
+
if (isInCommentOrUrl(source, m.index))
|
|
176
|
+
continue;
|
|
177
|
+
if (isCssCustomPropertyDeclaration(source, m.index))
|
|
178
|
+
continue;
|
|
179
|
+
hits.push({ match: m[0], index: m.index });
|
|
180
|
+
}
|
|
181
|
+
TW_ARBITRARY.lastIndex = 0;
|
|
182
|
+
while ((m = TW_ARBITRARY.exec(source)) !== null) {
|
|
183
|
+
if (isInsideCodeDisplay(source, m.index))
|
|
184
|
+
continue;
|
|
185
|
+
if (isInCommentOrUrl(source, m.index))
|
|
186
|
+
continue;
|
|
187
|
+
if (isCssCustomPropertyDeclaration(source, m.index))
|
|
188
|
+
continue;
|
|
189
|
+
hits.push({ match: m[0], index: m.index });
|
|
190
|
+
}
|
|
191
|
+
return hits;
|
|
192
|
+
}
|
|
193
|
+
function locationFromIndex(source, index) {
|
|
194
|
+
let line = 1;
|
|
195
|
+
let column = 1;
|
|
196
|
+
for (let i = 0; i < index && i < source.length; i++) {
|
|
197
|
+
if (source.charCodeAt(i) === 10) {
|
|
198
|
+
line++;
|
|
199
|
+
column = 1;
|
|
200
|
+
}
|
|
201
|
+
else {
|
|
202
|
+
column++;
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { line, column };
|
|
206
|
+
}
|
|
207
|
+
function suggestToken(ctx, raw) {
|
|
208
|
+
if (!ctx.tokens)
|
|
209
|
+
return undefined;
|
|
210
|
+
// Extract the hex from a Tailwind arbitrary value like bg-[#fff]
|
|
211
|
+
const key = raw.replace(/^.*\[(.*)\]$/, "$1").toLowerCase();
|
|
212
|
+
const tokens = ctx.tokens.colors.get(key) ?? ctx.tokens.colors.get(raw.toLowerCase());
|
|
213
|
+
if (!tokens || tokens.length === 0)
|
|
214
|
+
return undefined;
|
|
215
|
+
if (tokens.length === 1)
|
|
216
|
+
return `consider replacing with token ${tokens[0]}`;
|
|
217
|
+
return `consider replacing — multiple candidate tokens: ${tokens.join(", ")}`;
|
|
218
|
+
}
|
|
219
|
+
const evaluate = async (ctx, files) => {
|
|
220
|
+
const findings = [];
|
|
221
|
+
let opportunities = 0;
|
|
222
|
+
for (const f of files.ts) {
|
|
223
|
+
if (isPathExcluded(f.path, ctx.excludePaths))
|
|
224
|
+
continue;
|
|
225
|
+
const fileExt = f.path.match(/\.[^.]+$/)?.[0] ?? ".ts";
|
|
226
|
+
const hits = detectInText(f.source);
|
|
227
|
+
const compliantCount = countCompliantColorUses(f.source, fileExt);
|
|
228
|
+
opportunities += hits.length + compliantCount;
|
|
229
|
+
for (const h of hits) {
|
|
230
|
+
const loc = locationFromIndex(f.source, h.index);
|
|
231
|
+
const suggestion = suggestToken(ctx, h.match);
|
|
232
|
+
const lineText = f.source.split("\n")[loc.line - 1]?.trim().slice(0, 120);
|
|
233
|
+
findings.push({
|
|
234
|
+
ruleId: "tokens/no-hardcoded-color",
|
|
235
|
+
axis: "tokens",
|
|
236
|
+
severity: "warning",
|
|
237
|
+
location: { file: f.path, line: loc.line, column: loc.column },
|
|
238
|
+
message: `Hardcoded color value: ${h.match}`,
|
|
239
|
+
...(suggestion !== undefined && { suggestion }),
|
|
240
|
+
...(lineText !== undefined && { context: lineText }),
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
for (const c of files.css) {
|
|
245
|
+
if (isPathExcluded(c.path, ctx.excludePaths))
|
|
246
|
+
continue;
|
|
247
|
+
const fileExt = c.path.match(/\.[^.]+$/)?.[0] ?? ".css";
|
|
248
|
+
const hits = detectInText(c.source);
|
|
249
|
+
const compliantCount = countCompliantColorUses(c.source, fileExt);
|
|
250
|
+
opportunities += hits.length + compliantCount;
|
|
251
|
+
for (const h of hits) {
|
|
252
|
+
const loc = locationFromIndex(c.source, h.index);
|
|
253
|
+
const suggestion = suggestToken(ctx, h.match);
|
|
254
|
+
findings.push({
|
|
255
|
+
ruleId: "tokens/no-hardcoded-color",
|
|
256
|
+
axis: "tokens",
|
|
257
|
+
severity: "warning",
|
|
258
|
+
location: { file: c.path, line: loc.line, column: loc.column },
|
|
259
|
+
message: `Hardcoded color value: ${h.match}`,
|
|
260
|
+
...(suggestion !== undefined && { suggestion }),
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
for (const b of files.cssInJs) {
|
|
265
|
+
if (isPathExcluded(b.path, ctx.excludePaths))
|
|
266
|
+
continue;
|
|
267
|
+
const fileExt = b.path.match(/\.[^.]+$/)?.[0] ?? ".tsx";
|
|
268
|
+
const hits = detectInText(b.content);
|
|
269
|
+
const compliantCount = countCompliantColorUses(b.content, fileExt);
|
|
270
|
+
opportunities += hits.length + compliantCount;
|
|
271
|
+
for (const h of hits) {
|
|
272
|
+
const suggestion = suggestToken(ctx, h.match);
|
|
273
|
+
findings.push({
|
|
274
|
+
ruleId: "tokens/no-hardcoded-color",
|
|
275
|
+
axis: "tokens",
|
|
276
|
+
severity: "warning",
|
|
277
|
+
location: { file: b.path, line: b.line, column: 1 },
|
|
278
|
+
message: `Hardcoded color value in styled-components: ${h.match}`,
|
|
279
|
+
...(suggestion !== undefined && { suggestion }),
|
|
280
|
+
});
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return { findings, opportunities };
|
|
284
|
+
};
|
|
285
|
+
const TOKEN_DEF_EXPORT_NAME = /^(colors|theme|tokens|palette|brand)$/i;
|
|
286
|
+
const HEX_LITERAL = /^#[0-9a-fA-F]{3,8}$/;
|
|
287
|
+
/**
|
|
288
|
+
* Uses ts-morph to determine whether `filePath` is the canonical design-token
|
|
289
|
+
* definition file (e.g. exports `theme = { primary: "#ff0000", ... }`).
|
|
290
|
+
*
|
|
291
|
+
* Heuristic: the file's exported declarations include at least one name in
|
|
292
|
+
* `/^(colors|theme|tokens|palette|brand)$/i` whose initializer is an object
|
|
293
|
+
* literal with >= 3 string-valued properties, where most string values look
|
|
294
|
+
* like hex color literals.
|
|
295
|
+
*
|
|
296
|
+
* Returns `false` on any error (missing file, parse failure, etc.) so the
|
|
297
|
+
* check never introduces false negatives — at worst confidence stays "high".
|
|
298
|
+
*/
|
|
299
|
+
function isTokenDefinitionFile(filePath, repoRoot) {
|
|
300
|
+
if (!/\.(ts|tsx|js|jsx|mjs|cjs)$/.test(filePath))
|
|
301
|
+
return false;
|
|
302
|
+
const absolute = isAbsolute(filePath) ? filePath : join(repoRoot, filePath);
|
|
303
|
+
try {
|
|
304
|
+
const tsm = getTsMorphProject(repoRoot);
|
|
305
|
+
const sf = tsm.getSourceFile(absolute);
|
|
306
|
+
if (!sf)
|
|
307
|
+
return false;
|
|
308
|
+
const exported = sf.getExportedDeclarations();
|
|
309
|
+
for (const [name, decls] of exported) {
|
|
310
|
+
if (!TOKEN_DEF_EXPORT_NAME.test(name))
|
|
311
|
+
continue;
|
|
312
|
+
for (const decl of decls) {
|
|
313
|
+
if (looksLikeTokenObjectLiteral(decl))
|
|
314
|
+
return true;
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
return false;
|
|
318
|
+
}
|
|
319
|
+
catch {
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Returns true if `decl` is (or contains) an object literal with >= 3
|
|
325
|
+
* string-valued properties of which >= half are hex-color literals.
|
|
326
|
+
*/
|
|
327
|
+
function looksLikeTokenObjectLiteral(decl) {
|
|
328
|
+
const d = decl;
|
|
329
|
+
// Variable declarations carry an initializer; object-literal nodes carry
|
|
330
|
+
// properties directly.
|
|
331
|
+
let obj;
|
|
332
|
+
if (typeof d.getInitializer === "function") {
|
|
333
|
+
const init = d.getInitializer();
|
|
334
|
+
if (init && typeof init.getProperties === "function") {
|
|
335
|
+
obj = init;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
else if (typeof d.getProperties === "function") {
|
|
339
|
+
obj = d;
|
|
340
|
+
}
|
|
341
|
+
if (!obj?.getProperties)
|
|
342
|
+
return false;
|
|
343
|
+
const props = obj.getProperties();
|
|
344
|
+
if (props.length < 3)
|
|
345
|
+
return false;
|
|
346
|
+
let stringValueCount = 0;
|
|
347
|
+
let hexishCount = 0;
|
|
348
|
+
for (const p of props) {
|
|
349
|
+
const prop = p;
|
|
350
|
+
if (typeof prop.getInitializer !== "function")
|
|
351
|
+
continue;
|
|
352
|
+
const init = prop.getInitializer();
|
|
353
|
+
if (!init)
|
|
354
|
+
continue;
|
|
355
|
+
const raw = typeof init.getLiteralText === "function"
|
|
356
|
+
? init.getLiteralText()
|
|
357
|
+
: typeof init.getText === "function"
|
|
358
|
+
? init.getText().replace(/^['"`]|['"`]$/g, "")
|
|
359
|
+
: undefined;
|
|
360
|
+
if (typeof raw !== "string")
|
|
361
|
+
continue;
|
|
362
|
+
stringValueCount++;
|
|
363
|
+
if (HEX_LITERAL.test(raw.trim()))
|
|
364
|
+
hexishCount++;
|
|
365
|
+
}
|
|
366
|
+
if (stringValueCount < 3)
|
|
367
|
+
return false;
|
|
368
|
+
// Require at least half the string values to look like hex — guards against
|
|
369
|
+
// false positives on unrelated string maps named "theme" / "brand".
|
|
370
|
+
return hexishCount * 2 >= stringValueCount;
|
|
371
|
+
}
|
|
372
|
+
const classifyConfidence = (finding, ctx) => {
|
|
373
|
+
// Extract color value from message — format: "Hardcoded color value: <value>"
|
|
374
|
+
const colorMatch = finding.message.match(/:\s*(.+)$/);
|
|
375
|
+
const raw = colorMatch?.[1]?.trim() ?? "";
|
|
376
|
+
// Alpha channel means uncertain replacement — the token may not carry opacity
|
|
377
|
+
const hasAlpha = /rgba?\([^)]+,\s*[\d.]+\)|#[0-9a-fA-F]{8}\b/.test(raw);
|
|
378
|
+
if (hasAlpha)
|
|
379
|
+
return "medium";
|
|
380
|
+
if (!raw)
|
|
381
|
+
return "low";
|
|
382
|
+
const key = raw.toLowerCase();
|
|
383
|
+
const candidates = ctx.tokens.colors.get(key) ?? ctx.tokens.colors.get(key.replace(/^.*\[(.*)\]$/, "$1"));
|
|
384
|
+
if (!candidates || candidates.length === 0)
|
|
385
|
+
return "low";
|
|
386
|
+
// Token-definition files (where hex literals are EXPECTED) get medium
|
|
387
|
+
// confidence so they are not auto-fixed by default. Requires ctx.repoRoot
|
|
388
|
+
// to be set; if absent, fall through to "high".
|
|
389
|
+
if (ctx.repoRoot && isTokenDefinitionFile(finding.location.file, ctx.repoRoot)) {
|
|
390
|
+
return "medium";
|
|
391
|
+
}
|
|
392
|
+
return "high";
|
|
393
|
+
};
|
|
394
|
+
const applyCodemod = (finding, ctx) => {
|
|
395
|
+
const ruleCtx = {
|
|
396
|
+
repoRoot: "",
|
|
397
|
+
tokens: ctx.tokens,
|
|
398
|
+
componentsModule: ctx.config.designSystem?.componentsModule ?? null,
|
|
399
|
+
componentInventory: [],
|
|
400
|
+
storyIndex: null,
|
|
401
|
+
excludePaths: [],
|
|
402
|
+
};
|
|
403
|
+
const oldResult = fixHardcodedColor({
|
|
404
|
+
source: ctx.fileContent,
|
|
405
|
+
path: finding.location.file,
|
|
406
|
+
finding,
|
|
407
|
+
ctx: ruleCtx,
|
|
408
|
+
});
|
|
409
|
+
return adaptOldCodemodResult(oldResult);
|
|
410
|
+
};
|
|
411
|
+
export const rule = createLyseRule({
|
|
412
|
+
meta: {
|
|
413
|
+
axis: "tokens",
|
|
414
|
+
lyseRuleId: "tokens/no-hardcoded-color",
|
|
415
|
+
defaultSeverity: "warning",
|
|
416
|
+
shortDescription: "Disallow hardcoded color values",
|
|
417
|
+
fullDescription: "Hardcoded color values (#hex, rgb(), hsl(), oklch(), Tailwind arbitrary `bg-[#fff]`) bypass the design system. They survive token changes silently (a brand refresh becomes a manual hunt) and break dark-mode propagation through CSS variables.",
|
|
418
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/tokens-no-hardcoded-color.md",
|
|
419
|
+
rationale: `Why it matters
|
|
420
|
+
|
|
421
|
+
Hardcoded colors are the #1 signal that an AI agent ignored the design contract. They silently fork the design system: each #2563eb that should be color.action.primary is a token-rename bomb waiting to detonate.
|
|
422
|
+
|
|
423
|
+
When the rule fires, the suggestion includes the matching token from the project's TokenMap when the reverse-lookup yields exactly one candidate. When multiple tokens map to the same color value (common with primitive vs semantic token layers), all candidates are listed — the agent or human picks.`,
|
|
424
|
+
examples: [
|
|
425
|
+
{ good: '<div className="bg-action-primary text-on-action">', bad: '<div style={{ background: "#2563eb", color: "#fff" }}>' },
|
|
426
|
+
{ good: '<div className="bg-action-primary">', bad: '<div className="bg-[#2563eb]">' },
|
|
427
|
+
{ good: "color: var(--color-action-primary);", bad: "color: hsl(214, 86%, 53%);" },
|
|
428
|
+
],
|
|
429
|
+
allowlist: ["currentColor", "transparent", "inherit", "initial", "unset", "none", "auto"],
|
|
430
|
+
},
|
|
431
|
+
defaultOptions: [],
|
|
432
|
+
create: () => ({ evaluate }),
|
|
433
|
+
classifyConfidence,
|
|
434
|
+
applyCodemod,
|
|
435
|
+
});
|
|
436
|
+
//# sourceMappingURL=tokens-no-hardcoded-color.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tokens-no-hardcoded-color.js","sourceRoot":"","sources":["../../src/rules/tokens-no-hardcoded-color.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Rule } from "../types.js";
|
|
2
|
+
/**
|
|
3
|
+
* Counts compliant Tailwind spacing utility usages in TSX/JSX/TS/JS files.
|
|
4
|
+
* p-4, m-2, gap-8, space-x-4, w-full, h-screen, etc.
|
|
5
|
+
* These are on-scale token usages encoded as class names.
|
|
6
|
+
*/
|
|
7
|
+
export declare function countCompliantSpacingUses(source: string, fileExt: string): number;
|
|
8
|
+
export declare const rule: Rule;
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
import { isInsideSkippedJsxAttr, isInsideCodeDisplay, isCssCustomPropertyDeclaration } from "./_skip-context.js";
|
|
2
|
+
import { isPathExcluded } from "./_exclude.js";
|
|
3
|
+
import { fixHardcodedSpacing } from "../codemods/tokens-spacing.js";
|
|
4
|
+
import { adaptOldCodemodResult } from "./_codemod-adapter.js";
|
|
5
|
+
import { createLyseRule } from "./_rule-module.js";
|
|
6
|
+
const PX_REM_EM = /\b(\d+(\.\d+)?)(px|rem|em)\b/g;
|
|
7
|
+
const ALLOW_PX_VALUES = new Set(["0", "1", "100"]);
|
|
8
|
+
const ALLOW_KEYWORDS = new Set(["auto", "100%", "100vh", "100vw", "0"]);
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// Tailwind spacing scale — standard Tailwind v3/v4 spacing values.
|
|
11
|
+
// Static list; no eval risk.
|
|
12
|
+
// ---------------------------------------------------------------------------
|
|
13
|
+
// Standard numeric scale (including 0.5 / 1.5 etc. represented as "0\.5")
|
|
14
|
+
const TW_SPACING_SCALE_NUMS = [
|
|
15
|
+
"0", "0\\.5", "1", "1\\.5", "2", "2\\.5", "3", "3\\.5",
|
|
16
|
+
"4", "5", "6", "7", "8", "9", "10", "11", "12", "14",
|
|
17
|
+
"16", "20", "24", "28", "32", "36", "40", "44", "48",
|
|
18
|
+
"52", "56", "60", "64", "72", "80", "96",
|
|
19
|
+
];
|
|
20
|
+
const TW_SPACING_KEYWORDS = ["px", "full", "screen", "auto", "svh", "dvh", "lvh"];
|
|
21
|
+
const TW_SPACING_VALUES = `(?:${TW_SPACING_SCALE_NUMS.join("|")}|${TW_SPACING_KEYWORDS.join("|")})`;
|
|
22
|
+
// Spacing utility prefixes:
|
|
23
|
+
// p, px, py, pt, pr, pb, pl — padding
|
|
24
|
+
// m, mx, my, mt, mr, mb, ml — margin
|
|
25
|
+
// gap, gap-x, gap-y
|
|
26
|
+
// space-x, space-y
|
|
27
|
+
// w, h, min-w, min-h, max-w, max-h
|
|
28
|
+
// inset, inset-x, inset-y, top, right, bottom, left
|
|
29
|
+
const TW_SPACING_PREFIXES = "p[xytblr]?|m[xytblr]?|gap(?:-[xy])?|space-[xy]|w|h|min-[wh]|max-[wh]|inset(?:-[xy])?|top|right|bottom|left|size";
|
|
30
|
+
const TW_SPACING_UTILITY_RE = new RegExp(`\\b(?:${TW_SPACING_PREFIXES})-${TW_SPACING_VALUES}\\b`, "g");
|
|
31
|
+
/**
|
|
32
|
+
* Counts compliant Tailwind spacing utility usages in TSX/JSX/TS/JS files.
|
|
33
|
+
* p-4, m-2, gap-8, space-x-4, w-full, h-screen, etc.
|
|
34
|
+
* These are on-scale token usages encoded as class names.
|
|
35
|
+
*/
|
|
36
|
+
export function countCompliantSpacingUses(source, fileExt) {
|
|
37
|
+
if (fileExt !== ".ts" && fileExt !== ".tsx" && fileExt !== ".jsx" && fileExt !== ".js") {
|
|
38
|
+
return 0;
|
|
39
|
+
}
|
|
40
|
+
TW_SPACING_UTILITY_RE.lastIndex = 0;
|
|
41
|
+
const matches = source.match(TW_SPACING_UTILITY_RE);
|
|
42
|
+
return matches ? matches.length : 0;
|
|
43
|
+
}
|
|
44
|
+
function locationFromIndex(source, index) {
|
|
45
|
+
let line = 1, column = 1;
|
|
46
|
+
for (let i = 0; i < index && i < source.length; i++) {
|
|
47
|
+
if (source.charCodeAt(i) === 10) {
|
|
48
|
+
line++;
|
|
49
|
+
column = 1;
|
|
50
|
+
}
|
|
51
|
+
else {
|
|
52
|
+
column++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return { line, column };
|
|
56
|
+
}
|
|
57
|
+
function isOnScale(ctx, value) {
|
|
58
|
+
if (!ctx.tokens)
|
|
59
|
+
return false;
|
|
60
|
+
return ctx.tokens.spacing.has(String(value));
|
|
61
|
+
}
|
|
62
|
+
function suggestSpacing(ctx, raw) {
|
|
63
|
+
if (!ctx.tokens)
|
|
64
|
+
return undefined;
|
|
65
|
+
const m = raw.match(/^(\d+(\.\d+)?)(px|rem|em)$/);
|
|
66
|
+
if (!m)
|
|
67
|
+
return undefined;
|
|
68
|
+
const candidates = ctx.tokens.spacing.get(m[1]);
|
|
69
|
+
if (!candidates || candidates.length === 0)
|
|
70
|
+
return undefined;
|
|
71
|
+
return candidates.length === 1 ? `consider token ${candidates[0]}` : `candidate tokens: ${candidates.join(", ")}`;
|
|
72
|
+
}
|
|
73
|
+
const evaluate = async (ctx, files) => {
|
|
74
|
+
const findings = [];
|
|
75
|
+
let opportunities = 0;
|
|
76
|
+
const scan = (path, source, blockLine = 0) => {
|
|
77
|
+
PX_REM_EM.lastIndex = 0;
|
|
78
|
+
let m;
|
|
79
|
+
while ((m = PX_REM_EM.exec(source)) !== null) {
|
|
80
|
+
const raw = m[0];
|
|
81
|
+
const num = m[1];
|
|
82
|
+
const unit = m[3];
|
|
83
|
+
opportunities++;
|
|
84
|
+
if (unit === "px" && ALLOW_PX_VALUES.has(num))
|
|
85
|
+
continue;
|
|
86
|
+
if (isOnScale(ctx, parseFloat(num)))
|
|
87
|
+
continue;
|
|
88
|
+
// Skip px/rem/em values inside JSX attributes that carry media-query
|
|
89
|
+
// breakpoints (sizes, srcSet, media). These are responsive image
|
|
90
|
+
// markup — not spacing tokens. NOTE: sizes={"..."} (JSX expression
|
|
91
|
+
// form) is NOT handled here; requires AST traversal — deferred to V1.
|
|
92
|
+
if (isInsideSkippedJsxAttr(source, m.index))
|
|
93
|
+
continue;
|
|
94
|
+
// Skip values inside same-line <code>...</code> or <pre>...</pre>
|
|
95
|
+
// blocks (display-only examples). Multi-line blocks are V1 work.
|
|
96
|
+
if (isInsideCodeDisplay(source, m.index))
|
|
97
|
+
continue;
|
|
98
|
+
if (isCssCustomPropertyDeclaration(source, m.index))
|
|
99
|
+
continue;
|
|
100
|
+
const loc = blockLine > 0 ? { line: blockLine, column: 1 } : locationFromIndex(source, m.index);
|
|
101
|
+
const suggestion = suggestSpacing(ctx, raw);
|
|
102
|
+
findings.push({
|
|
103
|
+
ruleId: "tokens/no-hardcoded-spacing",
|
|
104
|
+
axis: "tokens",
|
|
105
|
+
severity: "warning",
|
|
106
|
+
location: { file: path, line: loc.line, column: loc.column },
|
|
107
|
+
message: `Off-scale spacing: ${raw}`,
|
|
108
|
+
...(suggestion !== undefined && { suggestion }),
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
for (const f of files.ts) {
|
|
113
|
+
if (isPathExcluded(f.path, ctx.excludePaths))
|
|
114
|
+
continue;
|
|
115
|
+
scan(f.path, f.source);
|
|
116
|
+
// Also count Tailwind spacing utility classes as compliant opportunities
|
|
117
|
+
const fileExt = f.path.match(/\.[^.]+$/)?.[0] ?? ".ts";
|
|
118
|
+
opportunities += countCompliantSpacingUses(f.source, fileExt);
|
|
119
|
+
}
|
|
120
|
+
for (const c of files.css) {
|
|
121
|
+
if (!isPathExcluded(c.path, ctx.excludePaths))
|
|
122
|
+
scan(c.path, c.source);
|
|
123
|
+
}
|
|
124
|
+
for (const b of files.cssInJs) {
|
|
125
|
+
if (!isPathExcluded(b.path, ctx.excludePaths))
|
|
126
|
+
scan(b.path, b.content, b.line);
|
|
127
|
+
}
|
|
128
|
+
return { findings, opportunities };
|
|
129
|
+
};
|
|
130
|
+
const classifyConfidence = (finding, ctx) => {
|
|
131
|
+
// Extract spacing value from message — format: "Off-scale spacing: 16px"
|
|
132
|
+
const rawMatch = finding.message.match(/:\s*(.+)$/);
|
|
133
|
+
const raw = rawMatch?.[1]?.trim() ?? "";
|
|
134
|
+
// Negative spacing is unusual — may be intentional, flag for human review
|
|
135
|
+
const isNegative = /^-\d/.test(raw) || (finding.context !== undefined && /-\d+px/.test(finding.context));
|
|
136
|
+
if (isNegative)
|
|
137
|
+
return "medium";
|
|
138
|
+
const numMatch = raw.match(/^(\d+(\.\d+)?)(px|rem|em)$/);
|
|
139
|
+
if (!numMatch)
|
|
140
|
+
return "low";
|
|
141
|
+
const numericValue = numMatch[1];
|
|
142
|
+
const candidates = ctx.tokens.spacing.get(numericValue);
|
|
143
|
+
if (!candidates || candidates.length === 0)
|
|
144
|
+
return "low";
|
|
145
|
+
// Multiple matches require human choice
|
|
146
|
+
if (candidates.length > 1)
|
|
147
|
+
return "medium";
|
|
148
|
+
return "high";
|
|
149
|
+
};
|
|
150
|
+
const applyCodemod = (finding, ctx) => {
|
|
151
|
+
const ruleCtx = {
|
|
152
|
+
repoRoot: "",
|
|
153
|
+
tokens: ctx.tokens,
|
|
154
|
+
componentsModule: ctx.config.designSystem?.componentsModule ?? null,
|
|
155
|
+
componentInventory: [],
|
|
156
|
+
storyIndex: null,
|
|
157
|
+
excludePaths: [],
|
|
158
|
+
};
|
|
159
|
+
const oldResult = fixHardcodedSpacing({
|
|
160
|
+
source: ctx.fileContent,
|
|
161
|
+
path: finding.location.file,
|
|
162
|
+
finding,
|
|
163
|
+
ctx: ruleCtx,
|
|
164
|
+
});
|
|
165
|
+
return adaptOldCodemodResult(oldResult);
|
|
166
|
+
};
|
|
167
|
+
export const rule = createLyseRule({
|
|
168
|
+
meta: {
|
|
169
|
+
axis: "tokens",
|
|
170
|
+
lyseRuleId: "tokens/no-hardcoded-spacing",
|
|
171
|
+
defaultSeverity: "warning",
|
|
172
|
+
shortDescription: "Disallow off-scale spacing values",
|
|
173
|
+
fullDescription: "Padding, margin, gap, and similar properties using raw px/rem/em values outside the documented spacing scale (Tailwind config, DTCG dimension tokens, or CSS variables) produce inconsistent rhythm and break theming.",
|
|
174
|
+
helpUri: "https://github.com/lyse-labs/lyse/blob/main/docs/rules/tokens-no-hardcoded-spacing.md",
|
|
175
|
+
rationale: `Why it matters
|
|
176
|
+
|
|
177
|
+
The spacing scale encodes the rhythm of the product. A one-off \`padding: 7px\` survives every design-pass and slowly desynchronizes layouts. When the rule fires, the suggestion includes the matching scale step when the value maps to exactly one token.
|
|
178
|
+
|
|
179
|
+
The allowlist accommodates 1px borders (\`border: 1px solid\`), zero, and full-viewport keywords — these are not design-system tokens but pragmatic primitives.`,
|
|
180
|
+
examples: [
|
|
181
|
+
{ good: '<div className="p-2">', bad: '<div style={{ padding: "7px" }}>' },
|
|
182
|
+
{ good: "gap: var(--spacing-4);", bad: "gap: 13px;" },
|
|
183
|
+
],
|
|
184
|
+
allowlist: ["0", "auto", "100%", "100vh", "100vw"],
|
|
185
|
+
},
|
|
186
|
+
defaultOptions: [],
|
|
187
|
+
create: () => ({ evaluate }),
|
|
188
|
+
classifyConfidence,
|
|
189
|
+
applyCodemod,
|
|
190
|
+
});
|
|
191
|
+
// silence "unused" — keyword detection is for future expansion.
|
|
192
|
+
void ALLOW_KEYWORDS;
|
|
193
|
+
//# sourceMappingURL=tokens-no-hardcoded-spacing.js.map
|