From 009a49b845790b1d92150cebaee80da6ab5597b7 Mon Sep 17 00:00:00 2001 From: Miklos Daniel Date: Sat, 5 Sep 2026 16:17:36 +0200 Subject: [PATCH 1/4] feat: auto-fixable ThemingParameters warning --- .changeset/pink-times-ring.md | 5 ++ src/rules/use-theming-parameters.test.ts | 100 ++++------------------- src/rules/use-theming-parameters.ts | 41 +++------- 3 files changed, 32 insertions(+), 114 deletions(-) create mode 100644 .changeset/pink-times-ring.md diff --git a/.changeset/pink-times-ring.md b/.changeset/pink-times-ring.md new file mode 100644 index 0000000..6c4d07c --- /dev/null +++ b/.changeset/pink-times-ring.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-ui5-webcomponents-react": minor +--- + +Add auto-fix feature, so oxlint/eslint can solve the warnings by passing the "--fix" flag diff --git a/src/rules/use-theming-parameters.test.ts b/src/rules/use-theming-parameters.test.ts index 04579a8..944726f 100644 --- a/src/rules/use-theming-parameters.test.ts +++ b/src/rules/use-theming-parameters.test.ts @@ -33,66 +33,34 @@ describe("use-theming-parameters", () => { invalid: [ { code: `
;`, - errors: [ - { - messageId: "useThemingParameters", - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: `
;`, - }, - { - messageId: "replaceAndImportThemingParameter", - output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\n
;`, - }, - ], - }, - ], + errors: [{ messageId: "useThemingParameters" }], + output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\n
;`, }, ], }, ); - ruleTester.run("flags multiple occurrences in one style prop", useThemingParameters as never, { + ruleTester.run("fixes multiple occurrences in one style prop", useThemingParameters as never, { valid: [], invalid: [ { - code: `My Text;`, + code: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nMy Text;`, errors: [ { messageId: "useThemingParameters", data: { replacement: "ThemingParameters.sapNegativeColor" }, - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: `My Text;`, - }, - { - messageId: "replaceAndImportThemingParameter", - output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nMy Text;`, - }, - ], }, { messageId: "useThemingParameters", data: { replacement: "ThemingParameters.sapFontLargeSize" }, - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: `My Text;`, - }, - { - messageId: "replaceAndImportThemingParameter", - output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nMy Text;`, - }, - ], }, ], + output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nMy Text;`, }, ], }); - ruleTester.run("flags variables in styles object outside JSX", useThemingParameters as never, { + ruleTester.run("fixes variables in styles object outside JSX", useThemingParameters as never, { valid: [], invalid: [ { @@ -101,23 +69,14 @@ describe("use-theming-parameters", () => { { messageId: "useThemingParameters", data: { replacement: "ThemingParameters.sapBackgroundColor" }, - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: `const styles = { background: ThemingParameters.sapBackgroundColor };`, - }, - { - messageId: "replaceAndImportThemingParameter", - output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst styles = { background: ThemingParameters.sapBackgroundColor };`, - }, - ], }, ], + output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst styles = { background: ThemingParameters.sapBackgroundColor };`, }, ], }); - ruleTester.run("flags template literal without interpolation", useThemingParameters as never, { + ruleTester.run("fixes template literal without interpolation", useThemingParameters as never, { valid: [], invalid: [ { @@ -126,18 +85,9 @@ describe("use-theming-parameters", () => { { messageId: "useThemingParameters", data: { replacement: "ThemingParameters.sapFontLargeSize" }, - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: "const x = ThemingParameters.sapFontLargeSize;", - }, - { - messageId: "replaceAndImportThemingParameter", - output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst x = ThemingParameters.sapFontLargeSize;`, - }, - ], }, ], + output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst x = ThemingParameters.sapFontLargeSize;`, }, ], }); @@ -151,23 +101,14 @@ describe("use-theming-parameters", () => { { messageId: "useThemingParameters", data: { replacement: 'ThemingParameters["sapFontUrl_SAP-icons_woff2"]' }, - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: `const x = ThemingParameters["sapFontUrl_SAP-icons_woff2"];`, - }, - { - messageId: "replaceAndImportThemingParameter", - output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst x = ThemingParameters["sapFontUrl_SAP-icons_woff2"];`, - }, - ], }, ], + output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst x = ThemingParameters["sapFontUrl_SAP-icons_woff2"];`, }, ], }); - ruleTester.run("only offers import suggestion when not imported", useThemingParameters as never, { + ruleTester.run("does not add an import when already imported", useThemingParameters as never, { valid: [], invalid: [ { @@ -175,14 +116,10 @@ describe("use-theming-parameters", () => { errors: [ { messageId: "useThemingParameters", - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst x = ThemingParameters.sapNegativeColor;`, - }, - ], + data: { replacement: "ThemingParameters.sapNegativeColor" }, }, ], + output: `import { ThemingParameters } from '@ui5/webcomponents-react-base/ThemingParameters';\nconst x = ThemingParameters.sapNegativeColor;`, }, ], }); @@ -197,18 +134,9 @@ describe("use-theming-parameters", () => { { messageId: "useThemingParameters", data: { replacement: "Theme.sapNegativeColor" }, - suggestions: [ - { - messageId: "replaceWithThemingParameter", - output: "const x = Theme.sapNegativeColor;", - }, - { - messageId: "replaceAndImportThemingParameter", - output: `import { Theme } from '@ui5/webcomponents-react-base';\nconst x = Theme.sapNegativeColor;`, - }, - ], }, ], + output: `import { Theme } from '@ui5/webcomponents-react-base';\nconst x = Theme.sapNegativeColor;`, }, ], }); diff --git a/src/rules/use-theming-parameters.ts b/src/rules/use-theming-parameters.ts index 1673160..62b44bf 100644 --- a/src/rules/use-theming-parameters.ts +++ b/src/rules/use-theming-parameters.ts @@ -1,5 +1,5 @@ import { defineRule } from "@oxlint/plugins"; -import type { Ranged, Suggestion } from "@oxlint/plugins"; +import type { Ranged } from "@oxlint/plugins"; export interface UseThemingParametersOptions { importSource?: string; @@ -45,13 +45,10 @@ export const useThemingParameters = defineRule({ additionalProperties: false, }, ], - hasSuggestions: true, + fixable: "code", messages: { useThemingParameters: "Avoid inline CSS variable strings. Use the type-safe ThemingParameters object instead: {{ replacement }}.", - replaceWithThemingParameter: "Replace with `{{ replacement }}`.", - replaceAndImportThemingParameter: - "Replace with `{{ replacement }}` and import ThemingParameters.", }, defaultOptions: [ { @@ -71,33 +68,21 @@ export const useThemingParameters = defineRule({ if (!match) return; const replacement = cssVarToReplacement(match[1]!, objectName); - const suggestions: Suggestion[] = [ - { - messageId: "replaceWithThemingParameter", - data: { replacement }, - fix: (fixer) => fixer.replaceTextRange(replacementRange, replacement), - }, - ]; - - if (!themingParametersImported) { - suggestions.push({ - messageId: "replaceAndImportThemingParameter", - data: { replacement }, - fix: (fixer) => [ - fixer.replaceTextRange(replacementRange, replacement), - fixer.insertTextBeforeRange( - [0, 0], - `import { ${objectName} } from '${importSource}';\n`, - ), - ], - }); - } - context.report({ node, messageId: "useThemingParameters", data: { replacement }, - suggest: suggestions, + fix: (fixer) => { + const replace = fixer.replaceTextRange(replacementRange, replacement); + if (themingParametersImported) return replace; + return [ + replace, + fixer.insertTextBeforeRange( + [0, 0], + `import { ${objectName} } from '${importSource}';\n`, + ), + ]; + }, }); }; From 2917bf67931ed01b0273178586d35e1a94707837 Mon Sep 17 00:00:00 2001 From: Miklos Daniel Date: Sat, 5 Sep 2026 16:17:42 +0200 Subject: [PATCH 2/4] update readme --- README.md | 111 +++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 105 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index faf5423..d41fb27 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,114 @@ # eslint-plugin-ui5-webcomponents-react -To install dependencies: +ESLint-compatible plugin with rules for [@ui5/webcomponents-react](https://github.com/SAP/ui5-webcomponents-react). Works with both [ESLint](https://eslint.org) (v9+) and [Oxlint](https://oxc.rs/docs/guide/usage/linter.html) via its JS plugin support. + +## Rules + +| Rule | Description | Fixable | +| ---------------------------------------- | -------------------------------------------------------------------------------------------- | ------- | +| `ui5-webcomponents-react/use-theming-parameters` | Flags inline `var(--sap...)` CSS variable strings and replaces them with the type-safe `ThemingParameters` object. | Yes | + +## Installation ```bash -bun install +npm install -D eslint-plugin-ui5-webcomponents-react ``` -To run: +## Oxlint (JS plugins) -```bash -bun run index.ts +Oxlint's plugin API is compatible with ESLint v9+, so this plugin works out of the box. Add it under `jsPlugins` and enable its rules under `rules`. + +### using `oxlint.config.ts` + +```ts +import { defineConfig } from "oxlint"; + +export default defineConfig({ + jsPlugins: ["eslint-plugin-ui5-webcomponents-react"], + rules: { + "ui5-webcomponents-react/use-theming-parameters": "warn", + }, +}); +``` + +### using `.oxlintrc.json` + +```jsonc +{ + "jsPlugins": ["eslint-plugin-ui5-webcomponents-react"], + "rules": { + "ui5-webcomponents-react/use-theming-parameters": "warn" + } +} +``` + +The rule name is derived from the plugin's `meta.name` (`eslint-plugin-` prefix is stripped). You can override it with an [alias](https://oxc.rs/docs/guide/usage/linter/js-plugins.html#plugin-aliases): + +```jsonc +{ + "jsPlugins": [{ "name": "ui5", "specifier": "eslint-plugin-ui5-webcomponents-react" }], + "rules": { + "ui5/use-theming-parameters": "warn" + } +} +``` + +Both rules are auto-fixable, so run `oxlint --fix` (or apply quick-fixes in your editor) to replace the strings and insert the missing import automatically. + +## ESLint (flat config) + +This plugin ships an ESLint `meta.name` and a `configs.recommended` export. Using the plugin directly: + +```js +// eslint.config.js +import ui5WebComponentsReact from "eslint-plugin-ui5-webcomponents-react"; + +export default [ + { + plugins: { + "ui5-webcomponents-react": ui5WebComponentsReact, + }, + rules: { + "ui5-webcomponents-react/use-theming-parameters": "warn", + }, + }, +]; +``` + +Or with the recommended preset: + +```js +// eslint.config.js +import ui5WebComponentsReact from "eslint-plugin-ui5-webcomponents-react"; + +export default [ + ui5WebComponentsReact.configs.recommended, +]; ``` -This project was created using `bun init` in bun v1.3.14. [Bun](https://bun.com) is a fast all-in-one JavaScript runtime. +## Rule options + +`use-theming-parameters` accepts an optional options object: + +| Option | Default | Description | +| -------------- | ---------------------------------------------------------------- | --------------------------------------------- | +| `importSource` | `@ui5/webcomponents-react-base/ThemingParameters` | Module that exports the theming parameters. | +| `objectName` | `ThemingParameters` | Identifier used for the replacement and import. | + +```jsonc +{ + "rules": { + "ui5-webcomponents-react/use-theming-parameters": ["warn", { "importSource": "@ui5/webcomponents-react-base", "objectName": "Theme" }] + } +} +``` + +## Development + +```bash +bun install # install dependencies +bun test # run tests +bun run typecheck # typecheck +bun run lint # lint with oxlint +bun run build # build the plugin +``` \ No newline at end of file From e642fd0421b251ab8c31554fcf05c313bf7f38e5 Mon Sep 17 00:00:00 2001 From: Miklos Daniel Date: Sat, 5 Sep 2026 16:19:57 +0200 Subject: [PATCH 3/4] style: format --- README.md | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index d41fb27..ed8733d 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,8 @@ ESLint-compatible plugin with rules for [@ui5/webcomponents-react](https://githu ## Rules -| Rule | Description | Fixable | -| ---------------------------------------- | -------------------------------------------------------------------------------------------- | ------- | +| Rule | Description | Fixable | +| ------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------ | ------- | | `ui5-webcomponents-react/use-theming-parameters` | Flags inline `var(--sap...)` CSS variable strings and replaces them with the type-safe `ThemingParameters` object. | Yes | ## Installation @@ -37,8 +37,8 @@ export default defineConfig({ { "jsPlugins": ["eslint-plugin-ui5-webcomponents-react"], "rules": { - "ui5-webcomponents-react/use-theming-parameters": "warn" - } + "ui5-webcomponents-react/use-theming-parameters": "warn", + }, } ``` @@ -48,8 +48,8 @@ The rule name is derived from the plugin's `meta.name` (`eslint-plugin-` prefix { "jsPlugins": [{ "name": "ui5", "specifier": "eslint-plugin-ui5-webcomponents-react" }], "rules": { - "ui5/use-theming-parameters": "warn" - } + "ui5/use-theming-parameters": "warn", + }, } ``` @@ -81,25 +81,26 @@ Or with the recommended preset: // eslint.config.js import ui5WebComponentsReact from "eslint-plugin-ui5-webcomponents-react"; -export default [ - ui5WebComponentsReact.configs.recommended, -]; +export default [ui5WebComponentsReact.configs.recommended]; ``` ## Rule options `use-theming-parameters` accepts an optional options object: -| Option | Default | Description | -| -------------- | ---------------------------------------------------------------- | --------------------------------------------- | -| `importSource` | `@ui5/webcomponents-react-base/ThemingParameters` | Module that exports the theming parameters. | -| `objectName` | `ThemingParameters` | Identifier used for the replacement and import. | +| Option | Default | Description | +| -------------- | ------------------------------------------------- | ----------------------------------------------- | +| `importSource` | `@ui5/webcomponents-react-base/ThemingParameters` | Module that exports the theming parameters. | +| `objectName` | `ThemingParameters` | Identifier used for the replacement and import. | ```jsonc { "rules": { - "ui5-webcomponents-react/use-theming-parameters": ["warn", { "importSource": "@ui5/webcomponents-react-base", "objectName": "Theme" }] - } + "ui5-webcomponents-react/use-theming-parameters": [ + "warn", + { "importSource": "@ui5/webcomponents-react-base", "objectName": "Theme" }, + ], + }, } ``` @@ -111,4 +112,4 @@ bun test # run tests bun run typecheck # typecheck bun run lint # lint with oxlint bun run build # build the plugin -``` \ No newline at end of file +``` From 52e5185578c379e3614ec8920f1f998a8bb63c13 Mon Sep 17 00:00:00 2001 From: Miklos Daniel Date: Sat, 5 Sep 2026 16:50:06 +0200 Subject: [PATCH 4/4] fix import + swap at the same time issue --- src/rules/use-theming-parameters.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/rules/use-theming-parameters.ts b/src/rules/use-theming-parameters.ts index 62b44bf..876992b 100644 --- a/src/rules/use-theming-parameters.ts +++ b/src/rules/use-theming-parameters.ts @@ -62,6 +62,7 @@ export const useThemingParameters = defineRule({ const importSource = options.importSource ?? "@ui5/webcomponents-react-base/ThemingParameters"; const objectName = options.objectName ?? "ThemingParameters"; let themingParametersImported = false; + let importFixAdded = false; const checkStringValue = (node: Ranged, replacementRange: [number, number], value: string) => { const match = SAP_VAR_PATTERN.exec(value); @@ -74,7 +75,8 @@ export const useThemingParameters = defineRule({ data: { replacement }, fix: (fixer) => { const replace = fixer.replaceTextRange(replacementRange, replacement); - if (themingParametersImported) return replace; + if (themingParametersImported || importFixAdded) return replace; + importFixAdded = true; return [ replace, fixer.insertTextBeforeRange(