@stll/oxlint-config 0.4.0 → 0.5.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/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -2
- package/dist/index.d.mts +1 -2
- package/dist/index.mjs +1 -1
- package/dist/index.mjs.map +1 -1
- package/dist/no-raw-colors.cjs +1 -1
- package/dist/no-raw-colors.cjs.map +1 -1
- package/dist/no-raw-colors.mjs +1 -1
- package/dist/no-raw-colors.mjs.map +1 -1
- package/dist/plugin.cjs +3 -3
- package/dist/plugin.cjs.map +1 -1
- package/dist/plugin.mjs +3 -3
- package/dist/plugin.mjs.map +1 -1
- package/package.json +5 -5
package/dist/index.cjs
CHANGED
|
@@ -136,7 +136,7 @@ const library = (options = {}) => (0, oxlint.defineConfig)({
|
|
|
136
136
|
typeAware: true,
|
|
137
137
|
...options.options
|
|
138
138
|
},
|
|
139
|
-
plugins: Array.from(new Set([...libraryPlugins, ...options.plugins ?? []])),
|
|
139
|
+
plugins: Array.from(/* @__PURE__ */ new Set([...libraryPlugins, ...options.plugins ?? []])),
|
|
140
140
|
jsPlugins: [
|
|
141
141
|
stellaLowercasePluginSpecifier,
|
|
142
142
|
noRawColorsPluginSpecifier,
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { defineConfig, type OxlintConfig, type OxlintOverride } from \"oxlint\";\n\ntype Rules = NonNullable<OxlintConfig[\"rules\"]>;\ntype JsPlugins = NonNullable<OxlintConfig[\"jsPlugins\"]>;\ntype Options = NonNullable<OxlintConfig[\"options\"]>;\ntype Plugins = NonNullable<OxlintConfig[\"plugins\"]>;\n\nexport type LibraryOptions = {\n jsPlugins?: JsPlugins;\n ignorePatterns?: string[];\n options?: Options;\n overrides?: OxlintOverride[];\n plugins?: Plugins;\n rules?: Rules;\n};\n\nexport const stellaLowercasePluginSpecifier = \"@stll/oxlint-config/plugin\";\nexport const noRawColorsPluginSpecifier = \"@stll/oxlint-config/no-raw-colors\";\n\nexport const libraryIgnorePatterns = [\"node_modules/\", \"dist/\", \"coverage/\"];\n\n// Setting oxlint's `plugins` config replaces its built-in default list rather\n// than appending to it. This must stay in sync with every plugin-prefixed\n// rule below: `import/*` and `promise/*` rules are configured further down,\n// but oxlint disables both plugins unless they are listed here, which left\n// `import/no-cycle` and every `promise/*` rule silently unenforced. `react`\n// is enabled for `react/react-compiler`; see the comment on that rule for\n// why every other `react/*` rule is explicitly turned back off below.\nexport const libraryPlugins = [\n \"eslint\",\n \"typescript\",\n \"unicorn\",\n \"oxc\",\n \"import\",\n \"promise\",\n \"react\",\n] satisfies Plugins;\n\nexport const libraryRules = {\n \"no-console\": \"off\",\n \"no-shadow\": \"error\",\n \"require-await\": \"error\",\n \"no-useless-catch\": \"error\",\n \"no-non-null-assertion\": \"error\",\n \"no-useless-assignment\": \"error\",\n \"no-loop-func\": \"error\",\n \"no-nested-ternary\": \"error\",\n \"no-void\": [\"error\", { allowAsStatement: true }],\n // Callback expressions should stay lexically scoped; avoids accidental `this` rebinding.\n \"prefer-arrow-callback\": \"error\",\n \"stella-lowercase/stella-lowercase\": \"error\",\n \"no-raw-colors/no-raw-colors\": \"error\",\n\n // Spreading into an accumulator on every loop iteration is O(n^2); push/concat instead.\n \"oxc/no-accumulating-spread\": \"error\",\n // Spreading inside `.map()` allocates a new array per callback invocation; push instead.\n \"oxc/no-map-spread\": \"error\",\n // Flags `if`/`switch` branches with identical bodies: likely dead logic or a missed merge.\n \"oxc/branches-sharing-code\": \"error\",\n\n \"typescript/no-explicit-any\": \"error\",\n \"typescript/no-dynamic-delete\": \"error\",\n \"typescript/no-misused-promises\": [\n \"error\",\n { checksVoidReturn: { attributes: false } },\n ],\n \"typescript/consistent-type-definitions\": [\"error\", \"type\"],\n \"typescript/consistent-return\": \"error\",\n \"typescript/dot-notation\": \"error\",\n \"typescript/no-inferrable-types\": \"off\",\n \"typescript/no-unnecessary-type-conversion\": \"error\",\n \"typescript/no-unnecessary-condition\": [\n \"error\",\n { allowConstantLoopConditions: \"only-allowed-literals\" },\n ],\n \"typescript/no-unnecessary-type-arguments\": \"error\",\n \"typescript/switch-exhaustiveness-check\": [\n \"error\",\n { considerDefaultExhaustiveForUnions: true },\n ],\n \"typescript/strict-boolean-expressions\": [\n \"error\",\n { allowNullableString: true, allowNullableBoolean: true },\n ],\n \"typescript/no-confusing-void-expression\": [\n \"error\",\n { ignoreArrowShorthand: true, ignoreVoidReturningFunctions: true },\n ],\n \"typescript/prefer-nullish-coalescing\": [\n \"error\",\n { ignorePrimitives: { string: true, boolean: true } },\n ],\n \"typescript/only-throw-error\": \"error\",\n \"typescript/return-await\": [\"error\", \"error-handling-correctness-only\"],\n\n \"unicorn/no-useless-undefined\": \"off\",\n \"unicorn/prefer-array-find\": \"error\",\n \"unicorn/prefer-at\": \"error\",\n \"unicorn/prefer-set-has\": \"error\",\n \"unicorn/filename-case\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"unicorn/prefer-ternary\": \"off\",\n \"unicorn/no-array-reduce\": \"error\",\n \"unicorn/no-nested-ternary\": \"off\",\n // `Array.from({ length }).fill(obj)` shares one reference across every slot.\n \"unicorn/no-array-fill-with-reference-type\": \"error\",\n\n \"import/no-cycle\": \"error\",\n \"import/consistent-type-specifier-style\": \"off\",\n // Keep a blank line after the import block so it stays visually separate from module body.\n \"import/newline-after-import\": \"error\",\n\n \"promise/always-return\": \"error\",\n \"promise/no-return-in-finally\": \"error\",\n \"promise/prefer-await-to-then\": \"off\",\n \"promise/prefer-await-to-callbacks\": \"off\",\n \"promise/avoid-new\": \"off\",\n\n // Enabling the `react` plugin (above) turns on its whole correctness-category\n // rule set at \"warn\" by default, not just react-compiler. Only react-compiler\n // is a deliberate addition here, so the rest are turned off to keep this\n // package's blast radius scoped to the one rule being added; lift any of\n // these independently if a future change wants them.\n \"react/exhaustive-deps\": \"off\",\n \"react/forward-ref-uses-ref\": \"off\",\n \"react/jsx-key\": \"off\",\n \"react/jsx-no-duplicate-props\": \"off\",\n \"react/jsx-no-undef\": \"off\",\n \"react/jsx-props-no-spread-multi\": \"off\",\n \"react/no-children-prop\": \"off\",\n \"react/no-danger-with-children\": \"off\",\n \"react/no-did-mount-set-state\": \"off\",\n \"react/no-did-update-set-state\": \"off\",\n \"react/no-direct-mutation-state\": \"off\",\n \"react/no-find-dom-node\": \"off\",\n \"react/no-is-mounted\": \"off\",\n \"react/no-render-return-value\": \"off\",\n \"react/no-string-refs\": \"off\",\n \"react/no-this-in-sfc\": \"off\",\n \"react/no-unsafe\": \"off\",\n \"react/no-will-update-set-state\": \"off\",\n \"react/void-dom-elements-no-children\": \"off\",\n // Requires oxlint >= 1.70. Nursery rule upstream: flags violations of the\n // Rules of React that block compiler memoization; diagnostics may shift\n // between oxlint minors, so re-audit findings after bumping the oxlint\n // devDependency. oxlint has no bulk-suppression/baseline mechanism as of\n // 1.72.0 (oxc-project/oxc#10549 tracks the upstream feature request and is\n // still open); consumers adopting this against an existing findings\n // backlog should carve out temporary `overrides` entries per legacy path\n // (or per rule, at `\"off\"`) and fix forward, rather than expecting a\n // generated suppression file.\n \"react/react-compiler\": \"error\",\n\n \"sort-keys\": \"off\",\n \"no-plusplus\": \"off\",\n \"no-inline-comments\": \"off\",\n \"max-statements\": \"off\",\n \"prefer-destructuring\": \"off\",\n \"no-negated-condition\": \"off\",\n \"no-use-before-define\": \"off\",\n \"no-useless-return\": \"off\",\n \"no-warning-comments\": \"off\",\n \"no-unexpected-multiline\": \"off\",\n \"max-classes-per-file\": \"off\",\n \"class-methods-use-this\": \"off\",\n \"no-unmodified-loop-condition\": \"off\",\n complexity: \"off\",\n \"func-style\": \"off\",\n \"func-names\": \"off\",\n \"default-case\": \"off\",\n} satisfies Rules;\n\nexport const libraryOverrides = [\n {\n files: [\"**/*.{test,spec}.{ts,tsx,js,jsx}\", \"__test__/**\", \"__tests__/**\"],\n rules: {\n \"no-console\": \"off\",\n \"require-await\": \"off\",\n \"no-non-null-assertion\": \"off\",\n },\n },\n] satisfies OxlintOverride[];\n\nexport const library = (options: LibraryOptions = {}): OxlintConfig =>\n defineConfig({\n options: {\n denyWarnings: true,\n reportUnusedDisableDirectives: \"error\",\n typeAware: true,\n ...options.options,\n },\n plugins: Array.from(\n new Set([...libraryPlugins, ...(options.plugins ?? [])]),\n ),\n jsPlugins: [\n stellaLowercasePluginSpecifier,\n noRawColorsPluginSpecifier,\n ...(options.jsPlugins ?? []),\n ],\n ignorePatterns: [\n ...libraryIgnorePatterns,\n ...(options.ignorePatterns ?? []),\n ],\n rules: {\n ...libraryRules,\n ...options.rules,\n },\n overrides: [...libraryOverrides, ...(options.overrides ?? [])],\n });\n\nexport default library();\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.cjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { defineConfig, type OxlintConfig, type OxlintOverride } from \"oxlint\";\n\ntype Rules = NonNullable<OxlintConfig[\"rules\"]>;\ntype JsPlugins = NonNullable<OxlintConfig[\"jsPlugins\"]>;\ntype Options = NonNullable<OxlintConfig[\"options\"]>;\ntype Plugins = NonNullable<OxlintConfig[\"plugins\"]>;\n\nexport type LibraryOptions = {\n jsPlugins?: JsPlugins;\n ignorePatterns?: string[];\n options?: Options;\n overrides?: OxlintOverride[];\n plugins?: Plugins;\n rules?: Rules;\n};\n\nexport const stellaLowercasePluginSpecifier = \"@stll/oxlint-config/plugin\";\nexport const noRawColorsPluginSpecifier = \"@stll/oxlint-config/no-raw-colors\";\n\nexport const libraryIgnorePatterns = [\"node_modules/\", \"dist/\", \"coverage/\"];\n\n// Setting oxlint's `plugins` config replaces its built-in default list rather\n// than appending to it. This must stay in sync with every plugin-prefixed\n// rule below: `import/*` and `promise/*` rules are configured further down,\n// but oxlint disables both plugins unless they are listed here, which left\n// `import/no-cycle` and every `promise/*` rule silently unenforced. `react`\n// is enabled for `react/react-compiler`; see the comment on that rule for\n// why every other `react/*` rule is explicitly turned back off below.\nexport const libraryPlugins = [\n \"eslint\",\n \"typescript\",\n \"unicorn\",\n \"oxc\",\n \"import\",\n \"promise\",\n \"react\",\n] satisfies Plugins;\n\nexport const libraryRules = {\n \"no-console\": \"off\",\n \"no-shadow\": \"error\",\n \"require-await\": \"error\",\n \"no-useless-catch\": \"error\",\n \"no-non-null-assertion\": \"error\",\n \"no-useless-assignment\": \"error\",\n \"no-loop-func\": \"error\",\n \"no-nested-ternary\": \"error\",\n \"no-void\": [\"error\", { allowAsStatement: true }],\n // Callback expressions should stay lexically scoped; avoids accidental `this` rebinding.\n \"prefer-arrow-callback\": \"error\",\n \"stella-lowercase/stella-lowercase\": \"error\",\n \"no-raw-colors/no-raw-colors\": \"error\",\n\n // Spreading into an accumulator on every loop iteration is O(n^2); push/concat instead.\n \"oxc/no-accumulating-spread\": \"error\",\n // Spreading inside `.map()` allocates a new array per callback invocation; push instead.\n \"oxc/no-map-spread\": \"error\",\n // Flags `if`/`switch` branches with identical bodies: likely dead logic or a missed merge.\n \"oxc/branches-sharing-code\": \"error\",\n\n \"typescript/no-explicit-any\": \"error\",\n \"typescript/no-dynamic-delete\": \"error\",\n \"typescript/no-misused-promises\": [\n \"error\",\n { checksVoidReturn: { attributes: false } },\n ],\n \"typescript/consistent-type-definitions\": [\"error\", \"type\"],\n \"typescript/consistent-return\": \"error\",\n \"typescript/dot-notation\": \"error\",\n \"typescript/no-inferrable-types\": \"off\",\n \"typescript/no-unnecessary-type-conversion\": \"error\",\n \"typescript/no-unnecessary-condition\": [\n \"error\",\n { allowConstantLoopConditions: \"only-allowed-literals\" },\n ],\n \"typescript/no-unnecessary-type-arguments\": \"error\",\n \"typescript/switch-exhaustiveness-check\": [\n \"error\",\n { considerDefaultExhaustiveForUnions: true },\n ],\n \"typescript/strict-boolean-expressions\": [\n \"error\",\n { allowNullableString: true, allowNullableBoolean: true },\n ],\n \"typescript/no-confusing-void-expression\": [\n \"error\",\n { ignoreArrowShorthand: true, ignoreVoidReturningFunctions: true },\n ],\n \"typescript/prefer-nullish-coalescing\": [\n \"error\",\n { ignorePrimitives: { string: true, boolean: true } },\n ],\n \"typescript/only-throw-error\": \"error\",\n \"typescript/return-await\": [\"error\", \"error-handling-correctness-only\"],\n\n \"unicorn/no-useless-undefined\": \"off\",\n \"unicorn/prefer-array-find\": \"error\",\n \"unicorn/prefer-at\": \"error\",\n \"unicorn/prefer-set-has\": \"error\",\n \"unicorn/filename-case\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"unicorn/prefer-ternary\": \"off\",\n \"unicorn/no-array-reduce\": \"error\",\n \"unicorn/no-nested-ternary\": \"off\",\n // `Array.from({ length }).fill(obj)` shares one reference across every slot.\n \"unicorn/no-array-fill-with-reference-type\": \"error\",\n\n \"import/no-cycle\": \"error\",\n \"import/consistent-type-specifier-style\": \"off\",\n // Keep a blank line after the import block so it stays visually separate from module body.\n \"import/newline-after-import\": \"error\",\n\n \"promise/always-return\": \"error\",\n \"promise/no-return-in-finally\": \"error\",\n \"promise/prefer-await-to-then\": \"off\",\n \"promise/prefer-await-to-callbacks\": \"off\",\n \"promise/avoid-new\": \"off\",\n\n // Enabling the `react` plugin (above) turns on its whole correctness-category\n // rule set at \"warn\" by default, not just react-compiler. Only react-compiler\n // is a deliberate addition here, so the rest are turned off to keep this\n // package's blast radius scoped to the one rule being added; lift any of\n // these independently if a future change wants them.\n \"react/exhaustive-deps\": \"off\",\n \"react/forward-ref-uses-ref\": \"off\",\n \"react/jsx-key\": \"off\",\n \"react/jsx-no-duplicate-props\": \"off\",\n \"react/jsx-no-undef\": \"off\",\n \"react/jsx-props-no-spread-multi\": \"off\",\n \"react/no-children-prop\": \"off\",\n \"react/no-danger-with-children\": \"off\",\n \"react/no-did-mount-set-state\": \"off\",\n \"react/no-did-update-set-state\": \"off\",\n \"react/no-direct-mutation-state\": \"off\",\n \"react/no-find-dom-node\": \"off\",\n \"react/no-is-mounted\": \"off\",\n \"react/no-render-return-value\": \"off\",\n \"react/no-string-refs\": \"off\",\n \"react/no-this-in-sfc\": \"off\",\n \"react/no-unsafe\": \"off\",\n \"react/no-will-update-set-state\": \"off\",\n \"react/void-dom-elements-no-children\": \"off\",\n // Requires oxlint >= 1.70. Nursery rule upstream: flags violations of the\n // Rules of React that block compiler memoization; diagnostics may shift\n // between oxlint minors, so re-audit findings after bumping the oxlint\n // devDependency. oxlint has no bulk-suppression/baseline mechanism as of\n // 1.72.0 (oxc-project/oxc#10549 tracks the upstream feature request and is\n // still open); consumers adopting this against an existing findings\n // backlog should carve out temporary `overrides` entries per legacy path\n // (or per rule, at `\"off\"`) and fix forward, rather than expecting a\n // generated suppression file.\n \"react/react-compiler\": \"error\",\n\n \"sort-keys\": \"off\",\n \"no-plusplus\": \"off\",\n \"no-inline-comments\": \"off\",\n \"max-statements\": \"off\",\n \"prefer-destructuring\": \"off\",\n \"no-negated-condition\": \"off\",\n \"no-use-before-define\": \"off\",\n \"no-useless-return\": \"off\",\n \"no-warning-comments\": \"off\",\n \"no-unexpected-multiline\": \"off\",\n \"max-classes-per-file\": \"off\",\n \"class-methods-use-this\": \"off\",\n \"no-unmodified-loop-condition\": \"off\",\n complexity: \"off\",\n \"func-style\": \"off\",\n \"func-names\": \"off\",\n \"default-case\": \"off\",\n} satisfies Rules;\n\nexport const libraryOverrides = [\n {\n files: [\"**/*.{test,spec}.{ts,tsx,js,jsx}\", \"__test__/**\", \"__tests__/**\"],\n rules: {\n \"no-console\": \"off\",\n \"require-await\": \"off\",\n \"no-non-null-assertion\": \"off\",\n },\n },\n] satisfies OxlintOverride[];\n\nexport const library = (options: LibraryOptions = {}): OxlintConfig =>\n defineConfig({\n options: {\n denyWarnings: true,\n reportUnusedDisableDirectives: \"error\",\n typeAware: true,\n ...options.options,\n },\n plugins: Array.from(\n new Set([...libraryPlugins, ...(options.plugins ?? [])]),\n ),\n jsPlugins: [\n stellaLowercasePluginSpecifier,\n noRawColorsPluginSpecifier,\n ...(options.jsPlugins ?? []),\n ],\n ignorePatterns: [\n ...libraryIgnorePatterns,\n ...(options.ignorePatterns ?? []),\n ],\n rules: {\n ...libraryRules,\n ...options.rules,\n },\n overrides: [...libraryOverrides, ...(options.overrides ?? [])],\n });\n\nexport default library();\n"],"mappings":"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"}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { OxlintConfig, OxlintOverride } from "oxlint";
|
|
2
|
-
|
|
3
2
|
//#region src/index.d.ts
|
|
4
3
|
type Rules = NonNullable<OxlintConfig["rules"]>;
|
|
5
4
|
type JsPlugins = NonNullable<OxlintConfig["jsPlugins"]>;
|
|
@@ -16,7 +15,7 @@ type LibraryOptions = {
|
|
|
16
15
|
declare const stellaLowercasePluginSpecifier = "@stll/oxlint-config/plugin";
|
|
17
16
|
declare const noRawColorsPluginSpecifier = "@stll/oxlint-config/no-raw-colors";
|
|
18
17
|
declare const libraryIgnorePatterns: string[];
|
|
19
|
-
declare const libraryPlugins: ("eslint" | "
|
|
18
|
+
declare const libraryPlugins: ("eslint" | "import" | "oxc" | "promise" | "react" | "typescript" | "unicorn")[];
|
|
20
19
|
declare const libraryRules: {
|
|
21
20
|
"no-console": "off";
|
|
22
21
|
"no-shadow": "error";
|
package/dist/index.d.mts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { OxlintConfig, OxlintOverride } from "oxlint";
|
|
2
|
-
|
|
3
2
|
//#region src/index.d.ts
|
|
4
3
|
type Rules = NonNullable<OxlintConfig["rules"]>;
|
|
5
4
|
type JsPlugins = NonNullable<OxlintConfig["jsPlugins"]>;
|
|
@@ -16,7 +15,7 @@ type LibraryOptions = {
|
|
|
16
15
|
declare const stellaLowercasePluginSpecifier = "@stll/oxlint-config/plugin";
|
|
17
16
|
declare const noRawColorsPluginSpecifier = "@stll/oxlint-config/no-raw-colors";
|
|
18
17
|
declare const libraryIgnorePatterns: string[];
|
|
19
|
-
declare const libraryPlugins: ("eslint" | "
|
|
18
|
+
declare const libraryPlugins: ("eslint" | "import" | "oxc" | "promise" | "react" | "typescript" | "unicorn")[];
|
|
20
19
|
declare const libraryRules: {
|
|
21
20
|
"no-console": "off";
|
|
22
21
|
"no-shadow": "error";
|
package/dist/index.mjs
CHANGED
|
@@ -132,7 +132,7 @@ const library = (options = {}) => defineConfig({
|
|
|
132
132
|
typeAware: true,
|
|
133
133
|
...options.options
|
|
134
134
|
},
|
|
135
|
-
plugins: Array.from(new Set([...libraryPlugins, ...options.plugins ?? []])),
|
|
135
|
+
plugins: Array.from(/* @__PURE__ */ new Set([...libraryPlugins, ...options.plugins ?? []])),
|
|
136
136
|
jsPlugins: [
|
|
137
137
|
stellaLowercasePluginSpecifier,
|
|
138
138
|
noRawColorsPluginSpecifier,
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { defineConfig, type OxlintConfig, type OxlintOverride } from \"oxlint\";\n\ntype Rules = NonNullable<OxlintConfig[\"rules\"]>;\ntype JsPlugins = NonNullable<OxlintConfig[\"jsPlugins\"]>;\ntype Options = NonNullable<OxlintConfig[\"options\"]>;\ntype Plugins = NonNullable<OxlintConfig[\"plugins\"]>;\n\nexport type LibraryOptions = {\n jsPlugins?: JsPlugins;\n ignorePatterns?: string[];\n options?: Options;\n overrides?: OxlintOverride[];\n plugins?: Plugins;\n rules?: Rules;\n};\n\nexport const stellaLowercasePluginSpecifier = \"@stll/oxlint-config/plugin\";\nexport const noRawColorsPluginSpecifier = \"@stll/oxlint-config/no-raw-colors\";\n\nexport const libraryIgnorePatterns = [\"node_modules/\", \"dist/\", \"coverage/\"];\n\n// Setting oxlint's `plugins` config replaces its built-in default list rather\n// than appending to it. This must stay in sync with every plugin-prefixed\n// rule below: `import/*` and `promise/*` rules are configured further down,\n// but oxlint disables both plugins unless they are listed here, which left\n// `import/no-cycle` and every `promise/*` rule silently unenforced. `react`\n// is enabled for `react/react-compiler`; see the comment on that rule for\n// why every other `react/*` rule is explicitly turned back off below.\nexport const libraryPlugins = [\n \"eslint\",\n \"typescript\",\n \"unicorn\",\n \"oxc\",\n \"import\",\n \"promise\",\n \"react\",\n] satisfies Plugins;\n\nexport const libraryRules = {\n \"no-console\": \"off\",\n \"no-shadow\": \"error\",\n \"require-await\": \"error\",\n \"no-useless-catch\": \"error\",\n \"no-non-null-assertion\": \"error\",\n \"no-useless-assignment\": \"error\",\n \"no-loop-func\": \"error\",\n \"no-nested-ternary\": \"error\",\n \"no-void\": [\"error\", { allowAsStatement: true }],\n // Callback expressions should stay lexically scoped; avoids accidental `this` rebinding.\n \"prefer-arrow-callback\": \"error\",\n \"stella-lowercase/stella-lowercase\": \"error\",\n \"no-raw-colors/no-raw-colors\": \"error\",\n\n // Spreading into an accumulator on every loop iteration is O(n^2); push/concat instead.\n \"oxc/no-accumulating-spread\": \"error\",\n // Spreading inside `.map()` allocates a new array per callback invocation; push instead.\n \"oxc/no-map-spread\": \"error\",\n // Flags `if`/`switch` branches with identical bodies: likely dead logic or a missed merge.\n \"oxc/branches-sharing-code\": \"error\",\n\n \"typescript/no-explicit-any\": \"error\",\n \"typescript/no-dynamic-delete\": \"error\",\n \"typescript/no-misused-promises\": [\n \"error\",\n { checksVoidReturn: { attributes: false } },\n ],\n \"typescript/consistent-type-definitions\": [\"error\", \"type\"],\n \"typescript/consistent-return\": \"error\",\n \"typescript/dot-notation\": \"error\",\n \"typescript/no-inferrable-types\": \"off\",\n \"typescript/no-unnecessary-type-conversion\": \"error\",\n \"typescript/no-unnecessary-condition\": [\n \"error\",\n { allowConstantLoopConditions: \"only-allowed-literals\" },\n ],\n \"typescript/no-unnecessary-type-arguments\": \"error\",\n \"typescript/switch-exhaustiveness-check\": [\n \"error\",\n { considerDefaultExhaustiveForUnions: true },\n ],\n \"typescript/strict-boolean-expressions\": [\n \"error\",\n { allowNullableString: true, allowNullableBoolean: true },\n ],\n \"typescript/no-confusing-void-expression\": [\n \"error\",\n { ignoreArrowShorthand: true, ignoreVoidReturningFunctions: true },\n ],\n \"typescript/prefer-nullish-coalescing\": [\n \"error\",\n { ignorePrimitives: { string: true, boolean: true } },\n ],\n \"typescript/only-throw-error\": \"error\",\n \"typescript/return-await\": [\"error\", \"error-handling-correctness-only\"],\n\n \"unicorn/no-useless-undefined\": \"off\",\n \"unicorn/prefer-array-find\": \"error\",\n \"unicorn/prefer-at\": \"error\",\n \"unicorn/prefer-set-has\": \"error\",\n \"unicorn/filename-case\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"unicorn/prefer-ternary\": \"off\",\n \"unicorn/no-array-reduce\": \"error\",\n \"unicorn/no-nested-ternary\": \"off\",\n // `Array.from({ length }).fill(obj)` shares one reference across every slot.\n \"unicorn/no-array-fill-with-reference-type\": \"error\",\n\n \"import/no-cycle\": \"error\",\n \"import/consistent-type-specifier-style\": \"off\",\n // Keep a blank line after the import block so it stays visually separate from module body.\n \"import/newline-after-import\": \"error\",\n\n \"promise/always-return\": \"error\",\n \"promise/no-return-in-finally\": \"error\",\n \"promise/prefer-await-to-then\": \"off\",\n \"promise/prefer-await-to-callbacks\": \"off\",\n \"promise/avoid-new\": \"off\",\n\n // Enabling the `react` plugin (above) turns on its whole correctness-category\n // rule set at \"warn\" by default, not just react-compiler. Only react-compiler\n // is a deliberate addition here, so the rest are turned off to keep this\n // package's blast radius scoped to the one rule being added; lift any of\n // these independently if a future change wants them.\n \"react/exhaustive-deps\": \"off\",\n \"react/forward-ref-uses-ref\": \"off\",\n \"react/jsx-key\": \"off\",\n \"react/jsx-no-duplicate-props\": \"off\",\n \"react/jsx-no-undef\": \"off\",\n \"react/jsx-props-no-spread-multi\": \"off\",\n \"react/no-children-prop\": \"off\",\n \"react/no-danger-with-children\": \"off\",\n \"react/no-did-mount-set-state\": \"off\",\n \"react/no-did-update-set-state\": \"off\",\n \"react/no-direct-mutation-state\": \"off\",\n \"react/no-find-dom-node\": \"off\",\n \"react/no-is-mounted\": \"off\",\n \"react/no-render-return-value\": \"off\",\n \"react/no-string-refs\": \"off\",\n \"react/no-this-in-sfc\": \"off\",\n \"react/no-unsafe\": \"off\",\n \"react/no-will-update-set-state\": \"off\",\n \"react/void-dom-elements-no-children\": \"off\",\n // Requires oxlint >= 1.70. Nursery rule upstream: flags violations of the\n // Rules of React that block compiler memoization; diagnostics may shift\n // between oxlint minors, so re-audit findings after bumping the oxlint\n // devDependency. oxlint has no bulk-suppression/baseline mechanism as of\n // 1.72.0 (oxc-project/oxc#10549 tracks the upstream feature request and is\n // still open); consumers adopting this against an existing findings\n // backlog should carve out temporary `overrides` entries per legacy path\n // (or per rule, at `\"off\"`) and fix forward, rather than expecting a\n // generated suppression file.\n \"react/react-compiler\": \"error\",\n\n \"sort-keys\": \"off\",\n \"no-plusplus\": \"off\",\n \"no-inline-comments\": \"off\",\n \"max-statements\": \"off\",\n \"prefer-destructuring\": \"off\",\n \"no-negated-condition\": \"off\",\n \"no-use-before-define\": \"off\",\n \"no-useless-return\": \"off\",\n \"no-warning-comments\": \"off\",\n \"no-unexpected-multiline\": \"off\",\n \"max-classes-per-file\": \"off\",\n \"class-methods-use-this\": \"off\",\n \"no-unmodified-loop-condition\": \"off\",\n complexity: \"off\",\n \"func-style\": \"off\",\n \"func-names\": \"off\",\n \"default-case\": \"off\",\n} satisfies Rules;\n\nexport const libraryOverrides = [\n {\n files: [\"**/*.{test,spec}.{ts,tsx,js,jsx}\", \"__test__/**\", \"__tests__/**\"],\n rules: {\n \"no-console\": \"off\",\n \"require-await\": \"off\",\n \"no-non-null-assertion\": \"off\",\n },\n },\n] satisfies OxlintOverride[];\n\nexport const library = (options: LibraryOptions = {}): OxlintConfig =>\n defineConfig({\n options: {\n denyWarnings: true,\n reportUnusedDisableDirectives: \"error\",\n typeAware: true,\n ...options.options,\n },\n plugins: Array.from(\n new Set([...libraryPlugins, ...(options.plugins ?? [])]),\n ),\n jsPlugins: [\n stellaLowercasePluginSpecifier,\n noRawColorsPluginSpecifier,\n ...(options.jsPlugins ?? []),\n ],\n ignorePatterns: [\n ...libraryIgnorePatterns,\n ...(options.ignorePatterns ?? []),\n ],\n rules: {\n ...libraryRules,\n ...options.rules,\n },\n overrides: [...libraryOverrides, ...(options.overrides ?? [])],\n });\n\nexport default library();\n"],"mappings":";;AAgBA,MAAa,iCAAiC;AAC9C,MAAa,6BAA6B;AAE1C,MAAa,wBAAwB;CAAC;CAAiB;CAAS;AAAW;AAS3E,MAAa,iBAAiB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAEA,MAAa,eAAe;CAC1B,cAAc;CACd,aAAa;CACb,iBAAiB;CACjB,oBAAoB;CACpB,yBAAyB;CACzB,yBAAyB;CACzB,gBAAgB;CAChB,qBAAqB;CACrB,WAAW,CAAC,SAAS,EAAE,kBAAkB,KAAK,CAAC;CAE/C,yBAAyB;CACzB,qCAAqC;CACrC,+BAA+B;CAG/B,8BAA8B;CAE9B,qBAAqB;CAErB,6BAA6B;CAE7B,8BAA8B;CAC9B,gCAAgC;CAChC,kCAAkC,CAChC,SACA,EAAE,kBAAkB,EAAE,YAAY,MAAM,EAAE,CAC5C;CACA,0CAA0C,CAAC,SAAS,MAAM;CAC1D,gCAAgC;CAChC,2BAA2B;CAC3B,kCAAkC;CAClC,6CAA6C;CAC7C,uCAAuC,CACrC,SACA,EAAE,6BAA6B,wBAAwB,CACzD;CACA,4CAA4C;CAC5C,0CAA0C,CACxC,SACA,EAAE,oCAAoC,KAAK,CAC7C;CACA,yCAAyC,CACvC,SACA;EAAE,qBAAqB;EAAM,sBAAsB;CAAK,CAC1D;CACA,2CAA2C,CACzC,SACA;EAAE,sBAAsB;EAAM,8BAA8B;CAAK,CACnE;CACA,wCAAwC,CACtC,SACA,EAAE,kBAAkB;EAAE,QAAQ;EAAM,SAAS;CAAK,EAAE,CACtD;CACA,+BAA+B;CAC/B,2BAA2B,CAAC,SAAS,iCAAiC;CAEtE,gCAAgC;CAChC,6BAA6B;CAC7B,qBAAqB;CACrB,0BAA0B;CAC1B,yBAAyB;CACzB,uCAAuC;CACvC,0BAA0B;CAC1B,2BAA2B;CAC3B,6BAA6B;CAE7B,6CAA6C;CAE7C,mBAAmB;CACnB,0CAA0C;CAE1C,+BAA+B;CAE/B,yBAAyB;CACzB,gCAAgC;CAChC,gCAAgC;CAChC,qCAAqC;CACrC,qBAAqB;CAOrB,yBAAyB;CACzB,8BAA8B;CAC9B,iBAAiB;CACjB,gCAAgC;CAChC,sBAAsB;CACtB,mCAAmC;CACnC,0BAA0B;CAC1B,iCAAiC;CACjC,gCAAgC;CAChC,iCAAiC;CACjC,kCAAkC;CAClC,0BAA0B;CAC1B,uBAAuB;CACvB,gCAAgC;CAChC,wBAAwB;CACxB,wBAAwB;CACxB,mBAAmB;CACnB,kCAAkC;CAClC,uCAAuC;CAUvC,wBAAwB;CAExB,aAAa;CACb,eAAe;CACf,sBAAsB;CACtB,kBAAkB;CAClB,wBAAwB;CACxB,wBAAwB;CACxB,wBAAwB;CACxB,qBAAqB;CACrB,uBAAuB;CACvB,2BAA2B;CAC3B,wBAAwB;CACxB,0BAA0B;CAC1B,gCAAgC;CAChC,YAAY;CACZ,cAAc;CACd,cAAc;CACd,gBAAgB;AAClB;AAEA,MAAa,mBAAmB,CAC9B;CACE,OAAO;EAAC;EAAoC;EAAe;CAAc;CACzE,OAAO;EACL,cAAc;EACd,iBAAiB;EACjB,yBAAyB;CAC3B;AACF,CACF;AAEA,MAAa,WAAW,UAA0B,CAAC,MACjD,aAAa;CACX,SAAS;EACP,cAAc;EACd,+BAA+B;EAC/B,WAAW;EACX,GAAG,QAAQ;CACb;CACA,SAAS,MAAM,KACb,IAAI,IAAI,CAAC,GAAG,gBAAgB,GAAI,QAAQ,WAAW,CAAC,CAAE,CAAC,CACzD;CACA,WAAW;EACT;EACA;EACA,GAAI,QAAQ,aAAa,CAAC;CAC5B;CACA,gBAAgB,CACd,GAAG,uBACH,GAAI,QAAQ,kBAAkB,CAAC,CACjC;CACA,OAAO;EACL,GAAG;EACH,GAAG,QAAQ;CACb;CACA,WAAW,CAAC,GAAG,kBAAkB,GAAI,QAAQ,aAAa,CAAC,CAAE;AAC/D,CAAC;AAEH,IAAA,cAAe,QAAQ"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../src/index.ts"],"sourcesContent":["import { defineConfig, type OxlintConfig, type OxlintOverride } from \"oxlint\";\n\ntype Rules = NonNullable<OxlintConfig[\"rules\"]>;\ntype JsPlugins = NonNullable<OxlintConfig[\"jsPlugins\"]>;\ntype Options = NonNullable<OxlintConfig[\"options\"]>;\ntype Plugins = NonNullable<OxlintConfig[\"plugins\"]>;\n\nexport type LibraryOptions = {\n jsPlugins?: JsPlugins;\n ignorePatterns?: string[];\n options?: Options;\n overrides?: OxlintOverride[];\n plugins?: Plugins;\n rules?: Rules;\n};\n\nexport const stellaLowercasePluginSpecifier = \"@stll/oxlint-config/plugin\";\nexport const noRawColorsPluginSpecifier = \"@stll/oxlint-config/no-raw-colors\";\n\nexport const libraryIgnorePatterns = [\"node_modules/\", \"dist/\", \"coverage/\"];\n\n// Setting oxlint's `plugins` config replaces its built-in default list rather\n// than appending to it. This must stay in sync with every plugin-prefixed\n// rule below: `import/*` and `promise/*` rules are configured further down,\n// but oxlint disables both plugins unless they are listed here, which left\n// `import/no-cycle` and every `promise/*` rule silently unenforced. `react`\n// is enabled for `react/react-compiler`; see the comment on that rule for\n// why every other `react/*` rule is explicitly turned back off below.\nexport const libraryPlugins = [\n \"eslint\",\n \"typescript\",\n \"unicorn\",\n \"oxc\",\n \"import\",\n \"promise\",\n \"react\",\n] satisfies Plugins;\n\nexport const libraryRules = {\n \"no-console\": \"off\",\n \"no-shadow\": \"error\",\n \"require-await\": \"error\",\n \"no-useless-catch\": \"error\",\n \"no-non-null-assertion\": \"error\",\n \"no-useless-assignment\": \"error\",\n \"no-loop-func\": \"error\",\n \"no-nested-ternary\": \"error\",\n \"no-void\": [\"error\", { allowAsStatement: true }],\n // Callback expressions should stay lexically scoped; avoids accidental `this` rebinding.\n \"prefer-arrow-callback\": \"error\",\n \"stella-lowercase/stella-lowercase\": \"error\",\n \"no-raw-colors/no-raw-colors\": \"error\",\n\n // Spreading into an accumulator on every loop iteration is O(n^2); push/concat instead.\n \"oxc/no-accumulating-spread\": \"error\",\n // Spreading inside `.map()` allocates a new array per callback invocation; push instead.\n \"oxc/no-map-spread\": \"error\",\n // Flags `if`/`switch` branches with identical bodies: likely dead logic or a missed merge.\n \"oxc/branches-sharing-code\": \"error\",\n\n \"typescript/no-explicit-any\": \"error\",\n \"typescript/no-dynamic-delete\": \"error\",\n \"typescript/no-misused-promises\": [\n \"error\",\n { checksVoidReturn: { attributes: false } },\n ],\n \"typescript/consistent-type-definitions\": [\"error\", \"type\"],\n \"typescript/consistent-return\": \"error\",\n \"typescript/dot-notation\": \"error\",\n \"typescript/no-inferrable-types\": \"off\",\n \"typescript/no-unnecessary-type-conversion\": \"error\",\n \"typescript/no-unnecessary-condition\": [\n \"error\",\n { allowConstantLoopConditions: \"only-allowed-literals\" },\n ],\n \"typescript/no-unnecessary-type-arguments\": \"error\",\n \"typescript/switch-exhaustiveness-check\": [\n \"error\",\n { considerDefaultExhaustiveForUnions: true },\n ],\n \"typescript/strict-boolean-expressions\": [\n \"error\",\n { allowNullableString: true, allowNullableBoolean: true },\n ],\n \"typescript/no-confusing-void-expression\": [\n \"error\",\n { ignoreArrowShorthand: true, ignoreVoidReturningFunctions: true },\n ],\n \"typescript/prefer-nullish-coalescing\": [\n \"error\",\n { ignorePrimitives: { string: true, boolean: true } },\n ],\n \"typescript/only-throw-error\": \"error\",\n \"typescript/return-await\": [\"error\", \"error-handling-correctness-only\"],\n\n \"unicorn/no-useless-undefined\": \"off\",\n \"unicorn/prefer-array-find\": \"error\",\n \"unicorn/prefer-at\": \"error\",\n \"unicorn/prefer-set-has\": \"error\",\n \"unicorn/filename-case\": \"off\",\n \"unicorn/consistent-function-scoping\": \"off\",\n \"unicorn/prefer-ternary\": \"off\",\n \"unicorn/no-array-reduce\": \"error\",\n \"unicorn/no-nested-ternary\": \"off\",\n // `Array.from({ length }).fill(obj)` shares one reference across every slot.\n \"unicorn/no-array-fill-with-reference-type\": \"error\",\n\n \"import/no-cycle\": \"error\",\n \"import/consistent-type-specifier-style\": \"off\",\n // Keep a blank line after the import block so it stays visually separate from module body.\n \"import/newline-after-import\": \"error\",\n\n \"promise/always-return\": \"error\",\n \"promise/no-return-in-finally\": \"error\",\n \"promise/prefer-await-to-then\": \"off\",\n \"promise/prefer-await-to-callbacks\": \"off\",\n \"promise/avoid-new\": \"off\",\n\n // Enabling the `react` plugin (above) turns on its whole correctness-category\n // rule set at \"warn\" by default, not just react-compiler. Only react-compiler\n // is a deliberate addition here, so the rest are turned off to keep this\n // package's blast radius scoped to the one rule being added; lift any of\n // these independently if a future change wants them.\n \"react/exhaustive-deps\": \"off\",\n \"react/forward-ref-uses-ref\": \"off\",\n \"react/jsx-key\": \"off\",\n \"react/jsx-no-duplicate-props\": \"off\",\n \"react/jsx-no-undef\": \"off\",\n \"react/jsx-props-no-spread-multi\": \"off\",\n \"react/no-children-prop\": \"off\",\n \"react/no-danger-with-children\": \"off\",\n \"react/no-did-mount-set-state\": \"off\",\n \"react/no-did-update-set-state\": \"off\",\n \"react/no-direct-mutation-state\": \"off\",\n \"react/no-find-dom-node\": \"off\",\n \"react/no-is-mounted\": \"off\",\n \"react/no-render-return-value\": \"off\",\n \"react/no-string-refs\": \"off\",\n \"react/no-this-in-sfc\": \"off\",\n \"react/no-unsafe\": \"off\",\n \"react/no-will-update-set-state\": \"off\",\n \"react/void-dom-elements-no-children\": \"off\",\n // Requires oxlint >= 1.70. Nursery rule upstream: flags violations of the\n // Rules of React that block compiler memoization; diagnostics may shift\n // between oxlint minors, so re-audit findings after bumping the oxlint\n // devDependency. oxlint has no bulk-suppression/baseline mechanism as of\n // 1.72.0 (oxc-project/oxc#10549 tracks the upstream feature request and is\n // still open); consumers adopting this against an existing findings\n // backlog should carve out temporary `overrides` entries per legacy path\n // (or per rule, at `\"off\"`) and fix forward, rather than expecting a\n // generated suppression file.\n \"react/react-compiler\": \"error\",\n\n \"sort-keys\": \"off\",\n \"no-plusplus\": \"off\",\n \"no-inline-comments\": \"off\",\n \"max-statements\": \"off\",\n \"prefer-destructuring\": \"off\",\n \"no-negated-condition\": \"off\",\n \"no-use-before-define\": \"off\",\n \"no-useless-return\": \"off\",\n \"no-warning-comments\": \"off\",\n \"no-unexpected-multiline\": \"off\",\n \"max-classes-per-file\": \"off\",\n \"class-methods-use-this\": \"off\",\n \"no-unmodified-loop-condition\": \"off\",\n complexity: \"off\",\n \"func-style\": \"off\",\n \"func-names\": \"off\",\n \"default-case\": \"off\",\n} satisfies Rules;\n\nexport const libraryOverrides = [\n {\n files: [\"**/*.{test,spec}.{ts,tsx,js,jsx}\", \"__test__/**\", \"__tests__/**\"],\n rules: {\n \"no-console\": \"off\",\n \"require-await\": \"off\",\n \"no-non-null-assertion\": \"off\",\n },\n },\n] satisfies OxlintOverride[];\n\nexport const library = (options: LibraryOptions = {}): OxlintConfig =>\n defineConfig({\n options: {\n denyWarnings: true,\n reportUnusedDisableDirectives: \"error\",\n typeAware: true,\n ...options.options,\n },\n plugins: Array.from(\n new Set([...libraryPlugins, ...(options.plugins ?? [])]),\n ),\n jsPlugins: [\n stellaLowercasePluginSpecifier,\n noRawColorsPluginSpecifier,\n ...(options.jsPlugins ?? []),\n ],\n ignorePatterns: [\n ...libraryIgnorePatterns,\n ...(options.ignorePatterns ?? []),\n ],\n rules: {\n ...libraryRules,\n ...options.rules,\n },\n overrides: [...libraryOverrides, ...(options.overrides ?? [])],\n });\n\nexport default library();\n"],"mappings":"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"}
|
package/dist/no-raw-colors.cjs
CHANGED
|
@@ -92,7 +92,7 @@ const checkValue = (context, node, value) => {
|
|
|
92
92
|
data: { match: token }
|
|
93
93
|
});
|
|
94
94
|
};
|
|
95
|
-
const IMPORT_EXPORT_SOURCE_PARENT_TYPES = new Set([
|
|
95
|
+
const IMPORT_EXPORT_SOURCE_PARENT_TYPES = /* @__PURE__ */ new Set([
|
|
96
96
|
"ExportAllDeclaration",
|
|
97
97
|
"ExportNamedDeclaration",
|
|
98
98
|
"ImportDeclaration",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-raw-colors.cjs","names":[],"sources":["../src/no-raw-colors.ts"],"sourcesContent":["// Detect hardcoded Tailwind color classes that bypass semantic tokens.\n//\n// Semantic tokens (bg-background, bg-muted, text-foreground, bg-success/12, …)\n// adapt via CSS variables and stay legible across themes. Raw palette colors\n// (bg-stone-50, text-gray-900, bg-emerald-100, text-amber-800, bg-white, …)\n// either break dark mode (neutrals) or pin a component to a hue that ignores\n// the design system. Use the semantic equivalents instead.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"rawColor\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof node.type === \"string\";\n\nconst SHADED_PALETTE_COLORS = [\n \"red\",\n \"orange\",\n \"amber\",\n \"yellow\",\n \"lime\",\n \"green\",\n \"emerald\",\n \"teal\",\n \"cyan\",\n \"sky\",\n \"blue\",\n \"indigo\",\n \"violet\",\n \"purple\",\n \"fuchsia\",\n \"pink\",\n \"rose\",\n \"slate\",\n \"gray\",\n \"zinc\",\n \"neutral\",\n \"stone\",\n \"taupe\",\n \"mauve\",\n \"mist\",\n \"olive\",\n] as const;\nconst COLOR_SHADES = [\n \"50\",\n \"100\",\n \"200\",\n \"300\",\n \"400\",\n \"500\",\n \"600\",\n \"700\",\n \"800\",\n \"900\",\n \"950\",\n] as const;\nconst COLOR_UTILITY_PREFIXES = [\n \"accent\",\n \"bg\",\n \"border\",\n \"border-b\",\n \"border-e\",\n \"border-l\",\n \"border-r\",\n \"border-s\",\n \"border-t\",\n \"border-x\",\n \"border-y\",\n \"caret\",\n \"decoration\",\n \"divide\",\n \"drop-shadow\",\n \"fill\",\n \"from\",\n \"inset-ring\",\n \"inset-shadow\",\n \"marker\",\n \"outline\",\n \"placeholder\",\n \"ring\",\n \"ring-offset\",\n \"scrollbar-thumb\",\n \"scrollbar-track\",\n \"shadow\",\n \"stroke\",\n \"text\",\n \"text-shadow\",\n \"to\",\n \"via\",\n] as const;\nconst COLOR_PREFIXES = COLOR_UTILITY_PREFIXES.join(\"|\");\nconst SHADED_PALETTE_COLOR_PATTERN = SHADED_PALETTE_COLORS.join(\"|\");\nconst COLOR_SHADE_PATTERN = COLOR_SHADES.join(\"|\");\nconst CLASS_COLOR_BOUNDARY = \"(?:^|:)!?\";\n\nconst SHADED_PALETTE_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:${SHADED_PALETTE_COLOR_PATTERN})-(?:${COLOR_SHADE_PATTERN})(?![\\\\w-])`,\n);\n// Standalone white/black utilities; permit opacity (bg-white/20) and compounds\n// (bg-whitesmoke) so only the bare palette tokens fire.\nconst BW_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:white|black)(?![/\\\\w-])`,\n);\n\nconst isRawColorClass = (value: string): boolean =>\n SHADED_PALETTE_PATTERN.test(value) || BW_PATTERN.test(value);\n\nconst checkValue = (\n context: RuleContext,\n node: unknown,\n value: string,\n): void => {\n for (const token of value.split(/\\s+/)) {\n if (isRawColorClass(token)) {\n context.report({\n node,\n messageId: \"rawColor\",\n data: { match: token },\n });\n }\n }\n};\n\nconst IMPORT_EXPORT_SOURCE_PARENT_TYPES = new Set([\n \"ExportAllDeclaration\",\n \"ExportNamedDeclaration\",\n \"ImportDeclaration\",\n \"TSExternalModuleReference\",\n \"TSImportType\",\n]);\n\nconst isImportOrExportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n return (\n isAstNode(parent) && IMPORT_EXPORT_SOURCE_PARENT_TYPES.has(parent.type)\n );\n};\n\nconst isDynamicImportCall = (node: AstNode): boolean => {\n if (node.type === \"ImportExpression\") {\n return true;\n }\n if (node.type !== \"CallExpression\") {\n return false;\n }\n const callee = node[\"callee\"];\n return isAstNode(callee) && callee.type === \"Import\";\n};\n\nconst isDynamicImportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (isDynamicImportCall(parent)) {\n return true;\n }\n if (parent.type !== \"TemplateLiteral\") {\n return false;\n }\n const grandparent = parent.parent;\n return isAstNode(grandparent) && isDynamicImportCall(grandparent);\n};\n\nconst templateElementText = (node: AstNode): string | null => {\n const value = node[\"value\"];\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n const cooked = \"cooked\" in value ? value.cooked : null;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const raw = \"raw\" in value ? value.raw : null;\n return typeof raw === \"string\" ? raw : null;\n};\n\nexport default {\n meta: { name: \"no-raw-colors\" },\n rules: {\n \"no-raw-colors\": {\n meta: {\n type: \"problem\",\n messages: {\n rawColor:\n \"Hardcoded color '{{match}}' bypasses semantic tokens. Use semantic tokens (bg-muted, text-foreground, bg-success/12, bg-warning/12, bg-destructive/12, etc.) instead.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n if (isImportOrExportSource(node) || isDynamicImportSource(node)) {\n return;\n }\n checkValue(context, node, value);\n },\n TemplateElement(node: AstNode) {\n if (isDynamicImportSource(node)) {\n return;\n }\n const value = templateElementText(node);\n if (value === null) {\n return;\n }\n checkValue(context, node, value);\n },\n };\n },\n },\n },\n};\n"],"mappings":";;;;;AAkBA,MAAM,aAAa,SACjB,OAAO,SAAS,YAChB,SAAS,QACT,UAAU,QACV,OAAO,KAAK,SAAS;AAEvB,MAAM,wBAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AACA,MAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAmCA,MAAM,iBAAiB;CAjCrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAE0C,
|
|
1
|
+
{"version":3,"file":"no-raw-colors.cjs","names":[],"sources":["../src/no-raw-colors.ts"],"sourcesContent":["// Detect hardcoded Tailwind color classes that bypass semantic tokens.\n//\n// Semantic tokens (bg-background, bg-muted, text-foreground, bg-success/12, …)\n// adapt via CSS variables and stay legible across themes. Raw palette colors\n// (bg-stone-50, text-gray-900, bg-emerald-100, text-amber-800, bg-white, …)\n// either break dark mode (neutrals) or pin a component to a hue that ignores\n// the design system. Use the semantic equivalents instead.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"rawColor\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof node.type === \"string\";\n\nconst SHADED_PALETTE_COLORS = [\n \"red\",\n \"orange\",\n \"amber\",\n \"yellow\",\n \"lime\",\n \"green\",\n \"emerald\",\n \"teal\",\n \"cyan\",\n \"sky\",\n \"blue\",\n \"indigo\",\n \"violet\",\n \"purple\",\n \"fuchsia\",\n \"pink\",\n \"rose\",\n \"slate\",\n \"gray\",\n \"zinc\",\n \"neutral\",\n \"stone\",\n \"taupe\",\n \"mauve\",\n \"mist\",\n \"olive\",\n] as const;\nconst COLOR_SHADES = [\n \"50\",\n \"100\",\n \"200\",\n \"300\",\n \"400\",\n \"500\",\n \"600\",\n \"700\",\n \"800\",\n \"900\",\n \"950\",\n] as const;\nconst COLOR_UTILITY_PREFIXES = [\n \"accent\",\n \"bg\",\n \"border\",\n \"border-b\",\n \"border-e\",\n \"border-l\",\n \"border-r\",\n \"border-s\",\n \"border-t\",\n \"border-x\",\n \"border-y\",\n \"caret\",\n \"decoration\",\n \"divide\",\n \"drop-shadow\",\n \"fill\",\n \"from\",\n \"inset-ring\",\n \"inset-shadow\",\n \"marker\",\n \"outline\",\n \"placeholder\",\n \"ring\",\n \"ring-offset\",\n \"scrollbar-thumb\",\n \"scrollbar-track\",\n \"shadow\",\n \"stroke\",\n \"text\",\n \"text-shadow\",\n \"to\",\n \"via\",\n] as const;\nconst COLOR_PREFIXES = COLOR_UTILITY_PREFIXES.join(\"|\");\nconst SHADED_PALETTE_COLOR_PATTERN = SHADED_PALETTE_COLORS.join(\"|\");\nconst COLOR_SHADE_PATTERN = COLOR_SHADES.join(\"|\");\nconst CLASS_COLOR_BOUNDARY = \"(?:^|:)!?\";\n\nconst SHADED_PALETTE_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:${SHADED_PALETTE_COLOR_PATTERN})-(?:${COLOR_SHADE_PATTERN})(?![\\\\w-])`,\n);\n// Standalone white/black utilities; permit opacity (bg-white/20) and compounds\n// (bg-whitesmoke) so only the bare palette tokens fire.\nconst BW_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:white|black)(?![/\\\\w-])`,\n);\n\nconst isRawColorClass = (value: string): boolean =>\n SHADED_PALETTE_PATTERN.test(value) || BW_PATTERN.test(value);\n\nconst checkValue = (\n context: RuleContext,\n node: unknown,\n value: string,\n): void => {\n for (const token of value.split(/\\s+/)) {\n if (isRawColorClass(token)) {\n context.report({\n node,\n messageId: \"rawColor\",\n data: { match: token },\n });\n }\n }\n};\n\nconst IMPORT_EXPORT_SOURCE_PARENT_TYPES = new Set([\n \"ExportAllDeclaration\",\n \"ExportNamedDeclaration\",\n \"ImportDeclaration\",\n \"TSExternalModuleReference\",\n \"TSImportType\",\n]);\n\nconst isImportOrExportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n return (\n isAstNode(parent) && IMPORT_EXPORT_SOURCE_PARENT_TYPES.has(parent.type)\n );\n};\n\nconst isDynamicImportCall = (node: AstNode): boolean => {\n if (node.type === \"ImportExpression\") {\n return true;\n }\n if (node.type !== \"CallExpression\") {\n return false;\n }\n const callee = node[\"callee\"];\n return isAstNode(callee) && callee.type === \"Import\";\n};\n\nconst isDynamicImportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (isDynamicImportCall(parent)) {\n return true;\n }\n if (parent.type !== \"TemplateLiteral\") {\n return false;\n }\n const grandparent = parent.parent;\n return isAstNode(grandparent) && isDynamicImportCall(grandparent);\n};\n\nconst templateElementText = (node: AstNode): string | null => {\n const value = node[\"value\"];\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n const cooked = \"cooked\" in value ? value.cooked : null;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const raw = \"raw\" in value ? value.raw : null;\n return typeof raw === \"string\" ? raw : null;\n};\n\nexport default {\n meta: { name: \"no-raw-colors\" },\n rules: {\n \"no-raw-colors\": {\n meta: {\n type: \"problem\",\n messages: {\n rawColor:\n \"Hardcoded color '{{match}}' bypasses semantic tokens. Use semantic tokens (bg-muted, text-foreground, bg-success/12, bg-warning/12, bg-destructive/12, etc.) instead.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n if (isImportOrExportSource(node) || isDynamicImportSource(node)) {\n return;\n }\n checkValue(context, node, value);\n },\n TemplateElement(node: AstNode) {\n if (isDynamicImportSource(node)) {\n return;\n }\n const value = templateElementText(node);\n if (value === null) {\n return;\n }\n checkValue(context, node, value);\n },\n };\n },\n },\n },\n};\n"],"mappings":"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"}
|
package/dist/no-raw-colors.mjs
CHANGED
|
@@ -88,7 +88,7 @@ const checkValue = (context, node, value) => {
|
|
|
88
88
|
data: { match: token }
|
|
89
89
|
});
|
|
90
90
|
};
|
|
91
|
-
const IMPORT_EXPORT_SOURCE_PARENT_TYPES = new Set([
|
|
91
|
+
const IMPORT_EXPORT_SOURCE_PARENT_TYPES = /* @__PURE__ */ new Set([
|
|
92
92
|
"ExportAllDeclaration",
|
|
93
93
|
"ExportNamedDeclaration",
|
|
94
94
|
"ImportDeclaration",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"no-raw-colors.mjs","names":[],"sources":["../src/no-raw-colors.ts"],"sourcesContent":["// Detect hardcoded Tailwind color classes that bypass semantic tokens.\n//\n// Semantic tokens (bg-background, bg-muted, text-foreground, bg-success/12, …)\n// adapt via CSS variables and stay legible across themes. Raw palette colors\n// (bg-stone-50, text-gray-900, bg-emerald-100, text-amber-800, bg-white, …)\n// either break dark mode (neutrals) or pin a component to a hue that ignores\n// the design system. Use the semantic equivalents instead.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"rawColor\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof node.type === \"string\";\n\nconst SHADED_PALETTE_COLORS = [\n \"red\",\n \"orange\",\n \"amber\",\n \"yellow\",\n \"lime\",\n \"green\",\n \"emerald\",\n \"teal\",\n \"cyan\",\n \"sky\",\n \"blue\",\n \"indigo\",\n \"violet\",\n \"purple\",\n \"fuchsia\",\n \"pink\",\n \"rose\",\n \"slate\",\n \"gray\",\n \"zinc\",\n \"neutral\",\n \"stone\",\n \"taupe\",\n \"mauve\",\n \"mist\",\n \"olive\",\n] as const;\nconst COLOR_SHADES = [\n \"50\",\n \"100\",\n \"200\",\n \"300\",\n \"400\",\n \"500\",\n \"600\",\n \"700\",\n \"800\",\n \"900\",\n \"950\",\n] as const;\nconst COLOR_UTILITY_PREFIXES = [\n \"accent\",\n \"bg\",\n \"border\",\n \"border-b\",\n \"border-e\",\n \"border-l\",\n \"border-r\",\n \"border-s\",\n \"border-t\",\n \"border-x\",\n \"border-y\",\n \"caret\",\n \"decoration\",\n \"divide\",\n \"drop-shadow\",\n \"fill\",\n \"from\",\n \"inset-ring\",\n \"inset-shadow\",\n \"marker\",\n \"outline\",\n \"placeholder\",\n \"ring\",\n \"ring-offset\",\n \"scrollbar-thumb\",\n \"scrollbar-track\",\n \"shadow\",\n \"stroke\",\n \"text\",\n \"text-shadow\",\n \"to\",\n \"via\",\n] as const;\nconst COLOR_PREFIXES = COLOR_UTILITY_PREFIXES.join(\"|\");\nconst SHADED_PALETTE_COLOR_PATTERN = SHADED_PALETTE_COLORS.join(\"|\");\nconst COLOR_SHADE_PATTERN = COLOR_SHADES.join(\"|\");\nconst CLASS_COLOR_BOUNDARY = \"(?:^|:)!?\";\n\nconst SHADED_PALETTE_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:${SHADED_PALETTE_COLOR_PATTERN})-(?:${COLOR_SHADE_PATTERN})(?![\\\\w-])`,\n);\n// Standalone white/black utilities; permit opacity (bg-white/20) and compounds\n// (bg-whitesmoke) so only the bare palette tokens fire.\nconst BW_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:white|black)(?![/\\\\w-])`,\n);\n\nconst isRawColorClass = (value: string): boolean =>\n SHADED_PALETTE_PATTERN.test(value) || BW_PATTERN.test(value);\n\nconst checkValue = (\n context: RuleContext,\n node: unknown,\n value: string,\n): void => {\n for (const token of value.split(/\\s+/)) {\n if (isRawColorClass(token)) {\n context.report({\n node,\n messageId: \"rawColor\",\n data: { match: token },\n });\n }\n }\n};\n\nconst IMPORT_EXPORT_SOURCE_PARENT_TYPES = new Set([\n \"ExportAllDeclaration\",\n \"ExportNamedDeclaration\",\n \"ImportDeclaration\",\n \"TSExternalModuleReference\",\n \"TSImportType\",\n]);\n\nconst isImportOrExportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n return (\n isAstNode(parent) && IMPORT_EXPORT_SOURCE_PARENT_TYPES.has(parent.type)\n );\n};\n\nconst isDynamicImportCall = (node: AstNode): boolean => {\n if (node.type === \"ImportExpression\") {\n return true;\n }\n if (node.type !== \"CallExpression\") {\n return false;\n }\n const callee = node[\"callee\"];\n return isAstNode(callee) && callee.type === \"Import\";\n};\n\nconst isDynamicImportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (isDynamicImportCall(parent)) {\n return true;\n }\n if (parent.type !== \"TemplateLiteral\") {\n return false;\n }\n const grandparent = parent.parent;\n return isAstNode(grandparent) && isDynamicImportCall(grandparent);\n};\n\nconst templateElementText = (node: AstNode): string | null => {\n const value = node[\"value\"];\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n const cooked = \"cooked\" in value ? value.cooked : null;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const raw = \"raw\" in value ? value.raw : null;\n return typeof raw === \"string\" ? raw : null;\n};\n\nexport default {\n meta: { name: \"no-raw-colors\" },\n rules: {\n \"no-raw-colors\": {\n meta: {\n type: \"problem\",\n messages: {\n rawColor:\n \"Hardcoded color '{{match}}' bypasses semantic tokens. Use semantic tokens (bg-muted, text-foreground, bg-success/12, bg-warning/12, bg-destructive/12, etc.) instead.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n if (isImportOrExportSource(node) || isDynamicImportSource(node)) {\n return;\n }\n checkValue(context, node, value);\n },\n TemplateElement(node: AstNode) {\n if (isDynamicImportSource(node)) {\n return;\n }\n const value = templateElementText(node);\n if (value === null) {\n return;\n }\n checkValue(context, node, value);\n },\n };\n },\n },\n },\n};\n"],"mappings":";AAkBA,MAAM,aAAa,SACjB,OAAO,SAAS,YAChB,SAAS,QACT,UAAU,QACV,OAAO,KAAK,SAAS;AAEvB,MAAM,wBAAwB;CAC5B;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AACA,MAAM,eAAe;CACnB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AACF;AAmCA,MAAM,iBAAiB;CAjCrB;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;AAE0C,
|
|
1
|
+
{"version":3,"file":"no-raw-colors.mjs","names":[],"sources":["../src/no-raw-colors.ts"],"sourcesContent":["// Detect hardcoded Tailwind color classes that bypass semantic tokens.\n//\n// Semantic tokens (bg-background, bg-muted, text-foreground, bg-success/12, …)\n// adapt via CSS variables and stay legible across themes. Raw palette colors\n// (bg-stone-50, text-gray-900, bg-emerald-100, text-amber-800, bg-white, …)\n// either break dark mode (neutrals) or pin a component to a hue that ignores\n// the design system. Use the semantic equivalents instead.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"rawColor\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof node.type === \"string\";\n\nconst SHADED_PALETTE_COLORS = [\n \"red\",\n \"orange\",\n \"amber\",\n \"yellow\",\n \"lime\",\n \"green\",\n \"emerald\",\n \"teal\",\n \"cyan\",\n \"sky\",\n \"blue\",\n \"indigo\",\n \"violet\",\n \"purple\",\n \"fuchsia\",\n \"pink\",\n \"rose\",\n \"slate\",\n \"gray\",\n \"zinc\",\n \"neutral\",\n \"stone\",\n \"taupe\",\n \"mauve\",\n \"mist\",\n \"olive\",\n] as const;\nconst COLOR_SHADES = [\n \"50\",\n \"100\",\n \"200\",\n \"300\",\n \"400\",\n \"500\",\n \"600\",\n \"700\",\n \"800\",\n \"900\",\n \"950\",\n] as const;\nconst COLOR_UTILITY_PREFIXES = [\n \"accent\",\n \"bg\",\n \"border\",\n \"border-b\",\n \"border-e\",\n \"border-l\",\n \"border-r\",\n \"border-s\",\n \"border-t\",\n \"border-x\",\n \"border-y\",\n \"caret\",\n \"decoration\",\n \"divide\",\n \"drop-shadow\",\n \"fill\",\n \"from\",\n \"inset-ring\",\n \"inset-shadow\",\n \"marker\",\n \"outline\",\n \"placeholder\",\n \"ring\",\n \"ring-offset\",\n \"scrollbar-thumb\",\n \"scrollbar-track\",\n \"shadow\",\n \"stroke\",\n \"text\",\n \"text-shadow\",\n \"to\",\n \"via\",\n] as const;\nconst COLOR_PREFIXES = COLOR_UTILITY_PREFIXES.join(\"|\");\nconst SHADED_PALETTE_COLOR_PATTERN = SHADED_PALETTE_COLORS.join(\"|\");\nconst COLOR_SHADE_PATTERN = COLOR_SHADES.join(\"|\");\nconst CLASS_COLOR_BOUNDARY = \"(?:^|:)!?\";\n\nconst SHADED_PALETTE_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:${SHADED_PALETTE_COLOR_PATTERN})-(?:${COLOR_SHADE_PATTERN})(?![\\\\w-])`,\n);\n// Standalone white/black utilities; permit opacity (bg-white/20) and compounds\n// (bg-whitesmoke) so only the bare palette tokens fire.\nconst BW_PATTERN = new RegExp(\n `${CLASS_COLOR_BOUNDARY}(?:${COLOR_PREFIXES})-(?:white|black)(?![/\\\\w-])`,\n);\n\nconst isRawColorClass = (value: string): boolean =>\n SHADED_PALETTE_PATTERN.test(value) || BW_PATTERN.test(value);\n\nconst checkValue = (\n context: RuleContext,\n node: unknown,\n value: string,\n): void => {\n for (const token of value.split(/\\s+/)) {\n if (isRawColorClass(token)) {\n context.report({\n node,\n messageId: \"rawColor\",\n data: { match: token },\n });\n }\n }\n};\n\nconst IMPORT_EXPORT_SOURCE_PARENT_TYPES = new Set([\n \"ExportAllDeclaration\",\n \"ExportNamedDeclaration\",\n \"ImportDeclaration\",\n \"TSExternalModuleReference\",\n \"TSImportType\",\n]);\n\nconst isImportOrExportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n return (\n isAstNode(parent) && IMPORT_EXPORT_SOURCE_PARENT_TYPES.has(parent.type)\n );\n};\n\nconst isDynamicImportCall = (node: AstNode): boolean => {\n if (node.type === \"ImportExpression\") {\n return true;\n }\n if (node.type !== \"CallExpression\") {\n return false;\n }\n const callee = node[\"callee\"];\n return isAstNode(callee) && callee.type === \"Import\";\n};\n\nconst isDynamicImportSource = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (isDynamicImportCall(parent)) {\n return true;\n }\n if (parent.type !== \"TemplateLiteral\") {\n return false;\n }\n const grandparent = parent.parent;\n return isAstNode(grandparent) && isDynamicImportCall(grandparent);\n};\n\nconst templateElementText = (node: AstNode): string | null => {\n const value = node[\"value\"];\n if (typeof value !== \"object\" || value === null) {\n return null;\n }\n const cooked = \"cooked\" in value ? value.cooked : null;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const raw = \"raw\" in value ? value.raw : null;\n return typeof raw === \"string\" ? raw : null;\n};\n\nexport default {\n meta: { name: \"no-raw-colors\" },\n rules: {\n \"no-raw-colors\": {\n meta: {\n type: \"problem\",\n messages: {\n rawColor:\n \"Hardcoded color '{{match}}' bypasses semantic tokens. Use semantic tokens (bg-muted, text-foreground, bg-success/12, bg-warning/12, bg-destructive/12, etc.) instead.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n if (isImportOrExportSource(node) || isDynamicImportSource(node)) {\n return;\n }\n checkValue(context, node, value);\n },\n TemplateElement(node: AstNode) {\n if (isDynamicImportSource(node)) {\n return;\n }\n const value = templateElementText(node);\n if (value === null) {\n return;\n }\n checkValue(context, node, value);\n },\n };\n },\n },\n },\n};\n"],"mappings":"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"}
|
package/dist/plugin.cjs
CHANGED
|
@@ -7,12 +7,12 @@ const isAstNode = (node) => typeof node === "object" && node !== null && "type"
|
|
|
7
7
|
const isStringLiteral = (node) => isAstNode(node) && node.type === "Literal" && typeof node["value"] === "string";
|
|
8
8
|
const CAPITALIZED_WORDMARK_PATTERN = /\bStella\b/gu;
|
|
9
9
|
const WHOLE_PATH = /^(?=.*[/\\.:~@])[\w./\\:~@-]+$/u;
|
|
10
|
-
const SKIP_WHITESPACE = new Set([
|
|
10
|
+
const SKIP_WHITESPACE = /* @__PURE__ */ new Set([
|
|
11
11
|
" ",
|
|
12
12
|
" ",
|
|
13
13
|
"\xA0"
|
|
14
14
|
]);
|
|
15
|
-
const SKIP_OPENERS = new Set([
|
|
15
|
+
const SKIP_OPENERS = /* @__PURE__ */ new Set([
|
|
16
16
|
"\"",
|
|
17
17
|
"'",
|
|
18
18
|
"`",
|
|
@@ -31,7 +31,7 @@ const SKIP_OPENERS = new Set([
|
|
|
31
31
|
"¿",
|
|
32
32
|
"¡"
|
|
33
33
|
]);
|
|
34
|
-
const SENTENCE_TERMINATORS = new Set([
|
|
34
|
+
const SENTENCE_TERMINATORS = /* @__PURE__ */ new Set([
|
|
35
35
|
".",
|
|
36
36
|
"!",
|
|
37
37
|
"?",
|
package/dist/plugin.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.cjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["// Enforce the lowercase \"stella\" wordmark in source text.\n//\n// The stella brand identity is a lowercase wordmark. Generated copy and\n// hand-written prose can introduce a capitalized form mid-sentence, which\n// contradicts the wordmark. This rule scans string literals, template\n// literals, and JSX text for capitalized wordmark tokens that are not at a\n// sentence boundary and do not look like code identifiers or asset filenames.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"lowercaseStella\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof (node as { type: unknown }).type === \"string\";\n\nconst isStringLiteral = (node: unknown): node is AstNode & { value: string } =>\n isAstNode(node) &&\n node.type === \"Literal\" &&\n typeof node[\"value\"] === \"string\";\n\nconst CAPITALIZED_WORDMARK_PATTERN = /\\bStella\\b/gu;\nconst WHOLE_PATH = /^(?=.*[/\\\\.:~@])[\\w./\\\\:~@-]+$/u;\nconst SKIP_WHITESPACE = new Set([\" \", \"\\t\", \" \"]);\nconst SKIP_OPENERS = new Set([\n '\"',\n \"'\",\n \"`\",\n \"(\",\n \"[\",\n \"{\",\n \"“\",\n \"”\",\n \"‘\",\n \"’\",\n \"„\",\n \"«\",\n \"»\",\n \"‹\",\n \"›\",\n \"¿\",\n \"¡\",\n]);\nconst SENTENCE_TERMINATORS = new Set([\".\", \"!\", \"?\", \"…\", \"\\n\", \"\\r\"]);\n\nconst isSentenceStart = (\n text: string,\n idx: number,\n isContinuation: boolean,\n): boolean => {\n let i = idx - 1;\n while (i >= 0) {\n const ch = text[i];\n if (ch === undefined) {\n return !isContinuation;\n }\n if (SKIP_WHITESPACE.has(ch) || SKIP_OPENERS.has(ch)) {\n i--;\n continue;\n }\n if (isContinuation && (ch === \"\\n\" || ch === \"\\r\")) {\n i--;\n continue;\n }\n break;\n }\n if (i < 0) {\n return !isContinuation;\n }\n const previous = text[i];\n return previous !== undefined && SENTENCE_TERMINATORS.has(previous);\n};\n\nconst isCodeLikeSuffix = (text: string, endIdx: number): boolean => {\n const next = text[endIdx];\n if (next === \"-\" || next === \"_\") {\n return true;\n }\n if (next === \".\") {\n return /[a-z]/u.test(text[endIdx + 1] ?? \"\");\n }\n return false;\n};\n\nconst checkText = (\n context: RuleContext,\n node: unknown,\n value: string,\n isContinuation: boolean,\n): void => {\n if (!value.includes(\"Stella\")) {\n return;\n }\n if (WHOLE_PATH.test(value)) {\n return;\n }\n CAPITALIZED_WORDMARK_PATTERN.lastIndex = 0;\n let match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n while (match !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (\n !isSentenceStart(value, start, isContinuation) &&\n !isCodeLikeSuffix(value, end)\n ) {\n context.report({\n node,\n messageId: \"lowercaseStella\",\n data: { match: match[0] },\n });\n return;\n }\n match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n }\n};\n\nconst isImportOrExportSource = (literalNode: AstNode): boolean => {\n const parent = literalNode.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n return (\n parent.type === \"ImportDeclaration\" ||\n parent.type === \"ExportNamedDeclaration\" ||\n parent.type === \"ExportAllDeclaration\"\n );\n};\n\nconst isJsxContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (parent.type !== \"JSXElement\" && parent.type !== \"JSXFragment\") {\n return false;\n }\n const children = parent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(node) > 0;\n};\n\nconst isJsxExpressionContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent) || parent.type !== \"JSXExpressionContainer\") {\n return false;\n }\n const grandparent = parent.parent;\n if (!isAstNode(grandparent)) {\n return false;\n }\n if (grandparent.type !== \"JSXElement\" && grandparent.type !== \"JSXFragment\") {\n return false;\n }\n const children = grandparent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(parent) > 0;\n};\n\nconst quasiText = (quasi: unknown): string | null => {\n if (!isAstNode(quasi)) {\n return null;\n }\n const raw = quasi[\"value\"];\n if (typeof raw !== \"object\" || raw === null) {\n return null;\n }\n const cooked = (raw as { cooked?: unknown }).cooked;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const rawValue = (raw as { raw?: unknown }).raw;\n return typeof rawValue === \"string\" ? rawValue : null;\n};\n\nexport default {\n meta: { name: \"stella-lowercase\" },\n rules: {\n \"stella-lowercase\": {\n meta: {\n type: \"problem\",\n messages: {\n lowercaseStella:\n \"Use lowercase `stella` for the product wordmark. The capitalized form is only valid at a sentence start.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n if (!isStringLiteral(node)) {\n return;\n }\n if (isImportOrExportSource(node)) {\n return;\n }\n checkText(\n context,\n node,\n node.value,\n isJsxExpressionContinuation(node),\n );\n },\n TemplateLiteral(node: AstNode) {\n const quasis = node[\"quasis\"];\n if (!Array.isArray(quasis)) {\n return;\n }\n const headIsContinuation = isJsxExpressionContinuation(node);\n for (let i = 0; i < quasis.length; i++) {\n const quasi = quasis[i];\n const value = quasiText(quasi);\n if (value === null) {\n continue;\n }\n checkText(context, quasi, value, headIsContinuation || i > 0);\n }\n },\n JSXText(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n checkText(context, node, value, isJsxContinuation(node));\n },\n };\n },\n },\n },\n};\n"],"mappings":";;;;;AAkBA,MAAM,aAAa,SACjB,OAAO,SAAS,YAChB,SAAS,QACT,UAAU,QACV,OAAQ,KAA2B,SAAS;AAE9C,MAAM,mBAAmB,SACvB,UAAU,IAAI,KACd,KAAK,SAAS,aACd,OAAO,KAAK,aAAa;AAE3B,MAAM,+BAA+B;AACrC,MAAM,aAAa;AACnB,MAAM,
|
|
1
|
+
{"version":3,"file":"plugin.cjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["// Enforce the lowercase \"stella\" wordmark in source text.\n//\n// The stella brand identity is a lowercase wordmark. Generated copy and\n// hand-written prose can introduce a capitalized form mid-sentence, which\n// contradicts the wordmark. This rule scans string literals, template\n// literals, and JSX text for capitalized wordmark tokens that are not at a\n// sentence boundary and do not look like code identifiers or asset filenames.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"lowercaseStella\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof (node as { type: unknown }).type === \"string\";\n\nconst isStringLiteral = (node: unknown): node is AstNode & { value: string } =>\n isAstNode(node) &&\n node.type === \"Literal\" &&\n typeof node[\"value\"] === \"string\";\n\nconst CAPITALIZED_WORDMARK_PATTERN = /\\bStella\\b/gu;\nconst WHOLE_PATH = /^(?=.*[/\\\\.:~@])[\\w./\\\\:~@-]+$/u;\nconst SKIP_WHITESPACE = new Set([\" \", \"\\t\", \" \"]);\nconst SKIP_OPENERS = new Set([\n '\"',\n \"'\",\n \"`\",\n \"(\",\n \"[\",\n \"{\",\n \"“\",\n \"”\",\n \"‘\",\n \"’\",\n \"„\",\n \"«\",\n \"»\",\n \"‹\",\n \"›\",\n \"¿\",\n \"¡\",\n]);\nconst SENTENCE_TERMINATORS = new Set([\".\", \"!\", \"?\", \"…\", \"\\n\", \"\\r\"]);\n\nconst isSentenceStart = (\n text: string,\n idx: number,\n isContinuation: boolean,\n): boolean => {\n let i = idx - 1;\n while (i >= 0) {\n const ch = text[i];\n if (ch === undefined) {\n return !isContinuation;\n }\n if (SKIP_WHITESPACE.has(ch) || SKIP_OPENERS.has(ch)) {\n i--;\n continue;\n }\n if (isContinuation && (ch === \"\\n\" || ch === \"\\r\")) {\n i--;\n continue;\n }\n break;\n }\n if (i < 0) {\n return !isContinuation;\n }\n const previous = text[i];\n return previous !== undefined && SENTENCE_TERMINATORS.has(previous);\n};\n\nconst isCodeLikeSuffix = (text: string, endIdx: number): boolean => {\n const next = text[endIdx];\n if (next === \"-\" || next === \"_\") {\n return true;\n }\n if (next === \".\") {\n return /[a-z]/u.test(text[endIdx + 1] ?? \"\");\n }\n return false;\n};\n\nconst checkText = (\n context: RuleContext,\n node: unknown,\n value: string,\n isContinuation: boolean,\n): void => {\n if (!value.includes(\"Stella\")) {\n return;\n }\n if (WHOLE_PATH.test(value)) {\n return;\n }\n CAPITALIZED_WORDMARK_PATTERN.lastIndex = 0;\n let match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n while (match !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (\n !isSentenceStart(value, start, isContinuation) &&\n !isCodeLikeSuffix(value, end)\n ) {\n context.report({\n node,\n messageId: \"lowercaseStella\",\n data: { match: match[0] },\n });\n return;\n }\n match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n }\n};\n\nconst isImportOrExportSource = (literalNode: AstNode): boolean => {\n const parent = literalNode.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n return (\n parent.type === \"ImportDeclaration\" ||\n parent.type === \"ExportNamedDeclaration\" ||\n parent.type === \"ExportAllDeclaration\"\n );\n};\n\nconst isJsxContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (parent.type !== \"JSXElement\" && parent.type !== \"JSXFragment\") {\n return false;\n }\n const children = parent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(node) > 0;\n};\n\nconst isJsxExpressionContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent) || parent.type !== \"JSXExpressionContainer\") {\n return false;\n }\n const grandparent = parent.parent;\n if (!isAstNode(grandparent)) {\n return false;\n }\n if (grandparent.type !== \"JSXElement\" && grandparent.type !== \"JSXFragment\") {\n return false;\n }\n const children = grandparent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(parent) > 0;\n};\n\nconst quasiText = (quasi: unknown): string | null => {\n if (!isAstNode(quasi)) {\n return null;\n }\n const raw = quasi[\"value\"];\n if (typeof raw !== \"object\" || raw === null) {\n return null;\n }\n const cooked = (raw as { cooked?: unknown }).cooked;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const rawValue = (raw as { raw?: unknown }).raw;\n return typeof rawValue === \"string\" ? rawValue : null;\n};\n\nexport default {\n meta: { name: \"stella-lowercase\" },\n rules: {\n \"stella-lowercase\": {\n meta: {\n type: \"problem\",\n messages: {\n lowercaseStella:\n \"Use lowercase `stella` for the product wordmark. The capitalized form is only valid at a sentence start.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n if (!isStringLiteral(node)) {\n return;\n }\n if (isImportOrExportSource(node)) {\n return;\n }\n checkText(\n context,\n node,\n node.value,\n isJsxExpressionContinuation(node),\n );\n },\n TemplateLiteral(node: AstNode) {\n const quasis = node[\"quasis\"];\n if (!Array.isArray(quasis)) {\n return;\n }\n const headIsContinuation = isJsxExpressionContinuation(node);\n for (let i = 0; i < quasis.length; i++) {\n const quasi = quasis[i];\n const value = quasiText(quasi);\n if (value === null) {\n continue;\n }\n checkText(context, quasi, value, headIsContinuation || i > 0);\n }\n },\n JSXText(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n checkText(context, node, value, isJsxContinuation(node));\n },\n };\n },\n },\n },\n};\n"],"mappings":"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"}
|
package/dist/plugin.mjs
CHANGED
|
@@ -3,12 +3,12 @@ const isAstNode = (node) => typeof node === "object" && node !== null && "type"
|
|
|
3
3
|
const isStringLiteral = (node) => isAstNode(node) && node.type === "Literal" && typeof node["value"] === "string";
|
|
4
4
|
const CAPITALIZED_WORDMARK_PATTERN = /\bStella\b/gu;
|
|
5
5
|
const WHOLE_PATH = /^(?=.*[/\\.:~@])[\w./\\:~@-]+$/u;
|
|
6
|
-
const SKIP_WHITESPACE = new Set([
|
|
6
|
+
const SKIP_WHITESPACE = /* @__PURE__ */ new Set([
|
|
7
7
|
" ",
|
|
8
8
|
" ",
|
|
9
9
|
"\xA0"
|
|
10
10
|
]);
|
|
11
|
-
const SKIP_OPENERS = new Set([
|
|
11
|
+
const SKIP_OPENERS = /* @__PURE__ */ new Set([
|
|
12
12
|
"\"",
|
|
13
13
|
"'",
|
|
14
14
|
"`",
|
|
@@ -27,7 +27,7 @@ const SKIP_OPENERS = new Set([
|
|
|
27
27
|
"¿",
|
|
28
28
|
"¡"
|
|
29
29
|
]);
|
|
30
|
-
const SENTENCE_TERMINATORS = new Set([
|
|
30
|
+
const SENTENCE_TERMINATORS = /* @__PURE__ */ new Set([
|
|
31
31
|
".",
|
|
32
32
|
"!",
|
|
33
33
|
"?",
|
package/dist/plugin.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["// Enforce the lowercase \"stella\" wordmark in source text.\n//\n// The stella brand identity is a lowercase wordmark. Generated copy and\n// hand-written prose can introduce a capitalized form mid-sentence, which\n// contradicts the wordmark. This rule scans string literals, template\n// literals, and JSX text for capitalized wordmark tokens that are not at a\n// sentence boundary and do not look like code identifiers or asset filenames.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"lowercaseStella\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof (node as { type: unknown }).type === \"string\";\n\nconst isStringLiteral = (node: unknown): node is AstNode & { value: string } =>\n isAstNode(node) &&\n node.type === \"Literal\" &&\n typeof node[\"value\"] === \"string\";\n\nconst CAPITALIZED_WORDMARK_PATTERN = /\\bStella\\b/gu;\nconst WHOLE_PATH = /^(?=.*[/\\\\.:~@])[\\w./\\\\:~@-]+$/u;\nconst SKIP_WHITESPACE = new Set([\" \", \"\\t\", \" \"]);\nconst SKIP_OPENERS = new Set([\n '\"',\n \"'\",\n \"`\",\n \"(\",\n \"[\",\n \"{\",\n \"“\",\n \"”\",\n \"‘\",\n \"’\",\n \"„\",\n \"«\",\n \"»\",\n \"‹\",\n \"›\",\n \"¿\",\n \"¡\",\n]);\nconst SENTENCE_TERMINATORS = new Set([\".\", \"!\", \"?\", \"…\", \"\\n\", \"\\r\"]);\n\nconst isSentenceStart = (\n text: string,\n idx: number,\n isContinuation: boolean,\n): boolean => {\n let i = idx - 1;\n while (i >= 0) {\n const ch = text[i];\n if (ch === undefined) {\n return !isContinuation;\n }\n if (SKIP_WHITESPACE.has(ch) || SKIP_OPENERS.has(ch)) {\n i--;\n continue;\n }\n if (isContinuation && (ch === \"\\n\" || ch === \"\\r\")) {\n i--;\n continue;\n }\n break;\n }\n if (i < 0) {\n return !isContinuation;\n }\n const previous = text[i];\n return previous !== undefined && SENTENCE_TERMINATORS.has(previous);\n};\n\nconst isCodeLikeSuffix = (text: string, endIdx: number): boolean => {\n const next = text[endIdx];\n if (next === \"-\" || next === \"_\") {\n return true;\n }\n if (next === \".\") {\n return /[a-z]/u.test(text[endIdx + 1] ?? \"\");\n }\n return false;\n};\n\nconst checkText = (\n context: RuleContext,\n node: unknown,\n value: string,\n isContinuation: boolean,\n): void => {\n if (!value.includes(\"Stella\")) {\n return;\n }\n if (WHOLE_PATH.test(value)) {\n return;\n }\n CAPITALIZED_WORDMARK_PATTERN.lastIndex = 0;\n let match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n while (match !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (\n !isSentenceStart(value, start, isContinuation) &&\n !isCodeLikeSuffix(value, end)\n ) {\n context.report({\n node,\n messageId: \"lowercaseStella\",\n data: { match: match[0] },\n });\n return;\n }\n match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n }\n};\n\nconst isImportOrExportSource = (literalNode: AstNode): boolean => {\n const parent = literalNode.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n return (\n parent.type === \"ImportDeclaration\" ||\n parent.type === \"ExportNamedDeclaration\" ||\n parent.type === \"ExportAllDeclaration\"\n );\n};\n\nconst isJsxContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (parent.type !== \"JSXElement\" && parent.type !== \"JSXFragment\") {\n return false;\n }\n const children = parent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(node) > 0;\n};\n\nconst isJsxExpressionContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent) || parent.type !== \"JSXExpressionContainer\") {\n return false;\n }\n const grandparent = parent.parent;\n if (!isAstNode(grandparent)) {\n return false;\n }\n if (grandparent.type !== \"JSXElement\" && grandparent.type !== \"JSXFragment\") {\n return false;\n }\n const children = grandparent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(parent) > 0;\n};\n\nconst quasiText = (quasi: unknown): string | null => {\n if (!isAstNode(quasi)) {\n return null;\n }\n const raw = quasi[\"value\"];\n if (typeof raw !== \"object\" || raw === null) {\n return null;\n }\n const cooked = (raw as { cooked?: unknown }).cooked;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const rawValue = (raw as { raw?: unknown }).raw;\n return typeof rawValue === \"string\" ? rawValue : null;\n};\n\nexport default {\n meta: { name: \"stella-lowercase\" },\n rules: {\n \"stella-lowercase\": {\n meta: {\n type: \"problem\",\n messages: {\n lowercaseStella:\n \"Use lowercase `stella` for the product wordmark. The capitalized form is only valid at a sentence start.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n if (!isStringLiteral(node)) {\n return;\n }\n if (isImportOrExportSource(node)) {\n return;\n }\n checkText(\n context,\n node,\n node.value,\n isJsxExpressionContinuation(node),\n );\n },\n TemplateLiteral(node: AstNode) {\n const quasis = node[\"quasis\"];\n if (!Array.isArray(quasis)) {\n return;\n }\n const headIsContinuation = isJsxExpressionContinuation(node);\n for (let i = 0; i < quasis.length; i++) {\n const quasi = quasis[i];\n const value = quasiText(quasi);\n if (value === null) {\n continue;\n }\n checkText(context, quasi, value, headIsContinuation || i > 0);\n }\n },\n JSXText(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n checkText(context, node, value, isJsxContinuation(node));\n },\n };\n },\n },\n },\n};\n"],"mappings":";AAkBA,MAAM,aAAa,SACjB,OAAO,SAAS,YAChB,SAAS,QACT,UAAU,QACV,OAAQ,KAA2B,SAAS;AAE9C,MAAM,mBAAmB,SACvB,UAAU,IAAI,KACd,KAAK,SAAS,aACd,OAAO,KAAK,aAAa;AAE3B,MAAM,+BAA+B;AACrC,MAAM,aAAa;AACnB,MAAM,
|
|
1
|
+
{"version":3,"file":"plugin.mjs","names":[],"sources":["../src/plugin.ts"],"sourcesContent":["// Enforce the lowercase \"stella\" wordmark in source text.\n//\n// The stella brand identity is a lowercase wordmark. Generated copy and\n// hand-written prose can introduce a capitalized form mid-sentence, which\n// contradicts the wordmark. This rule scans string literals, template\n// literals, and JSX text for capitalized wordmark tokens that are not at a\n// sentence boundary and do not look like code identifiers or asset filenames.\n\ntype AstNode = { type: string; parent?: unknown } & Record<string, unknown>;\n\ntype RuleContext = {\n report: (diagnostic: {\n node: unknown;\n messageId: \"lowercaseStella\";\n data?: { match: string };\n }) => void;\n};\n\nconst isAstNode = (node: unknown): node is AstNode =>\n typeof node === \"object\" &&\n node !== null &&\n \"type\" in node &&\n typeof (node as { type: unknown }).type === \"string\";\n\nconst isStringLiteral = (node: unknown): node is AstNode & { value: string } =>\n isAstNode(node) &&\n node.type === \"Literal\" &&\n typeof node[\"value\"] === \"string\";\n\nconst CAPITALIZED_WORDMARK_PATTERN = /\\bStella\\b/gu;\nconst WHOLE_PATH = /^(?=.*[/\\\\.:~@])[\\w./\\\\:~@-]+$/u;\nconst SKIP_WHITESPACE = new Set([\" \", \"\\t\", \" \"]);\nconst SKIP_OPENERS = new Set([\n '\"',\n \"'\",\n \"`\",\n \"(\",\n \"[\",\n \"{\",\n \"“\",\n \"”\",\n \"‘\",\n \"’\",\n \"„\",\n \"«\",\n \"»\",\n \"‹\",\n \"›\",\n \"¿\",\n \"¡\",\n]);\nconst SENTENCE_TERMINATORS = new Set([\".\", \"!\", \"?\", \"…\", \"\\n\", \"\\r\"]);\n\nconst isSentenceStart = (\n text: string,\n idx: number,\n isContinuation: boolean,\n): boolean => {\n let i = idx - 1;\n while (i >= 0) {\n const ch = text[i];\n if (ch === undefined) {\n return !isContinuation;\n }\n if (SKIP_WHITESPACE.has(ch) || SKIP_OPENERS.has(ch)) {\n i--;\n continue;\n }\n if (isContinuation && (ch === \"\\n\" || ch === \"\\r\")) {\n i--;\n continue;\n }\n break;\n }\n if (i < 0) {\n return !isContinuation;\n }\n const previous = text[i];\n return previous !== undefined && SENTENCE_TERMINATORS.has(previous);\n};\n\nconst isCodeLikeSuffix = (text: string, endIdx: number): boolean => {\n const next = text[endIdx];\n if (next === \"-\" || next === \"_\") {\n return true;\n }\n if (next === \".\") {\n return /[a-z]/u.test(text[endIdx + 1] ?? \"\");\n }\n return false;\n};\n\nconst checkText = (\n context: RuleContext,\n node: unknown,\n value: string,\n isContinuation: boolean,\n): void => {\n if (!value.includes(\"Stella\")) {\n return;\n }\n if (WHOLE_PATH.test(value)) {\n return;\n }\n CAPITALIZED_WORDMARK_PATTERN.lastIndex = 0;\n let match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n while (match !== null) {\n const start = match.index;\n const end = start + match[0].length;\n if (\n !isSentenceStart(value, start, isContinuation) &&\n !isCodeLikeSuffix(value, end)\n ) {\n context.report({\n node,\n messageId: \"lowercaseStella\",\n data: { match: match[0] },\n });\n return;\n }\n match = CAPITALIZED_WORDMARK_PATTERN.exec(value);\n }\n};\n\nconst isImportOrExportSource = (literalNode: AstNode): boolean => {\n const parent = literalNode.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n return (\n parent.type === \"ImportDeclaration\" ||\n parent.type === \"ExportNamedDeclaration\" ||\n parent.type === \"ExportAllDeclaration\"\n );\n};\n\nconst isJsxContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent)) {\n return false;\n }\n if (parent.type !== \"JSXElement\" && parent.type !== \"JSXFragment\") {\n return false;\n }\n const children = parent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(node) > 0;\n};\n\nconst isJsxExpressionContinuation = (node: AstNode): boolean => {\n const parent = node.parent;\n if (!isAstNode(parent) || parent.type !== \"JSXExpressionContainer\") {\n return false;\n }\n const grandparent = parent.parent;\n if (!isAstNode(grandparent)) {\n return false;\n }\n if (grandparent.type !== \"JSXElement\" && grandparent.type !== \"JSXFragment\") {\n return false;\n }\n const children = grandparent[\"children\"];\n if (!Array.isArray(children)) {\n return false;\n }\n return children.indexOf(parent) > 0;\n};\n\nconst quasiText = (quasi: unknown): string | null => {\n if (!isAstNode(quasi)) {\n return null;\n }\n const raw = quasi[\"value\"];\n if (typeof raw !== \"object\" || raw === null) {\n return null;\n }\n const cooked = (raw as { cooked?: unknown }).cooked;\n if (typeof cooked === \"string\") {\n return cooked;\n }\n const rawValue = (raw as { raw?: unknown }).raw;\n return typeof rawValue === \"string\" ? rawValue : null;\n};\n\nexport default {\n meta: { name: \"stella-lowercase\" },\n rules: {\n \"stella-lowercase\": {\n meta: {\n type: \"problem\",\n messages: {\n lowercaseStella:\n \"Use lowercase `stella` for the product wordmark. The capitalized form is only valid at a sentence start.\",\n },\n },\n create(context: RuleContext) {\n return {\n Literal(node: AstNode) {\n if (!isStringLiteral(node)) {\n return;\n }\n if (isImportOrExportSource(node)) {\n return;\n }\n checkText(\n context,\n node,\n node.value,\n isJsxExpressionContinuation(node),\n );\n },\n TemplateLiteral(node: AstNode) {\n const quasis = node[\"quasis\"];\n if (!Array.isArray(quasis)) {\n return;\n }\n const headIsContinuation = isJsxExpressionContinuation(node);\n for (let i = 0; i < quasis.length; i++) {\n const quasi = quasis[i];\n const value = quasiText(quasi);\n if (value === null) {\n continue;\n }\n checkText(context, quasi, value, headIsContinuation || i > 0);\n }\n },\n JSXText(node: AstNode) {\n const value = node[\"value\"];\n if (typeof value !== \"string\") {\n return;\n }\n checkText(context, node, value, isJsxContinuation(node));\n },\n };\n },\n },\n },\n};\n"],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/oxlint-config",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "Shared general oxlint configuration for stella packages.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"repository": {
|
|
@@ -61,12 +61,12 @@
|
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@stll/typescript-config": "workspace:*",
|
|
63
63
|
"oxfmt": "0.48.0",
|
|
64
|
-
"oxlint": "1.
|
|
65
|
-
"typescript": "
|
|
64
|
+
"oxlint": "1.75.0",
|
|
65
|
+
"typescript": "7.0.2"
|
|
66
66
|
},
|
|
67
67
|
"peerDependencies": {
|
|
68
|
-
"oxlint": ">=1.
|
|
69
|
-
"oxlint-tsgolint": ">=0.
|
|
68
|
+
"oxlint": ">=1.75.0 <2",
|
|
69
|
+
"oxlint-tsgolint": ">=7.0.2001 <8"
|
|
70
70
|
},
|
|
71
71
|
"engines": {
|
|
72
72
|
"node": "^20.19.0 || >=22.12.0"
|