diff --git a/.changeset/shiny-dodos-relax.md b/.changeset/shiny-dodos-relax.md new file mode 100644 index 00000000..34489b5b --- /dev/null +++ b/.changeset/shiny-dodos-relax.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-jsonc": minor +--- + +feat: add `jsonc/no-irregular-whitespace` rule diff --git a/README.md b/README.md index 039f02c0..aba4dc47 100644 --- a/README.md +++ b/README.md @@ -212,6 +212,7 @@ The rules with the following star :star: are included in the config. | [jsonc/key-spacing](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/key-spacing.html) | enforce consistent spacing between keys and values in object literal properties | :wrench: | | | | | [jsonc/no-dupe-keys](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-dupe-keys.html) | disallow duplicate keys in object literals | | :star: | :star: | :star: | | [jsonc/no-floating-decimal](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-floating-decimal.html) | disallow leading or trailing decimal points in numeric literals | :wrench: | :star: | :star: | | +| [jsonc/no-irregular-whitespace](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-irregular-whitespace.html) | disallow irregular whitespace | | | | | | [jsonc/no-multi-str](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-multi-str.html) | disallow multiline strings | | :star: | :star: | | | [jsonc/no-octal-escape](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-octal-escape.html) | disallow octal escape sequences in string literals | | | | | | [jsonc/no-octal](https://ota-meshi.github.io/eslint-plugin-jsonc/rules/no-octal.html) | disallow legacy octal literals | | :star: | :star: | :star: | diff --git a/docs/rules/README.md b/docs/rules/README.md index ef505209..246f8666 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -50,6 +50,7 @@ The rules with the following star :star: are included in the `plugin:jsonc/recom | [jsonc/key-spacing](./key-spacing.md) | enforce consistent spacing between keys and values in object literal properties | :wrench: | | | | | [jsonc/no-dupe-keys](./no-dupe-keys.md) | disallow duplicate keys in object literals | | :star: | :star: | :star: | | [jsonc/no-floating-decimal](./no-floating-decimal.md) | disallow leading or trailing decimal points in numeric literals | :wrench: | :star: | :star: | | +| [jsonc/no-irregular-whitespace](./no-irregular-whitespace.md) | disallow irregular whitespace | | | | | | [jsonc/no-multi-str](./no-multi-str.md) | disallow multiline strings | | :star: | :star: | | | [jsonc/no-octal-escape](./no-octal-escape.md) | disallow octal escape sequences in string literals | | | | | | [jsonc/no-octal](./no-octal.md) | disallow legacy octal literals | | :star: | :star: | :star: | diff --git a/docs/rules/key-spacing.md b/docs/rules/key-spacing.md index b888bb17..5656515e 100644 --- a/docs/rules/key-spacing.md +++ b/docs/rules/key-spacing.md @@ -47,7 +47,7 @@ This rule enforces consistent spacing between keys and values in object literal } ``` -Same as [key-spacing] rule option. See [here](https://eslint.org/docs/rules/key-spacing#options) for details. +Same as [key-spacing] rule option. See [here](https://eslint.org/docs/rules/key-spacing#options) for details. ## :couple: Related rules diff --git a/docs/rules/no-irregular-whitespace.md b/docs/rules/no-irregular-whitespace.md new file mode 100644 index 00000000..f5a148b8 --- /dev/null +++ b/docs/rules/no-irregular-whitespace.md @@ -0,0 +1,76 @@ +--- +pageClass: "rule-details" +sidebarDepth: 0 +title: "jsonc/no-irregular-whitespace" +description: "disallow irregular whitespace" +--- + +# jsonc/no-irregular-whitespace + +> disallow irregular whitespace + +- :exclamation: **_This rule has not been released yet._** + +## :book: Rule Details + +This rule is aimed at catching invalid whitespace that is not a normal tab and space. + + + + + +```json +/* eslint jsonc/no-irregular-whitespace: 'error' */ +{ + /* ✓ GOOD */ + "GOOD": " ", + + /* ✗ BAD */ + "BAD": "foo", +} +``` + + + +ESLint core [no-irregular-whitespace] rule don't work well in JSON. Turn off that rule in JSON files and use `jsonc/no-irregular-whitespace` rule. + +## :wrench: Options + +Nothing. + +```json +{ + "overrides": [ + { + "files": ["*.json", "*.json5"], + "rules": { + "no-irregular-whitespace": "off", + "jsonc/no-irregular-whitespace": [ + "error", + { + "skipStrings": true, + "skipComments": false, + "skipRegExps": false, + "skipTemplates": false + } + ] + } + } + ] +} +``` + +Same as [no-irregular-whitespace] rule option. See [here](https://eslint.org/docs/rules/no-irregular-whitespace#options) for details. + +## :couple: Related rules + +- [no-irregular-whitespace] + +[no-irregular-whitespace]: https://eslint.org/docs/rules/no-irregular-whitespace + +## :mag: Implementation + +- [Rule source](/~https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/lib/rules/no-irregular-whitespace.ts) +- [Test source](/~https://github.com/ota-meshi/eslint-plugin-jsonc/blob/master/tests/lib/rules/no-irregular-whitespace.ts) + +Taken with ❤️ [from ESLint core](https://eslint.org/docs/rules/no-irregular-whitespace) diff --git a/lib/rules/no-irregular-whitespace.ts b/lib/rules/no-irregular-whitespace.ts new file mode 100644 index 00000000..17d76c99 --- /dev/null +++ b/lib/rules/no-irregular-whitespace.ts @@ -0,0 +1,23 @@ +import { createRule, defineWrapperListener, getCoreRule } from "../utils"; +const coreRule = getCoreRule("no-irregular-whitespace"); + +export default createRule("no-irregular-whitespace", { + meta: { + docs: { + description: "disallow irregular whitespace", + // TODO: We will switch this in the next major version. + recommended: null, + // recommended: ["json", "jsonc", "json5"], // TODO: We need to turn off core `no-irregular-whitespace` rule in shared config. + extensionRule: true, + layout: false, + }, + fixable: coreRule.meta?.fixable, + hasSuggestions: (coreRule.meta as any)?.hasSuggestions, + schema: coreRule.meta!.schema!, + messages: coreRule.meta!.messages!, + type: coreRule.meta!.type!, + }, + create(context) { + return defineWrapperListener(coreRule, context, context.options); + }, +}); diff --git a/lib/utils/index.ts b/lib/utils/index.ts index e76f1a24..f533bc32 100644 --- a/lib/utils/index.ts +++ b/lib/utils/index.ts @@ -81,7 +81,7 @@ export function defineWrapperListener( continue; } const jsonKey = key.replace( - /(?:^|\b)(ExpressionStatement|ArrayExpression|ObjectExpression|Property|Identifier|Literal|UnaryExpression)(?:\b|$)/gu, + /(?:^|\b)(ExpressionStatement|(?:Template)?Literal|(?:Array|Object|Unary)Expression|Property|Identifier|TemplateElement)(?:\b|$)/gu, "JSON$1" ); jsonListener[jsonKey] = function (node: AST.JSONNode, ...args) { diff --git a/lib/utils/rules.ts b/lib/utils/rules.ts index c3b4800a..47518229 100644 --- a/lib/utils/rules.ts +++ b/lib/utils/rules.ts @@ -17,6 +17,7 @@ import noEscapeSequenceInIdentifier from "../rules/no-escape-sequence-in-identif import noFloatingDecimal from "../rules/no-floating-decimal"; import noHexadecimalNumericLiterals from "../rules/no-hexadecimal-numeric-literals"; import noInfinity from "../rules/no-infinity"; +import noIrregularWhitespace from "../rules/no-irregular-whitespace"; import noMultiStr from "../rules/no-multi-str"; import noNan from "../rules/no-nan"; import noNumberProps from "../rules/no-number-props"; @@ -62,6 +63,7 @@ export const rules = [ noFloatingDecimal, noHexadecimalNumericLiterals, noInfinity, + noIrregularWhitespace, noMultiStr, noNan, noNumberProps, diff --git a/tests/lib/rules/no-irregular-whitespace.ts b/tests/lib/rules/no-irregular-whitespace.ts new file mode 100644 index 00000000..a29ee4fb --- /dev/null +++ b/tests/lib/rules/no-irregular-whitespace.ts @@ -0,0 +1,233 @@ +import { RuleTester } from "eslint"; +import rule from "../../../lib/rules/no-irregular-whitespace"; + +const tester = new RuleTester({ + parser: require.resolve("jsonc-eslint-parser"), + parserOptions: { + ecmaVersion: 2020, + }, +}); + +tester.run("no-irregular-whitespace", rule as any, { + valid: [ + `"\u0020"`, + `"\u0009"`, + `"\\u000B"`, + `["\u0009"]`, + `["\\u000B"]`, + `{"\u0009": "\u0009"}`, + `{"\\u000B": "\\u000B"}`, + + // String + `"\u000B"`, + `["\u000B"]`, + `{"\u000B": "\u000B"}`, + { + code: `"\u000B"`, + options: [{ skipStrings: true }], + }, + { + code: `["\u000B"]`, + options: [{ skipStrings: true }], + }, + { + code: `{"\u000B": "\u000B"}`, + options: [{ skipStrings: true }], + }, + // Templates + { + code: `\`\u000B\``, + options: [{ skipTemplates: true }], + }, + { + code: `[\`\u000B\`]`, + options: [{ skipTemplates: true }], + }, + { + code: `{"\u000B": \`\u000B\`}`, + options: [{ skipTemplates: true }], + }, + // RegExps + { + code: `/\u000B/`, + options: [{ skipRegExps: true }], + }, + { + code: `[/\u000B/]`, + options: [{ skipRegExps: true }], + }, + { + code: `{"\u000B": /\u000B/}`, + options: [{ skipRegExps: true }], + }, + // Comments + { + code: `{} // \u000B`, + options: [{ skipComments: true }], + }, + ], + invalid: [ + { + code: `{"\u000B": [\`\u000B\`, /\u000B/]} \u000B // \u000B`, + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 9, + }, + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 14, + }, + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 19, + }, + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 24, + }, + ], + }, + { + code: `{"\u000B": [\`\u000B\`, /\u000B/]} \u000B // \u000B`, + options: [ + { + skipStrings: true, + skipComments: true, + skipRegExps: true, + skipTemplates: true, + }, + ], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 19, + }, + ], + }, + // String + { + code: `{"\u000B": "\u000B"}`, + options: [{ skipStrings: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 3, + }, + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 8, + }, + ], + }, + { + code: `["\u000B"]`, + options: [{ skipStrings: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 3, + }, + ], + }, + { + code: `"\u000B"`, + options: [{ skipStrings: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 2, + }, + ], + }, + // Templates + { + code: `\`\u000B\``, + options: [{ skipTemplates: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 2, + }, + ], + }, + { + code: `[\`\u000B\`]`, + options: [{ skipTemplates: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 3, + }, + ], + }, + { + code: `{"\u000B": \`\u000B\`}`, + options: [{ skipTemplates: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 8, + }, + ], + }, + // RegExps + { + code: `/\u000B/`, + options: [{ skipRegExps: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 2, + }, + ], + }, + { + code: `[/\u000B/]`, + options: [{ skipRegExps: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 3, + }, + ], + }, + { + code: `{"\u000B": /\u000B/}`, + options: [{ skipRegExps: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 8, + }, + ], + }, + // Comments + { + code: `{} // \u000B`, + options: [{ skipComments: false }], + errors: [ + { + message: "Irregular whitespace not allowed.", + line: 1, + column: 7, + }, + ], + }, + ], +});