From 59d4365e62d022dbea21352b0c1d869b84a52b75 Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Mon, 18 Mar 2024 22:15:14 +0900 Subject: [PATCH 1/3] Add support for flat config --- .devcontainer/devcontainer.json | 6 +-- .eslintrc.js | 17 ++++++ README.md | 38 +++++++++++++- docs/user-guide/index.md | 38 +++++++++++++- lib/configs/flat/all.ts | 16 ++++++ lib/configs/flat/base.ts | 29 ++++++++++ lib/configs/flat/prettier.ts | 26 +++++++++ lib/configs/flat/recommended-with-json.ts | 41 +++++++++++++++ lib/configs/flat/recommended-with-json5.ts | 30 +++++++++++ lib/configs/flat/recommended-with-jsonc.ts | 39 ++++++++++++++ lib/index.ts | 12 +++++ package.json | 2 +- tests/lib/configs/recommended-with-json.ts | 61 ++++++++++++++++++++++ tests/lib/test-lib/eslint-compat.ts | 4 +- tools/update-rulesets.ts | 39 ++++++++++++++ tsconfig.json | 8 +-- 16 files changed, 394 insertions(+), 12 deletions(-) create mode 100644 lib/configs/flat/all.ts create mode 100644 lib/configs/flat/base.ts create mode 100644 lib/configs/flat/prettier.ts create mode 100644 lib/configs/flat/recommended-with-json.ts create mode 100644 lib/configs/flat/recommended-with-json5.ts create mode 100644 lib/configs/flat/recommended-with-jsonc.ts create mode 100644 tests/lib/configs/recommended-with-json.ts diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index f95d8f1a..20482296 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -17,9 +17,9 @@ // Configure tool-specific properties. "customizations": { "vscode": { - "extensions": ["dbaeumer.vscode-eslint"], - }, - }, + "extensions": ["dbaeumer.vscode-eslint"] + } + } // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. // "remoteUser": "root" diff --git a/.eslintrc.js b/.eslintrc.js index 0d66c88e..d0bb9392 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -104,6 +104,12 @@ module.exports = { project: "./tsconfig.json", }, }, + { + files: ["*.md/**", "**/*.md/**"], + rules: { + "n/no-missing-import": "off", + }, + }, { files: ["scripts/**/*.ts", "tests/**/*.ts", "tests-integrations/**/*.ts"], rules: { @@ -113,5 +119,16 @@ module.exports = { "@typescript-eslint/no-misused-promises": "off", }, }, + { + files: ["docs/.vitepress/**/*.*"], + rules: { + "eslint-plugin/require-meta-docs-description": "off", + "eslint-plugin/require-meta-docs-url": "off", + "eslint-plugin/require-meta-type": "off", + "eslint-plugin/prefer-message-ids": "off", + "eslint-plugin/prefer-object-rule": "off", + "eslint-plugin/require-meta-schema": "off", + }, + }, ], }; diff --git a/README.md b/README.md index aba4dc47..a60c66ab 100644 --- a/README.md +++ b/README.md @@ -89,7 +89,43 @@ npm install --save-dev eslint eslint-plugin-jsonc ### Configuration -Use `.eslintrc.*` file to configure rules. See also: [https://eslint.org/docs/user-guide/configuring](https://eslint.org/docs/user-guide/configuring). +#### New (ESLint>=v9) Config (Flat Config) + +Use `eslint.config.js` file to configure rules. See also: . + +Example **eslint.config.js**: + +```mjs +import eslintPluginJsonc from 'eslint-plugin-jsonc'; +export default [ + // add more generic rule sets here, such as: + // js.configs.recommended, + ...eslintPluginJsonc.configs['flat/recommended-with-jsonc'], + { + rules: { + // override/add rules settings here, such as: + // 'jsonc/rule-name': 'error' + } + } +]; +``` + +This plugin provides configs: + +- `*.configs['flat/base']` ... Configuration to enable correct JSON parsing. +- `*.configs['flat/recommended-with-json']` ... Recommended configuration for JSON. +- `*.configs['flat/recommended-with-jsonc']` ... Recommended configuration for JSONC. +- `*.configs['flat/recommended-with-json5']` ... Recommended configuration for JSON5. +- `*.configs['flat/prettier']` ... Turn off rules that may conflict with [Prettier](https://prettier.io/). +- `*.configs['flat/all']` ... Enables all rules. It's meant for testing, not for production use because it changes with every minor and major version of the plugin. Use it at your own risk. + +This plugin will parse `.json`, `.jsonc` and `.json5` by default using the configuration provided by the plugin (unless you already have a parser configured - see below). + +See [the rule list](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/) to get the `rules` that this plugin provides. + +#### Legacy Config (ESLint. Example **.eslintrc.js**: diff --git a/docs/user-guide/index.md b/docs/user-guide/index.md index a46ecc11..ba23eaf4 100644 --- a/docs/user-guide/index.md +++ b/docs/user-guide/index.md @@ -18,7 +18,43 @@ npm install --save-dev eslint eslint-plugin-jsonc ### Configuration -Use `.eslintrc.*` file to configure rules. See also: [https://eslint.org/docs/user-guide/configuring](https://eslint.org/docs/user-guide/configuring). +#### New (ESLint>=v9) Config (Flat Config) + +Use `eslint.config.js` file to configure rules. See also: . + +Example **eslint.config.js**: + +```mjs +import eslintPluginJsonc from 'eslint-plugin-jsonc'; +export default [ + // add more generic rule sets here, such as: + // js.configs.recommended, + ...eslintPluginJsonc.configs['flat/recommended-with-jsonc'], + { + rules: { + // override/add rules settings here, such as: + // 'jsonc/rule-name': 'error' + } + } +]; +``` + +This plugin provides configs: + +- `*.configs['flat/base']` ... Configuration to enable correct JSON parsing. +- `*.configs['flat/recommended-with-json']` ... Recommended configuration for JSON. +- `*.configs['flat/recommended-with-jsonc']` ... Recommended configuration for JSONC. +- `*.configs['flat/recommended-with-json5']` ... Recommended configuration for JSON5. +- `*.configs['flat/prettier']` ... Turn off rules that may conflict with [Prettier](https://prettier.io/). +- `*.configs['flat/all']` ... Enables all rules. It's meant for testing, not for production use because it changes with every minor and major version of the plugin. Use it at your own risk. + +This plugin will parse `.json`, `.jsonc` and `.json5` by default using the configuration provided by the plugin (unless you already have a parser configured - see below). + +See [the rule list](../rules/index.md) to get the `rules` that this plugin provides. + +#### Legacy Config (ESLint. Example **.eslintrc.js**: diff --git a/lib/configs/flat/all.ts b/lib/configs/flat/all.ts new file mode 100644 index 00000000..7f358a72 --- /dev/null +++ b/lib/configs/flat/all.ts @@ -0,0 +1,16 @@ +import { rules } from "../../utils/rules"; +import base from "./base"; +const all: Record = {}; +for (const rule of rules) { + if (rule.meta.docs.ruleId === "jsonc/sort-array-values") continue; + all[rule.meta.docs.ruleId] = "error"; +} + +export default [ + ...base, + { + rules: { + ...all, + }, + }, +]; diff --git a/lib/configs/flat/base.ts b/lib/configs/flat/base.ts new file mode 100644 index 00000000..9f75848e --- /dev/null +++ b/lib/configs/flat/base.ts @@ -0,0 +1,29 @@ +import type { ESLint } from "eslint"; +import * as parser from "jsonc-eslint-parser"; +export default [ + { + files: [ + "*.json", + "**/*.json", + "*.json5", + "**/*.json5", + "*.jsonc", + "**/*.jsonc", + ], + plugins: { + get jsonc(): ESLint.Plugin { + // eslint-disable-next-line @typescript-eslint/no-require-imports -- ignore + return require("../../index"); + }, + }, + languageOptions: { + parser, + }, + rules: { + // ESLint core rules known to cause problems with JSON. + strict: "off", + "no-unused-expressions": "off", + "no-unused-vars": "off", + }, + }, +]; diff --git a/lib/configs/flat/prettier.ts b/lib/configs/flat/prettier.ts new file mode 100644 index 00000000..1a05470f --- /dev/null +++ b/lib/configs/flat/prettier.ts @@ -0,0 +1,26 @@ +// IMPORTANT! +// This file has been automatically generated, +// in order to update its content execute "npm run update" +import base from "./base"; +export default [ + ...base, + { + rules: { + // eslint-plugin-jsonc rules + "jsonc/array-bracket-newline": "off", + "jsonc/array-bracket-spacing": "off", + "jsonc/array-element-newline": "off", + "jsonc/comma-dangle": "off", + "jsonc/comma-style": "off", + "jsonc/indent": "off", + "jsonc/key-spacing": "off", + "jsonc/no-floating-decimal": "off", + "jsonc/object-curly-newline": "off", + "jsonc/object-curly-spacing": "off", + "jsonc/object-property-newline": "off", + "jsonc/quote-props": "off", + "jsonc/quotes": "off", + "jsonc/space-unary-ops": "off", + }, + }, +]; diff --git a/lib/configs/flat/recommended-with-json.ts b/lib/configs/flat/recommended-with-json.ts new file mode 100644 index 00000000..8f64b30e --- /dev/null +++ b/lib/configs/flat/recommended-with-json.ts @@ -0,0 +1,41 @@ +// IMPORTANT! +// This file has been automatically generated, +// in order to update its content execute "npm run update" +import base from "./base"; +export default [ + ...base, + { + rules: { + // eslint-plugin-jsonc rules + "jsonc/comma-dangle": "error", + "jsonc/no-bigint-literals": "error", + "jsonc/no-binary-expression": "error", + "jsonc/no-binary-numeric-literals": "error", + "jsonc/no-comments": "error", + "jsonc/no-dupe-keys": "error", + "jsonc/no-escape-sequence-in-identifier": "error", + "jsonc/no-floating-decimal": "error", + "jsonc/no-hexadecimal-numeric-literals": "error", + "jsonc/no-infinity": "error", + "jsonc/no-multi-str": "error", + "jsonc/no-nan": "error", + "jsonc/no-number-props": "error", + "jsonc/no-numeric-separators": "error", + "jsonc/no-octal-numeric-literals": "error", + "jsonc/no-octal": "error", + "jsonc/no-parenthesized": "error", + "jsonc/no-plus-sign": "error", + "jsonc/no-regexp-literals": "error", + "jsonc/no-sparse-arrays": "error", + "jsonc/no-template-literals": "error", + "jsonc/no-undefined-value": "error", + "jsonc/no-unicode-codepoint-escapes": "error", + "jsonc/no-useless-escape": "error", + "jsonc/quote-props": "error", + "jsonc/quotes": "error", + "jsonc/space-unary-ops": "error", + "jsonc/valid-json-number": "error", + "jsonc/vue-custom-block/no-parsing-error": "error", + }, + }, +]; diff --git a/lib/configs/flat/recommended-with-json5.ts b/lib/configs/flat/recommended-with-json5.ts new file mode 100644 index 00000000..5bae83f0 --- /dev/null +++ b/lib/configs/flat/recommended-with-json5.ts @@ -0,0 +1,30 @@ +// IMPORTANT! +// This file has been automatically generated, +// in order to update its content execute "npm run update" +import base from "./base"; +export default [ + ...base, + { + rules: { + // eslint-plugin-jsonc rules + "jsonc/no-bigint-literals": "error", + "jsonc/no-binary-expression": "error", + "jsonc/no-binary-numeric-literals": "error", + "jsonc/no-dupe-keys": "error", + "jsonc/no-escape-sequence-in-identifier": "error", + "jsonc/no-number-props": "error", + "jsonc/no-numeric-separators": "error", + "jsonc/no-octal-numeric-literals": "error", + "jsonc/no-octal": "error", + "jsonc/no-parenthesized": "error", + "jsonc/no-regexp-literals": "error", + "jsonc/no-sparse-arrays": "error", + "jsonc/no-template-literals": "error", + "jsonc/no-undefined-value": "error", + "jsonc/no-unicode-codepoint-escapes": "error", + "jsonc/no-useless-escape": "error", + "jsonc/space-unary-ops": "error", + "jsonc/vue-custom-block/no-parsing-error": "error", + }, + }, +]; diff --git a/lib/configs/flat/recommended-with-jsonc.ts b/lib/configs/flat/recommended-with-jsonc.ts new file mode 100644 index 00000000..29887ca4 --- /dev/null +++ b/lib/configs/flat/recommended-with-jsonc.ts @@ -0,0 +1,39 @@ +// IMPORTANT! +// This file has been automatically generated, +// in order to update its content execute "npm run update" +import base from "./base"; +export default [ + ...base, + { + rules: { + // eslint-plugin-jsonc rules + "jsonc/no-bigint-literals": "error", + "jsonc/no-binary-expression": "error", + "jsonc/no-binary-numeric-literals": "error", + "jsonc/no-dupe-keys": "error", + "jsonc/no-escape-sequence-in-identifier": "error", + "jsonc/no-floating-decimal": "error", + "jsonc/no-hexadecimal-numeric-literals": "error", + "jsonc/no-infinity": "error", + "jsonc/no-multi-str": "error", + "jsonc/no-nan": "error", + "jsonc/no-number-props": "error", + "jsonc/no-numeric-separators": "error", + "jsonc/no-octal-numeric-literals": "error", + "jsonc/no-octal": "error", + "jsonc/no-parenthesized": "error", + "jsonc/no-plus-sign": "error", + "jsonc/no-regexp-literals": "error", + "jsonc/no-sparse-arrays": "error", + "jsonc/no-template-literals": "error", + "jsonc/no-undefined-value": "error", + "jsonc/no-unicode-codepoint-escapes": "error", + "jsonc/no-useless-escape": "error", + "jsonc/quote-props": "error", + "jsonc/quotes": "error", + "jsonc/space-unary-ops": "error", + "jsonc/valid-json-number": "error", + "jsonc/vue-custom-block/no-parsing-error": "error", + }, + }, +]; diff --git a/lib/index.ts b/lib/index.ts index 350e1c5f..e22757fb 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -7,6 +7,12 @@ import recommendedWithJsonc from "./configs/recommended-with-jsonc"; import recommendedWithJson5 from "./configs/recommended-with-json5"; import prettier from "./configs/prettier"; import all from "./configs/all"; +import flatBase from "./configs/base"; +import flatRecommendedWithJson from "./configs/flat/recommended-with-json"; +import flatRecommendedWithJsonc from "./configs/flat/recommended-with-jsonc"; +import flatRecommendedWithJson5 from "./configs/flat/recommended-with-json5"; +import flatPrettier from "./configs/flat/prettier"; +import flatAll from "./configs/all"; import * as meta from "./meta"; // backward compatibility @@ -26,6 +32,12 @@ const configs = { "recommended-with-json5": recommendedWithJson5, prettier, all, + "flat/base": flatBase, + "flat/recommended-with-json": flatRecommendedWithJson, + "flat/recommended-with-jsonc": flatRecommendedWithJsonc, + "flat/recommended-with-json5": flatRecommendedWithJson5, + "flat/prettier": flatPrettier, + "flat/all": flatAll, }; const rules = ruleList.reduce( diff --git a/package.json b/package.json index 4f3e383e..968fd12a 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,7 @@ "homepage": "https://ota-meshi.github.io/eslint-plugin-jsonc/", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", - "eslint-compat-utils": "^0.4.0", + "eslint-compat-utils": "^0.5.0", "espree": "^9.6.1", "graphemer": "^1.4.0", "jsonc-eslint-parser": "^2.0.4", diff --git a/tests/lib/configs/recommended-with-json.ts b/tests/lib/configs/recommended-with-json.ts new file mode 100644 index 00000000..e5dd44aa --- /dev/null +++ b/tests/lib/configs/recommended-with-json.ts @@ -0,0 +1,61 @@ +import assert from "assert"; +import plugin from "../../../lib/index"; +import { LegacyESLint, ESLint } from "../test-lib/eslint-compat"; + +const code = `{ foo: 42 }`; +describe("`recommended-with-json` config", () => { + it("legacy `recommended-with-json` config should work. ", async () => { + const linter = new LegacyESLint({ + plugins: { + svelte: plugin as never, + }, + baseConfig: { + parserOptions: { + ecmaVersion: 2020, + }, + extends: ["plugin:jsonc/recommended-with-json"], + }, + useEslintrc: false, + }); + const result = await linter.lintText(code, { filePath: "test.json" }); + const messages = result[0].messages; + + assert.deepStrictEqual( + messages.map((m) => ({ + ruleId: m.ruleId, + line: m.line, + message: m.message, + })), + [ + { + message: "Unquoted property 'foo' found.", + ruleId: "jsonc/quote-props", + line: 1, + }, + ], + ); + }); + it("`flat/recommended-with-json` config should work. ", async () => { + const linter = new ESLint({ + overrideConfigFile: true as never, + overrideConfig: plugin.configs["flat/recommended-with-json"] as never, + }); + const result = await linter.lintText(code, { filePath: "test.json" }); + const messages = result[0].messages; + + assert.deepStrictEqual( + messages.map((m) => ({ + ruleId: m.ruleId, + line: m.line, + message: m.message, + })), + [ + { + message: "Unquoted property 'foo' found.", + ruleId: "jsonc/quote-props", + line: 1, + }, + ], + ); + }); +}); diff --git a/tests/lib/test-lib/eslint-compat.ts b/tests/lib/test-lib/eslint-compat.ts index 85a15a37..73753b92 100644 --- a/tests/lib/test-lib/eslint-compat.ts +++ b/tests/lib/test-lib/eslint-compat.ts @@ -1,6 +1,6 @@ import { getRuleTester } from "eslint-compat-utils/rule-tester"; -import { getLegacyESLint } from "eslint-compat-utils/eslint"; +import { getLegacyESLint, getESLint } from "eslint-compat-utils/eslint"; export const RuleTester = getRuleTester(); - export const LegacyESLint = getLegacyESLint(); +export const ESLint = getESLint(); diff --git a/tools/update-rulesets.ts b/tools/update-rulesets.ts index b4f4130c..f2757464 100644 --- a/tools/update-rulesets.ts +++ b/tools/update-rulesets.ts @@ -96,3 +96,42 @@ export = { // Update file. fs.writeFileSync(filePath, content); } + +for (const rec of ["json", "jsonc", "json5", "prettier"] as const) { + let content = `/* + * IMPORTANT! + * This file has been automatically generated, + * in order to update its content execute "npm run update" + */ +import base from './base'; +export default [ + ...base, + { + rules: { + // eslint-plugin-jsonc rules + ${rules + .filter(CONFIGS[rec].filter) + .map((rule) => { + return `"${rule.meta.docs.ruleId}": "${CONFIGS[rec].option(rule)}"`; + }) + .join(",\n")} + }, + } +] +`; + + const filePath = path.resolve( + __dirname, + `../lib/configs/flat/${CONFIGS[rec].config}.ts`, + ); + + if (isWin) { + content = content + .replace(/\r?\n/gu, "\n") + .replace(/\r/gu, "\n") + .replace(/\n/gu, "\r\n"); + } + + // Update file. + fs.writeFileSync(filePath, content); +} diff --git a/tsconfig.json b/tsconfig.json index 45001cbf..0815140d 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,12 +15,12 @@ "noFallthroughCasesInSwitch": true, "baseUrl": ".", "paths": { - "*": ["typings/*"], + "*": ["typings/*"] }, "esModuleInterop": true, "resolveJsonModule": true, - "skipLibCheck": true, + "skipLibCheck": true }, "include": [ "lib/**/*", @@ -28,7 +28,7 @@ "tests-integrations/lib/**/*", "tools/**/*", "typings/**/*", - "docs/.vitepress/**/*", + "docs/.vitepress/**/*" ], - "exclude": ["dist/**/*"], + "exclude": ["dist/**/*"] } From c39fb219a800ba8a2099a0562dd9dc5a1f2e74c7 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Mon, 18 Mar 2024 22:16:19 +0900 Subject: [PATCH 2/3] Create famous-countries-applaud.md --- .changeset/famous-countries-applaud.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/famous-countries-applaud.md diff --git a/.changeset/famous-countries-applaud.md b/.changeset/famous-countries-applaud.md new file mode 100644 index 00000000..9824e534 --- /dev/null +++ b/.changeset/famous-countries-applaud.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-jsonc": minor +--- + +Add support for flat config From ba76591f89c7ac935068db730310a1d740dd3b9a Mon Sep 17 00:00:00 2001 From: ota-meshi Date: Mon, 18 Mar 2024 22:24:23 +0900 Subject: [PATCH 3/3] fix --- .eslintrc.for-vscode.js | 9 ++++++++ .vscode/settings.json | 3 +++ package.json | 1 + tests/lib/rules/no-useless-escape.ts | 33 +++++++++++++++++++++++----- 4 files changed, 40 insertions(+), 6 deletions(-) create mode 100644 .eslintrc.for-vscode.js diff --git a/.eslintrc.for-vscode.js b/.eslintrc.for-vscode.js new file mode 100644 index 00000000..bec1765c --- /dev/null +++ b/.eslintrc.for-vscode.js @@ -0,0 +1,9 @@ +module.exports = { + extends: [require.resolve("./.eslintrc.js")], + overrides: [ + { + files: ["tests/lib/rules/*"], + extends: ["plugin:eslint-rule-tester/recommended-legacy"], + }, + ], +}; diff --git a/.vscode/settings.json b/.vscode/settings.json index 9455e9b6..94cdb65f 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,6 +9,9 @@ "markdown", "yaml" ], + "eslint.options": { + "overrideConfigFile": "./.eslintrc.for-vscode.js" + }, "typescript.validate.enable": true, "javascript.validate.enable": false, "vetur.validation.script": false, diff --git a/package.json b/package.json index 968fd12a..6f51e235 100644 --- a/package.json +++ b/package.json @@ -99,6 +99,7 @@ "eslint-config-prettier": "^9.0.0", "eslint-plugin-eslint-comments": "^3.2.0", "eslint-plugin-eslint-plugin": "^5.0.0", + "eslint-plugin-eslint-rule-tester": "^0.5.1", "eslint-plugin-json-schema-validator": "^4.6.1", "eslint-plugin-jsonc": "^2.0.0", "eslint-plugin-markdown": "^3.0.0", diff --git a/tests/lib/rules/no-useless-escape.ts b/tests/lib/rules/no-useless-escape.ts index 2ea8477a..d095efa1 100644 --- a/tests/lib/rules/no-useless-escape.ts +++ b/tests/lib/rules/no-useless-escape.ts @@ -15,17 +15,38 @@ tester.run("no-useless-escape", rule as any, { { filename: "test.json", code: '"hol\\a"', - errors: ["Unnecessary escape character: \\a."], + errors: [ + { + message: "Unnecessary escape character: \\a.", + suggestions: [ + { messageId: "removeEscape", output: `"hola"` }, + { messageId: "escapeBackslash", output: String.raw`"hol\\a"` }, + ], + }, + ], }, { filename: "test.vue", code: `"hol\\a"`, - errors: ["Unnecessary escape character: \\a."], - ...({ - languageOptions: { - parser: vueParser, + errors: [ + { + message: "Unnecessary escape character: \\a.", + suggestions: [ + { + messageId: "removeEscape", + output: `"hola"`, + }, + { + messageId: "escapeBackslash", + output: String.raw`"hol\\a"`, + }, + ], }, - } as any), + ], + // @ts-expect-error + languageOptions: { + parser: vueParser, + }, }, ], });