From fa76a3ca8e77633a41404e6f2d8335af668b390b Mon Sep 17 00:00:00 2001 From: Brandon White Date: Wed, 2 Sep 2026 09:13:06 -0500 Subject: [PATCH] build: lint JavaScript and JSON from one eslint config super-linter shares JAVASCRIPT_ES_LINTER_RULES across its JAVASCRIPT_ES, JSON, JSONC and VUE linters, so a single config has to cover JS and JSON before super-linter can be pointed at ours. #28 worked around that by disabling VALIDATE_JAVASCRIPT_ES and letting ci.yml's `npm run lint` be the authority for JS, which left super-linter using its own bundled config (eslint-plugin-react rules and all) for JSON. Added @eslint/json and scoped the JavaScript configs to js/mjs/cjs. None of js.configs.recommended, eslint-plugin-jest or eslint-plugin-prettier declare `files` of their own, so without that scoping their rules would be applied to the JSON files too. .github/linters/eslint.config.mjs re-exports the root config, because JAVASCRIPT_ES_CONFIG_FILE resolves under LINTER_RULES_PATH rather than the repo root. VALIDATE_JAVASCRIPT_ES goes back to its default, so super-linter and `npm run lint` now enforce the same rules over the same files. json/no-empty-keys is off for package-lock.json only: npm's `packages` map uses "" for the root project. The file is still checked by the other three JSON rules. Verified `eslint --print-config src/main.js` is unchanged - 82 active rules before and after, none lost or added. eslint 9.39.4, the version in the super-linter container, loads this config through the shim and reports real JS and JSON errors with exit 1, so it is linting rather than silently matching nothing. Co-Authored-By: Claude Opus 5 --- .github/linters/eslint.config.mjs | 5 ++++ .github/workflows/linter.yml | 8 ------ eslint.config.mjs | 44 ++++++++++++++++++++++++++++--- package-lock.json | 27 +++++++++++++++++++ package.json | 1 + 5 files changed, 74 insertions(+), 11 deletions(-) create mode 100644 .github/linters/eslint.config.mjs diff --git a/.github/linters/eslint.config.mjs b/.github/linters/eslint.config.mjs new file mode 100644 index 0000000..e0c0ef0 --- /dev/null +++ b/.github/linters/eslint.config.mjs @@ -0,0 +1,5 @@ +// super-linter resolves JAVASCRIPT_ES_CONFIG_FILE under LINTER_RULES_PATH +// (.github/linters), so this re-exports the repo's config. That keeps one +// source of truth for `npm run lint` and for super-linter's JAVASCRIPT_ES, +// JSON and JSONC linters, which all share this config. +export { default } from '../../eslint.config.mjs' diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml index 672c867..6e2ff6a 100644 --- a/.github/workflows/linter.yml +++ b/.github/workflows/linter.yml @@ -55,14 +55,6 @@ jobs: STRIP_DEFAULT_WORKSPACE_FOR_REGEX: true GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} VALIDATE_ALL_CODEBASE: true - # ci.yml already runs `npm run lint`, which is this repo's eslint and - # this repo's eslint.config.mjs. super-linter cannot be pointed at that - # config instead: JAVASCRIPT_ES_CONFIG_FILE is shared with the JSON, - # JSONC and VUE linters, and our config has no JSON language support, - # so reusing it would silently stop linting JSON. Rather than lint JS - # twice under two different rule sets, defer to ci.yml and let - # super-linter keep its own config for JSON/JSONC. - VALIDATE_JAVASCRIPT_ES: false VALIDATE_JSCPD: false VALIDATE_HTML: false # Biome is a competing formatter/linter: it wants semicolons and diff --git a/eslint.config.mjs b/eslint.config.mjs index 086446e..2efabdc 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -1,18 +1,31 @@ // See: https://eslint.org/docs/latest/use/configure/configuration-files import js from '@eslint/js' +import json from '@eslint/json' import jest from 'eslint-plugin-jest' import prettierRecommended from 'eslint-plugin-prettier/recommended' import globals from 'globals' +// super-linter lints JavaScript and JSON with a single eslint config, so this +// file covers both. None of the shared JavaScript configs below declare `files` +// of their own, which would apply their rules to the JSON files too, so they +// are scoped explicitly. +const jsFiles = ['**/*.js', '**/*.mjs', '**/*.cjs'] + export default [ { ignores: ['**/coverage', '**/dist', '**/linter', '**/node_modules'] }, - js.configs.recommended, - jest.configs['flat/recommended'], - prettierRecommended, + + ...[ + js.configs.recommended, + jest.configs['flat/recommended'], + prettierRecommended + ].map((config) => ({ ...config, files: jsFiles })), + { + files: jsFiles, + languageOptions: { globals: { ...globals.node, @@ -32,5 +45,30 @@ export default [ 'no-unused-vars': 'off', 'prettier/prettier': 'error' } + }, + + { + ...json.configs.recommended, + files: ['**/*.json'], + language: 'json/json' + }, + + { + // super-linter routes .jsonc through this config too, so cover it here + // rather than leaving it silently unlinted if a file is ever added. + ...json.configs.recommended, + files: ['**/*.jsonc'], + language: 'json/jsonc' + }, + + { + // npm generates the lockfile, and its `packages` map uses "" as the key for + // the root project. Turn off just that rule rather than skipping the file, + // so the remaining JSON checks still apply. + files: ['package-lock.json'], + language: 'json/json', + rules: { + 'json/no-empty-keys': 'off' + } } ] diff --git a/package-lock.json b/package-lock.json index 95d5a9a..52a42cd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@eslint/json": "^2.1.0", "@jest/globals": "^30.4.1", "@rollup/plugin-commonjs": "^29.0.3", "@rollup/plugin-json": "^6.1.0", @@ -1081,6 +1082,22 @@ } } }, + "node_modules/@eslint/json": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/json/-/json-2.1.0.tgz", + "integrity": "sha512-Zcg+SXHmdAlo+0ZFwxOvDQye2zxhaFCu/CPdJTsMDSEYZXkX67AWvdwzhI/YdPYNW68Q1Tr1Rv2SsuUZydnjBg==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@eslint/core": "^1.2.1", + "@eslint/plugin-kit": "^0.7.2", + "@humanwhocodes/momoa": "^3.3.10", + "natural-compare": "^1.4.0" + }, + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + } + }, "node_modules/@eslint/object-schema": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-3.0.5.tgz", @@ -1157,6 +1174,16 @@ "url": "https://github.com/sponsors/nzakas" } }, + "node_modules/@humanwhocodes/momoa": { + "version": "3.3.12", + "resolved": "https://registry.npmjs.org/@humanwhocodes/momoa/-/momoa-3.3.12.tgz", + "integrity": "sha512-xS9xl4ieqTqhSZfKV3YXj/htwJCUxbgvkTVaK1fkESgrOzvoVSOTRQeQ8tTLOZUS0Csi2xqtJQXgVTRH5OEbHQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/@humanwhocodes/retry": { "version": "0.4.3", "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.3.tgz", diff --git a/package.json b/package.json index ae60f1a..90f0cbe 100644 --- a/package.json +++ b/package.json @@ -46,6 +46,7 @@ }, "devDependencies": { "@eslint/js": "^10.0.1", + "@eslint/json": "^2.1.0", "@jest/globals": "^30.4.1", "@rollup/plugin-commonjs": "^29.0.3", "@rollup/plugin-json": "^6.1.0",