From 4f1400a758d11c2857200b31dd9724aa82a95d22 Mon Sep 17 00:00:00 2001 From: fisker Cheung Date: Thu, 9 May 2024 21:57:59 +0800 Subject: [PATCH] Add `prefer-string-raw` rule (#2339) Co-authored-by: Sindre Sorhus --- docs/rules/prefer-string-raw.md | 30 +++ package.json | 2 + readme.md | 1 + rules/ast/index.js | 1 + rules/ast/is-directive.js | 7 + rules/no-empty-file.js | 3 +- rules/no-unnecessary-polyfills.js | 4 +- rules/prefer-string-raw.js | 93 +++++++++ rules/utils/escape-template-element-raw.js | 2 +- test/better-regex.mjs | 210 ++++++++++----------- test/escape-case.mjs | 160 ++++++++-------- test/expiring-todo-comments.mjs | 6 +- test/filename-case.mjs | 48 ++--- test/no-console-spaces.mjs | 4 +- test/no-hex-escape.mjs | 88 ++++----- test/prefer-string-raw.mjs | 40 ++++ test/prefer-string-replace-all.mjs | 44 ++--- test/prevent-abbreviations.mjs | 2 +- test/relative-url-style.mjs | 10 +- test/run-rules-on-codebase/lint.mjs | 6 + test/snapshots/prefer-string-raw.mjs.md | 89 +++++++++ test/snapshots/prefer-string-raw.mjs.snap | Bin 0 -> 446 bytes test/string-content.mjs | 22 +-- test/text-encoding-identifier-case.mjs | 2 +- 24 files changed, 571 insertions(+), 303 deletions(-) create mode 100644 docs/rules/prefer-string-raw.md create mode 100644 rules/ast/is-directive.js create mode 100644 rules/prefer-string-raw.js create mode 100644 test/prefer-string-raw.mjs create mode 100644 test/snapshots/prefer-string-raw.mjs.md create mode 100644 test/snapshots/prefer-string-raw.mjs.snap diff --git a/docs/rules/prefer-string-raw.md b/docs/rules/prefer-string-raw.md new file mode 100644 index 0000000000..c6e1f30daa --- /dev/null +++ b/docs/rules/prefer-string-raw.md @@ -0,0 +1,30 @@ +# Prefer using the `String.raw` tag to avoid escaping `\` + +๐Ÿ’ผ This rule is enabled in the โœ… `recommended` [config](https://github.com/sindresorhus/eslint-plugin-unicorn#preset-configs-eslintconfigjs). + +๐Ÿ”ง This rule is automatically fixable by the [`--fix` CLI option](https://eslint.org/docs/latest/user-guide/command-line-interface#--fix). + + + + +[`String.raw`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/raw) can be used to avoid escaping `\`. + +## Fail + +```js +const file = "C:\\windows\\style\\path\\to\\file.js"; +``` + +```js +const regexp = new RegExp('foo\\.bar'); +``` + +## Pass + +```js +const file = String.raw`C:\windows\style\path\to\file.js`; +``` + +```js +const regexp = new RegExp(String.raw`foo\.bar`); +``` diff --git a/package.json b/package.json index f0af8e4f5c..1740cdf419 100644 --- a/package.json +++ b/package.json @@ -124,7 +124,9 @@ "test/integration/{fixtures,fixtures-local}/**" ], "rules": { + "unicorn/escape-case": "off", "unicorn/expiring-todo-comments": "off", + "unicorn/no-hex-escape": "off", "unicorn/no-null": "error", "unicorn/prefer-array-flat": [ "error", diff --git a/readme.md b/readme.md index e10cde0bc5..86fa989b4a 100644 --- a/readme.md +++ b/readme.md @@ -207,6 +207,7 @@ If you don't use the preset, ensure you use the same `env` and `parserOptions` c | [prefer-set-has](docs/rules/prefer-set-has.md) | Prefer `Set#has()` over `Array#includes()` when checking for existence or non-existence. | โœ… | ๐Ÿ”ง | ๐Ÿ’ก | | [prefer-set-size](docs/rules/prefer-set-size.md) | Prefer using `Set#size` instead of `Array#length`. | โœ… | ๐Ÿ”ง | | | [prefer-spread](docs/rules/prefer-spread.md) | Prefer the spread operator over `Array.from(โ€ฆ)`, `Array#concat(โ€ฆ)`, `Array#{slice,toSpliced}()` and `String#split('')`. | โœ… | ๐Ÿ”ง | ๐Ÿ’ก | +| [prefer-string-raw](docs/rules/prefer-string-raw.md) | Prefer using the `String.raw` tag to avoid escaping `\`. | โœ… | ๐Ÿ”ง | | | [prefer-string-replace-all](docs/rules/prefer-string-replace-all.md) | Prefer `String#replaceAll()` over regex searches with the global flag. | โœ… | ๐Ÿ”ง | | | [prefer-string-slice](docs/rules/prefer-string-slice.md) | Prefer `String#slice()` over `String#substr()` and `String#substring()`. | โœ… | ๐Ÿ”ง | | | [prefer-string-starts-ends-with](docs/rules/prefer-string-starts-ends-with.md) | Prefer `String#startsWith()` & `String#endsWith()` over `RegExp#test()`. | โœ… | ๐Ÿ”ง | ๐Ÿ’ก | diff --git a/rules/ast/index.js b/rules/ast/index.js index 1799d515f3..a75e53f0cc 100644 --- a/rules/ast/index.js +++ b/rules/ast/index.js @@ -25,6 +25,7 @@ module.exports = { isArrowFunctionBody: require('./is-arrow-function-body.js'), isCallExpression, isCallOrNewExpression, + isDirective: require('./is-directive.js'), isEmptyNode: require('./is-empty-node.js'), isExpressionStatement: require('./is-expression-statement.js'), isFunction: require('./is-function.js'), diff --git a/rules/ast/is-directive.js b/rules/ast/is-directive.js new file mode 100644 index 0000000000..3de65a0eba --- /dev/null +++ b/rules/ast/is-directive.js @@ -0,0 +1,7 @@ +'use strict'; + +const isDirective = node => + node.type === 'ExpressionStatement' + && typeof node.directive === 'string'; + +module.exports = isDirective; diff --git a/rules/no-empty-file.js b/rules/no-empty-file.js index f1711a3959..f833886e05 100644 --- a/rules/no-empty-file.js +++ b/rules/no-empty-file.js @@ -1,12 +1,11 @@ 'use strict'; -const {isEmptyNode} = require('./ast/index.js'); +const {isEmptyNode, isDirective} = require('./ast/index.js'); const MESSAGE_ID = 'no-empty-file'; const messages = { [MESSAGE_ID]: 'Empty files are not allowed.', }; -const isDirective = node => node.type === 'ExpressionStatement' && typeof node.directive === 'string'; const isEmpty = node => isEmptyNode(node, isDirective); const isTripleSlashDirective = node => diff --git a/rules/no-unnecessary-polyfills.js b/rules/no-unnecessary-polyfills.js index 6f2d09a835..7ab4e904d0 100644 --- a/rules/no-unnecessary-polyfills.js +++ b/rules/no-unnecessary-polyfills.js @@ -23,13 +23,13 @@ const additionalPolyfillPatterns = { const prefixes = '(mdn-polyfills/|polyfill-)'; const suffixes = '(-polyfill)'; -const delimiter = '(\\.|-|\\.prototype\\.|/)?'; +const delimiter = String.raw`(\.|-|\.prototype\.|/)?`; const polyfills = Object.keys(compatData).map(feature => { let [ecmaVersion, constructorName, methodName = ''] = feature.split('.'); if (ecmaVersion === 'es') { - ecmaVersion = '(es\\d*)'; + ecmaVersion = String.raw`(es\d*)`; } constructorName = `(${constructorName}|${camelCase(constructorName)})`; diff --git a/rules/prefer-string-raw.js b/rules/prefer-string-raw.js new file mode 100644 index 0000000000..4241e11163 --- /dev/null +++ b/rules/prefer-string-raw.js @@ -0,0 +1,93 @@ +'use strict'; +const {isStringLiteral, isDirective} = require('./ast/index.js'); +const {fixSpaceAroundKeyword} = require('./fix/index.js'); + +const MESSAGE_ID = 'prefer-string-raw'; +const messages = { + [MESSAGE_ID]: '`String.raw` should be used to avoid escaping `\\`.', +}; + +const BACKSLASH = '\\'; + +function unescapeBackslash(raw) { + const quote = raw.charAt(0); + + raw = raw.slice(1, -1); + + let result = ''; + for (let position = 0; position < raw.length; position++) { + const character = raw[position]; + if (character === BACKSLASH) { + const nextCharacter = raw[position + 1]; + if (nextCharacter === BACKSLASH || nextCharacter === quote) { + result += nextCharacter; + position++; + continue; + } + } + + result += character; + } + + return result; +} + +/** @param {import('eslint').Rule.RuleContext} context */ +const create = context => { + context.on('Literal', node => { + if ( + !isStringLiteral(node) + || isDirective(node.parent) + || ( + ( + node.parent.type === 'ImportDeclaration' + || node.parent.type === 'ExportNamedDeclaration' + || node.parent.type === 'ExportAllDeclaration' + ) && node.parent.source === node + ) + || (node.parent.type === 'Property' && !node.parent.computed && node.parent.key === node) + || (node.parent.type === 'JSXAttribute' && node.parent.value === node) + ) { + return; + } + + const {raw} = node; + if ( + raw.at(-2) === BACKSLASH + || !raw.includes(BACKSLASH + BACKSLASH) + || raw.includes('`') + || raw.includes('${') + || node.loc.start.line !== node.loc.end.line + ) { + return; + } + + const unescaped = unescapeBackslash(raw); + if (unescaped !== node.value) { + return; + } + + return { + node, + messageId: MESSAGE_ID, + * fix(fixer) { + yield fixer.replaceText(node, `String.raw\`${unescaped}\``); + yield * fixSpaceAroundKeyword(fixer, node, context.sourceCode); + }, + }; + }); +}; + +/** @type {import('eslint').Rule.RuleModule} */ +module.exports = { + create, + meta: { + type: 'suggestion', + docs: { + description: 'Prefer using the `String.raw` tag to avoid escaping `\\`.', + recommended: true, + }, + fixable: 'code', + messages, + }, +}; diff --git a/rules/utils/escape-template-element-raw.js b/rules/utils/escape-template-element-raw.js index 14bc08c40a..8612a449fc 100644 --- a/rules/utils/escape-template-element-raw.js +++ b/rules/utils/escape-template-element-raw.js @@ -2,6 +2,6 @@ const escapeTemplateElementRaw = string => string.replaceAll( /(?<=(?:^|[^\\])(?:\\\\)*)(?(?:`|\$(?={)))/g, - '\\$', + String.raw`\$`, ); module.exports = escapeTemplateElementRaw; diff --git a/test/better-regex.mjs b/test/better-regex.mjs index e976120a54..69182a0900 100644 --- a/test/better-regex.mjs +++ b/test/better-regex.mjs @@ -29,23 +29,23 @@ const testCase = (original, optimized) => ({ test({ valid: [ // Literal regex - 'const foo = /\\d/', - 'const foo = /\\W/i', - 'const foo = /\\w/gi', + String.raw`const foo = /\d/`, + String.raw`const foo = /\W/i`, + String.raw`const foo = /\w/gi`, 'const foo = /[a-z]/gi', - 'const foo = /\\d*?/gi', + String.raw`const foo = /\d*?/gi`, // Should not crash ESLint (#446 and #448) - '/\\{\\{verificationUrl\\}\\}/gu', - '/^test-(?[a-zA-Z-\\d]+)$/u', + String.raw`/\{\{verificationUrl\}\}/gu`, + String.raw`/^test-(?[a-zA-Z-\d]+)$/u`, String.raw`/[\p{Script_Extensions=Greek}--ฯ€]/v`, // Should not suggest wrong regex (#447) - '/(\\s|\\.|@|_|-)/u', - '/[\\s.@_-]/u', + String.raw`/(\s|\.|@|_|-)/u`, + String.raw`/[\s.@_-]/u`, // Should not remove repeating patterns too easily (#769) - '/http:\\/\\/[^/]+\\/pull\\/commits/gi', + String.raw`/http:\/\/[^/]+\/pull\/commits/gi`, { code: '/[GgHhIiรฅ.Z:a-f"0-8%A*รค]/', @@ -53,14 +53,14 @@ test({ }, // `RegExp()` constructor - 'new RegExp(\'\\d\')', - 'new RegExp(\'\\d\', \'ig\')', - 'new RegExp(\'\\d*?\')', + String.raw`new RegExp('\d')`, + String.raw`new RegExp('\d', 'ig')`, + String.raw`new RegExp('\d*?')`, 'new RegExp(\'[a-z]\', \'i\')', - 'new RegExp(/\\d/)', - 'new RegExp(/\\d/gi)', - 'new RegExp(/\\d/, \'ig\')', - 'new RegExp(/\\d*?/)', + String.raw`new RegExp(/\d/)`, + String.raw`new RegExp(/\d/gi)`, + String.raw`new RegExp(/\d/, 'ig')`, + String.raw`new RegExp(/\d*?/)`, 'new RegExp(/[a-z]/, \'i\')', // Not `new` 'RegExp("[0-9]")', @@ -79,105 +79,105 @@ test({ '/[ ;-]/g', // #994 - '/\\s?\\s?/', // https://github.com/DmitrySoshnikov/regexp-tree/issues/216#issuecomment-762073297 - '/\\s{0,2}/', + String.raw`/\s?\s?/`, // https://github.com/DmitrySoshnikov/regexp-tree/issues/216#issuecomment-762073297 + String.raw`/\s{0,2}/`, ], invalid: [ // Literal regex { - code: 'const foo = /\\w/ig', - errors: createError('/\\w/ig', '/\\w/gi'), - output: 'const foo = /\\w/gi', + code: String.raw`const foo = /\w/ig`, + errors: createError(String.raw`/\w/ig`, String.raw`/\w/gi`), + output: String.raw`const foo = /\w/gi`, }, { code: 'const foo = /[0-9]/', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = /\\d/', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = /\d/`, }, { code: 'const foo = /[0-9]/ig', - errors: createError('/[0-9]/ig', '/\\d/gi'), - output: 'const foo = /\\d/gi', + errors: createError('/[0-9]/ig', String.raw`/\d/gi`), + output: String.raw`const foo = /\d/gi`, }, { code: 'const foo = /[^0-9]/', - errors: createError('/[^0-9]/', '/\\D/'), - output: 'const foo = /\\D/', + errors: createError('/[^0-9]/', String.raw`/\D/`), + output: String.raw`const foo = /\D/`, }, { code: 'const foo = /[A-Za-z0-9_]/', - errors: createError('/[A-Za-z0-9_]/', '/\\w/'), - output: 'const foo = /\\w/', + errors: createError('/[A-Za-z0-9_]/', String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { - code: 'const foo = /[A-Za-z\\d_]/', - errors: createError('/[A-Za-z\\d_]/', '/\\w/'), - output: 'const foo = /\\w/', + code: String.raw`const foo = /[A-Za-z\d_]/`, + errors: createError(String.raw`/[A-Za-z\d_]/`, String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { code: 'const foo = /[a-zA-Z0-9_]/', - errors: createError('/[a-zA-Z0-9_]/', '/\\w/'), - output: 'const foo = /\\w/', + errors: createError('/[a-zA-Z0-9_]/', String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { - code: 'const foo = /[a-zA-Z\\d_]/', - errors: createError('/[a-zA-Z\\d_]/', '/\\w/'), - output: 'const foo = /\\w/', + code: String.raw`const foo = /[a-zA-Z\d_]/`, + errors: createError(String.raw`/[a-zA-Z\d_]/`, String.raw`/\w/`), + output: String.raw`const foo = /\w/`, }, { - code: 'const foo = /[A-Za-z0-9_]+[0-9]?\\.[A-Za-z0-9_]*/', - errors: createError('/[A-Za-z0-9_]+[0-9]?\\.[A-Za-z0-9_]*/', '/\\w+\\d?\\.\\w*/'), - output: 'const foo = /\\w+\\d?\\.\\w*/', + code: String.raw`const foo = /[A-Za-z0-9_]+[0-9]?\.[A-Za-z0-9_]*/`, + errors: createError(String.raw`/[A-Za-z0-9_]+[0-9]?\.[A-Za-z0-9_]*/`, String.raw`/\w+\d?\.\w*/`), + output: String.raw`const foo = /\w+\d?\.\w*/`, }, { code: 'const foo = /[a-z0-9_]/i', - errors: createError('/[a-z0-9_]/i', '/\\w/i'), - output: 'const foo = /\\w/i', + errors: createError('/[a-z0-9_]/i', String.raw`/\w/i`), + output: String.raw`const foo = /\w/i`, }, { - code: 'const foo = /[a-z\\d_]/i', - errors: createError('/[a-z\\d_]/i', '/\\w/i'), - output: 'const foo = /\\w/i', + code: String.raw`const foo = /[a-z\d_]/i`, + errors: createError(String.raw`/[a-z\d_]/i`, String.raw`/\w/i`), + output: String.raw`const foo = /\w/i`, }, { code: 'const foo = /[^A-Za-z0-9_]/', - errors: createError('/[^A-Za-z0-9_]/', '/\\W/'), - output: 'const foo = /\\W/', + errors: createError('/[^A-Za-z0-9_]/', String.raw`/\W/`), + output: String.raw`const foo = /\W/`, }, { - code: 'const foo = /[^A-Za-z\\d_]/', - errors: createError('/[^A-Za-z\\d_]/', '/\\W/'), - output: 'const foo = /\\W/', + code: String.raw`const foo = /[^A-Za-z\d_]/`, + errors: createError(String.raw`/[^A-Za-z\d_]/`, String.raw`/\W/`), + output: String.raw`const foo = /\W/`, }, { code: 'const foo = /[^a-z0-9_]/i', - errors: createError('/[^a-z0-9_]/i', '/\\W/i'), - output: 'const foo = /\\W/i', + errors: createError('/[^a-z0-9_]/i', String.raw`/\W/i`), + output: String.raw`const foo = /\W/i`, }, { - code: 'const foo = /[^a-z\\d_]/i', - errors: createError('/[^a-z\\d_]/i', '/\\W/i'), - output: 'const foo = /\\W/i', + code: String.raw`const foo = /[^a-z\d_]/i`, + errors: createError(String.raw`/[^a-z\d_]/i`, String.raw`/\W/i`), + output: String.raw`const foo = /\W/i`, }, { - code: 'const foo = /[^a-z\\d_]/ig', - errors: createError('/[^a-z\\d_]/ig', '/\\W/gi'), - output: 'const foo = /\\W/gi', + code: String.raw`const foo = /[^a-z\d_]/ig`, + errors: createError(String.raw`/[^a-z\d_]/ig`, String.raw`/\W/gi`), + output: String.raw`const foo = /\W/gi`, }, { - code: 'const foo = /[^\\d_a-z]/ig', - errors: createError('/[^\\d_a-z]/ig', '/\\W/gi'), - output: 'const foo = /\\W/gi', + code: String.raw`const foo = /[^\d_a-z]/ig`, + errors: createError(String.raw`/[^\d_a-z]/ig`, String.raw`/\W/gi`), + output: String.raw`const foo = /\W/gi`, }, { code: 'const foo = /[a-z0-9_]/', - errors: createError('/[a-z0-9_]/', '/[\\d_a-z]/'), - output: 'const foo = /[\\d_a-z]/', + errors: createError('/[a-z0-9_]/', String.raw`/[\d_a-z]/`), + output: String.raw`const foo = /[\d_a-z]/`, }, { code: 'const foo = /^by @([a-zA-Z0-9-]+)/', - errors: createError('/^by @([a-zA-Z0-9-]+)/', '/^by @([\\dA-Za-z-]+)/'), - output: 'const foo = /^by @([\\dA-Za-z-]+)/', + errors: createError('/^by @([a-zA-Z0-9-]+)/', String.raw`/^by @([\dA-Za-z-]+)/`), + output: String.raw`const foo = /^by @([\dA-Za-z-]+)/`, }, { code: '/[GgHhIiรฅ.Z:a-f"0-8%A*รค]/', @@ -188,100 +188,100 @@ test({ { code: '/[a0-9b]/', options: disableSortCharacterClassesOptions, - errors: createError('/[a0-9b]/', '/[a\\db]/'), - output: '/[a\\db]/', + errors: createError('/[a0-9b]/', String.raw`/[a\db]/`), + output: String.raw`/[a\db]/`, }, // `RegExp()` constructor { code: 'const foo = new RegExp(\'[0-9]\')', - errors: createError('[0-9]', '\\d'), - output: 'const foo = new RegExp(\'\\\\d\')', + errors: createError('[0-9]', String.raw`\d`), + output: String.raw`const foo = new RegExp('\\d')`, }, { code: 'const foo = new RegExp("[0-9]")', - errors: createError('[0-9]', '\\d'), - output: 'const foo = new RegExp("\\\\d")', + errors: createError('[0-9]', String.raw`\d`), + output: String.raw`const foo = new RegExp("\\d")`, }, { - code: 'const foo = new RegExp(\'\\\'[0-9]\\\'\')', - errors: createError('\'[0-9]\'', '\'\\d\''), - output: 'const foo = new RegExp(\'\\\'\\\\d\\\'\')', + code: String.raw`const foo = new RegExp('\'[0-9]\'')`, + errors: createError('\'[0-9]\'', String.raw`'\d'`), + output: String.raw`const foo = new RegExp('\'\\d\'')`, }, { code: 'const foo = new RegExp("\'[0-9]\'")', - errors: createError('\'[0-9]\'', '\'\\d\''), - output: 'const foo = new RegExp("\'\\\\d\'")', + errors: createError('\'[0-9]\'', String.raw`'\d'`), + output: String.raw`const foo = new RegExp("'\\d'")`, }, { code: 'const foo = new RegExp(\'[0-9]\', \'ig\')', - errors: createError('[0-9]', '\\d'), - output: 'const foo = new RegExp(\'\\\\d\', \'ig\')', + errors: createError('[0-9]', String.raw`\d`), + output: String.raw`const foo = new RegExp('\\d', 'ig')`, }, { code: 'const foo = new RegExp(/[0-9]/, \'ig\')', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, \'ig\')', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/, 'ig')`, }, { code: 'const foo = new RegExp(/^[^*]*[*]?$/)', - errors: createError('/^[^*]*[*]?$/', '/^[^*]*\\*?$/'), - output: 'const foo = new RegExp(/^[^*]*\\*?$/)', + errors: createError('/^[^*]*[*]?$/', String.raw`/^[^*]*\*?$/`), + output: String.raw`const foo = new RegExp(/^[^*]*\*?$/)`, }, // No `flags` { code: 'const foo = new RegExp(/[0-9]/)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/)', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/)`, }, // `flags` not `Literal` { code: 'const foo = new RegExp(/[0-9]/, ig)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, ig)', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/, ig)`, }, // `flags` not `string` { code: 'const foo = new RegExp(/[0-9]/, 0)', - errors: createError('/[0-9]/', '/\\d/'), - output: 'const foo = new RegExp(/\\d/, 0)', + errors: createError('/[0-9]/', String.raw`/\d/`), + output: String.raw`const foo = new RegExp(/\d/, 0)`, }, // `\s` rewrite testCase( - '/[ \\f\\n\\r\\t\\v\\u00a0\\u1680\\u2000-\\u200a\\u2028\\u2029\\u202f\\u205f\\u3000\\ufeff]+/', - '/\\s+/', + String.raw`/[ \f\n\r\t\v\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/`, + String.raw`/\s+/`, ), // #499 { - code: '/^[a-z][a-z0-9\\-]{5,29}$/', - errors: createError('/^[a-z][a-z0-9\\-]{5,29}$/', '/^[a-z][\\da-z\\-]{5,29}$/'), - output: '/^[a-z][\\da-z\\-]{5,29}$/', + code: String.raw`/^[a-z][a-z0-9\-]{5,29}$/`, + errors: createError(String.raw`/^[a-z][a-z0-9\-]{5,29}$/`, String.raw`/^[a-z][\da-z\-]{5,29}$/`), + output: String.raw`/^[a-z][\da-z\-]{5,29}$/`, }, // #477 testCase( - '/[ \\n\\t\\r\\]]/g', - '/[\\t\\n\\r \\]]/g', + String.raw`/[ \n\t\r\]]/g`, + String.raw`/[\t\n\r \]]/g`, ), testCase( - '/[ \\n\\t\\r\\f"#\'()/;[\\\\\\]{}]/g', - '/[\\t\\n\\f\\r "#\'()/;[\\\\\\]{}]/g', + String.raw`/[ \n\t\r\f"#'()/;[\\\]{}]/g`, + String.raw`/[\t\n\f\r "#'()/;[\\\]{}]/g`, ), testCase( - '/[ \\n\\t\\r\\f(){}:;@!\'"\\\\\\][#]|\\/(?=\\*)/g', - '/[\\t\\n\\f\\r !"#\'():;@[\\\\\\]{}]|\\/(?=\\*)/g', + String.raw`/[ \n\t\r\f(){}:;@!'"\\\][#]|\/(?=\*)/g`, + String.raw`/[\t\n\f\r !"#'():;@[\\\]{}]|\/(?=\*)/g`, ), // #994 testCase( - '/\\s?\\s?\\s?/', - '/\\s{0,3}/', + String.raw`/\s?\s?\s?/`, + String.raw`/\s{0,3}/`, ), // Actual message { code: '/[0-9]/', - output: '/\\d/', + output: String.raw`/\d/`, errors: [ { - message: '/[0-9]/ can be optimized to /\\d/.', + message: String.raw`/[0-9]/ can be optimized to /\d/.`, }, ], }, @@ -301,11 +301,11 @@ test({ // Not fixable { code: 'const foo = /[0-9]/.toString', - errors: createError('/[0-9]/', '/\\d/'), + errors: createError('/[0-9]/', String.raw`/\d/`), }, { code: 'const foo = /[0-9]/.source', - errors: createError('/[0-9]/', '/\\d/'), + errors: createError('/[0-9]/', String.raw`/\d/`), }, ], }); diff --git a/test/escape-case.mjs b/test/escape-case.mjs index e527d11c90..64f1293f2c 100644 --- a/test/escape-case.mjs +++ b/test/escape-case.mjs @@ -1,4 +1,4 @@ -/* eslint-disable no-template-curly-in-string, unicorn/escape-case */ +/* eslint-disable no-template-curly-in-string */ import {getTester} from './utils/test.mjs'; const {test} = getTester(import.meta); @@ -12,19 +12,19 @@ const errors = [ test({ valid: [ // Literal string - 'const foo = "\\xA9";', - 'const foo = "\\uD834";', - 'const foo = "\\u{1D306}";', - 'const foo = "\\uD834foo";', - 'const foo = "foo\\uD834";', - 'const foo = "foo \\uD834";', - 'const foo = "foo \\u2500";', - 'const foo = "foo \\x46";', - 'const foo = "foo\\\\xbar";', - 'const foo = "foo\\\\ubarbaz";', - 'const foo = "foo\\\\\\\\xbar";', - 'const foo = "foo\\\\\\\\ubarbaz";', - 'const foo = "\\ca";', + String.raw`const foo = "\xA9";`, + String.raw`const foo = "\uD834";`, + String.raw`const foo = "\u{1D306}";`, + String.raw`const foo = "\uD834foo";`, + String.raw`const foo = "foo\uD834";`, + String.raw`const foo = "foo \uD834";`, + String.raw`const foo = "foo \u2500";`, + String.raw`const foo = "foo \x46";`, + String.raw`const foo = "foo\\xbar";`, + String.raw`const foo = "foo\\ubarbaz";`, + String.raw`const foo = "foo\\\\xbar";`, + String.raw`const foo = "foo\\\\ubarbaz";`, + String.raw`const foo = "\ca";`, // TemplateLiteral 'const foo = `\\xA9`;', @@ -44,95 +44,95 @@ test({ 'const foo = String.raw`\\uAaAa`;', // Literal regex - 'const foo = /foo\\xA9/', - 'const foo = /foo\\uD834/', - 'const foo = /foo\\u{1D306}/u', - 'const foo = /foo\\cA/', + String.raw`const foo = /foo\xA9/`, + String.raw`const foo = /foo\uD834/`, + String.raw`const foo = /foo\u{1D306}/u`, + String.raw`const foo = /foo\cA/`, // Escape - 'const foo = /foo\\\\xa9/;', - 'const foo = /foo\\\\\\\\xa9/;', - 'const foo = /foo\\\\uD834/', - 'const foo = /foo\\\\u{1}/u', - 'const foo = /foo\\\\cA/', + String.raw`const foo = /foo\\xa9/;`, + String.raw`const foo = /foo\\\\xa9/;`, + String.raw`const foo = /foo\\uD834/`, + String.raw`const foo = /foo\\u{1}/u`, + String.raw`const foo = /foo\\cA/`, // RegExp - 'const foo = new RegExp("/\\xA9")', - 'const foo = new RegExp("/\\uD834/")', - 'const foo = new RegExp("/\\u{1D306}/", "u")', - 'const foo = new RegExp("/\\ca/")', - 'const foo = new RegExp("/\\cA/")', + String.raw`const foo = new RegExp("/\xA9")`, + String.raw`const foo = new RegExp("/\uD834/")`, + String.raw`const foo = new RegExp("/\u{1D306}/", "u")`, + String.raw`const foo = new RegExp("/\ca/")`, + String.raw`const foo = new RegExp("/\cA/")`, ], invalid: [ // Literal string { - code: 'const foo = "\\xa9";', + code: String.raw`const foo = "\xa9";`, errors, - output: 'const foo = "\\xA9";', + output: String.raw`const foo = "\xA9";`, }, // Mixed cases { - code: 'const foo = "\\xAa";', + code: String.raw`const foo = "\xAa";`, errors, - output: 'const foo = "\\xAA";', + output: String.raw`const foo = "\xAA";`, }, { - code: 'const foo = "\\uAaAa";', + code: String.raw`const foo = "\uAaAa";`, errors, - output: 'const foo = "\\uAAAA";', + output: String.raw`const foo = "\uAAAA";`, }, { - code: 'const foo = "\\u{AaAa}";', + code: String.raw`const foo = "\u{AaAa}";`, errors, - output: 'const foo = "\\u{AAAA}";', + output: String.raw`const foo = "\u{AAAA}";`, }, // Many { - code: 'const foo = "\\xAab\\xaab\\xAAb\\uAaAab\\uaaaab\\uAAAAb\\u{AaAa}b\\u{aaaa}b\\u{AAAA}";', + code: String.raw`const foo = "\xAab\xaab\xAAb\uAaAab\uaaaab\uAAAAb\u{AaAa}b\u{aaaa}b\u{AAAA}";`, errors, - output: 'const foo = "\\xAAb\\xAAb\\xAAb\\uAAAAb\\uAAAAb\\uAAAAb\\u{AAAA}b\\u{AAAA}b\\u{AAAA}";', + output: String.raw`const foo = "\xAAb\xAAb\xAAb\uAAAAb\uAAAAb\uAAAAb\u{AAAA}b\u{AAAA}b\u{AAAA}";`, }, { - code: 'const foo = "\\ud834";', + code: String.raw`const foo = "\ud834";`, errors, - output: 'const foo = "\\uD834";', + output: String.raw`const foo = "\uD834";`, }, { - code: 'const foo = "\\u{1d306}";', + code: String.raw`const foo = "\u{1d306}";`, errors, - output: 'const foo = "\\u{1D306}";', + output: String.raw`const foo = "\u{1D306}";`, }, { - code: 'const foo = "\\ud834foo";', + code: String.raw`const foo = "\ud834foo";`, errors, - output: 'const foo = "\\uD834foo";', + output: String.raw`const foo = "\uD834foo";`, }, { - code: 'const foo = "foo\\ud834";', + code: String.raw`const foo = "foo\ud834";`, errors, - output: 'const foo = "foo\\uD834";', + output: String.raw`const foo = "foo\uD834";`, }, { - code: 'const foo = "foo \\ud834";', + code: String.raw`const foo = "foo \ud834";`, errors, - output: 'const foo = "foo \\uD834";', + output: String.raw`const foo = "foo \uD834";`, }, { - code: 'const foo = "\\\\\\ud834foo";', + code: String.raw`const foo = "\\\ud834foo";`, errors, - output: 'const foo = "\\\\\\uD834foo";', + output: String.raw`const foo = "\\\uD834foo";`, }, { - code: 'const foo = "foo\\\\\\ud834";', + code: String.raw`const foo = "foo\\\ud834";`, errors, - output: 'const foo = "foo\\\\\\uD834";', + output: String.raw`const foo = "foo\\\uD834";`, }, { - code: 'const foo = "foo \\\\\\ud834";', + code: String.raw`const foo = "foo \\\ud834";`, errors, - output: 'const foo = "foo \\\\\\uD834";', + output: String.raw`const foo = "foo \\\uD834";`, }, // TemplateLiteral @@ -230,75 +230,75 @@ test({ // Literal regex { - code: 'const foo = /\\xa9/;', + code: String.raw`const foo = /\xa9/;`, errors, - output: 'const foo = /\\xA9/;', + output: String.raw`const foo = /\xA9/;`, }, { - code: 'const foo = /\\ud834/', + code: String.raw`const foo = /\ud834/`, errors, - output: 'const foo = /\\uD834/', + output: String.raw`const foo = /\uD834/`, }, { - code: 'const foo = /\\u{1d306}/u', + code: String.raw`const foo = /\u{1d306}/u`, errors, - output: 'const foo = /\\u{1D306}/u', + output: String.raw`const foo = /\u{1D306}/u`, }, { - code: 'const foo = /\\ca/', + code: String.raw`const foo = /\ca/`, errors, - output: 'const foo = /\\cA/', + output: String.raw`const foo = /\cA/`, }, { - code: 'const foo = /foo\\\\\\xa9/;', + code: String.raw`const foo = /foo\\\xa9/;`, errors, - output: 'const foo = /foo\\\\\\xA9/;', + output: String.raw`const foo = /foo\\\xA9/;`, }, { - code: 'const foo = /foo\\\\\\\\\\xa9/;', + code: String.raw`const foo = /foo\\\\\xa9/;`, errors, - output: 'const foo = /foo\\\\\\\\\\xA9/;', + output: String.raw`const foo = /foo\\\\\xA9/;`, }, // Mixed cases { - code: 'const foo = /\\xAa/;', + code: String.raw`const foo = /\xAa/;`, errors, - output: 'const foo = /\\xAA/;', + output: String.raw`const foo = /\xAA/;`, }, { - code: 'const foo = /\\uAaAa/;', + code: String.raw`const foo = /\uAaAa/;`, errors, - output: 'const foo = /\\uAAAA/;', + output: String.raw`const foo = /\uAAAA/;`, }, { - code: 'const foo = /\\u{AaAa}/;', + code: String.raw`const foo = /\u{AaAa}/;`, errors, - output: 'const foo = /\\u{AAAA}/;', + output: String.raw`const foo = /\u{AAAA}/;`, }, // Many { - code: 'const foo = /\\xAab\\xaab\\xAAb\\uAaAab\\uaaaab\\uAAAAb\\u{AaAa}b\\u{aaaa}b\\u{AAAA}b\\ca/;', + code: String.raw`const foo = /\xAab\xaab\xAAb\uAaAab\uaaaab\uAAAAb\u{AaAa}b\u{aaaa}b\u{AAAA}b\ca/;`, errors, - output: 'const foo = /\\xAAb\\xAAb\\xAAb\\uAAAAb\\uAAAAb\\uAAAAb\\u{AAAA}b\\u{AAAA}b\\u{AAAA}b\\cA/;', + output: String.raw`const foo = /\xAAb\xAAb\xAAb\uAAAAb\uAAAAb\uAAAAb\u{AAAA}b\u{AAAA}b\u{AAAA}b\cA/;`, }, // RegExp { - code: 'const foo = new RegExp("/\\xa9")', + code: String.raw`const foo = new RegExp("/\xa9")`, errors, - output: 'const foo = new RegExp("/\\xA9")', + output: String.raw`const foo = new RegExp("/\xA9")`, }, { - code: 'const foo = new RegExp("/\\ud834/")', + code: String.raw`const foo = new RegExp("/\ud834/")`, errors, - output: 'const foo = new RegExp("/\\uD834/")', + output: String.raw`const foo = new RegExp("/\uD834/")`, }, { - code: 'const foo = new RegExp("/\\u{1d306}/", "u")', + code: String.raw`const foo = new RegExp("/\u{1d306}/", "u")`, errors, - output: 'const foo = new RegExp("/\\u{1D306}/", "u")', + output: String.raw`const foo = new RegExp("/\u{1D306}/", "u")`, }, ], }); diff --git a/test/expiring-todo-comments.mjs b/test/expiring-todo-comments.mjs index 1ddfaf595b..9174b5f5ad 100644 --- a/test/expiring-todo-comments.mjs +++ b/test/expiring-todo-comments.mjs @@ -96,15 +96,15 @@ test({ }, { code: '// TODO ISSUE-123 fix later', - options: [{allowWarningComments: false, ignore: ['ISSUE-\\d+']}], + options: [{allowWarningComments: false, ignore: [String.raw`ISSUE-\d+`]}], }, { code: '// TODO [ISSUE-123] fix later', - options: [{allowWarningComments: false, ignore: ['ISSUE-\\d+']}], + options: [{allowWarningComments: false, ignore: [String.raw`ISSUE-\d+`]}], }, { code: '// TODO [1999-01-01, ISSUE-123] fix later', - options: [{allowWarningComments: false, ignore: ['ISSUE-\\d+']}], + options: [{allowWarningComments: false, ignore: [String.raw`ISSUE-\d+`]}], }, { code: '// TODO [Issue-123] fix later', diff --git a/test/filename-case.mjs b/test/filename-case.mjs index 69e51ddbfc..012feff0d2 100644 --- a/test/filename-case.mjs +++ b/test/filename-case.mjs @@ -102,52 +102,52 @@ test({ testCase('src/foo/[fooBar].js', 'camelCase'), testCase('src/foo/{foo_bar}.js', 'snakeCase'), testCaseWithOptions(undefined, undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions(undefined, undefined, [ {case: 'kebabCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/index.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/index.js', undefined, [ {case: 'kebabCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'kebabCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'camelCase', ignore: ['FOOBAR\\.js']}, + {case: 'camelCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'camelCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'snakeCase', ignore: ['FOOBAR\\.js']}, + {case: 'snakeCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'pascalCase', ignore: ['FOOBAR\\.js']}, + {case: 'pascalCase', ignore: [String.raw`FOOBAR\.js`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'pascalCase', ignore: [/FOOBAR\.js/u]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js', 'BARBAZ\\.js']}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`, String.raw`BARBAZ\.js`]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ - {case: 'kebabCase', ignore: ['FOOBAR\\.js', /BARBAZ\.js/u]}, + {case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`, /BARBAZ\.js/u]}, ]), testCaseWithOptions('src/foo/[FOOBAR].js', undefined, [ - {case: 'camelCase', ignore: ['\\[FOOBAR\\]\\.js']}, + {case: 'camelCase', ignore: [String.raw`\[FOOBAR\]\.js`]}, ]), testCaseWithOptions('src/foo/[FOOBAR].js', undefined, [ {case: 'camelCase', ignore: [/\[FOOBAR]\.js/]}, ]), testCaseWithOptions('src/foo/{FOOBAR}.js', undefined, [ - {case: 'snakeCase', ignore: ['\\{FOOBAR\\}\\.js']}, + {case: 'snakeCase', ignore: [String.raw`\{FOOBAR\}\.js`]}, ]), testCaseWithOptions('src/foo/{FOOBAR}.js', undefined, [ {case: 'snakeCase', ignore: [/{FOOBAR}\.js/]}, @@ -171,16 +171,16 @@ test({ {case: 'kebabCase', ignore: [/foo/iu]}, ]), testCaseWithOptions('src/foo/foo-bar.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.web.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.android.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.ios.js', undefined, [ - {case: 'kebabCase', ignore: ['\\.(web|android|ios)\\.js$']}, + {case: 'kebabCase', ignore: [String.raw`\.(web|android|ios)\.js$`]}, ]), testCaseWithOptions('src/foo/FooBar.something.js', undefined, [ {case: 'kebabCase', ignore: [/\.(?:web|android|ios|something)\.js$/u]}, @@ -192,13 +192,13 @@ test({ {case: 'kebabCase', ignore: [/^[Ff]oo/u]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ - {case: 'kebabCase', ignore: ['^FOO', 'BAZ\\.js$']}, + {case: 'kebabCase', ignore: ['^FOO', String.raw`BAZ\.js$`]}, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ {case: 'kebabCase', ignore: [/^FOO/, /BAZ\.js$/u]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ - {case: 'kebabCase', ignore: ['^FOO', 'BAZ\\.js$']}, + {case: 'kebabCase', ignore: ['^FOO', String.raw`BAZ\.js$`]}, ]), testCaseWithOptions('src/foo/BARBAZ.js', undefined, [ {case: 'kebabCase', ignore: [/^FOO/, /BAZ\.js$/]}, @@ -211,7 +211,7 @@ test({ snakeCase: true, pascalCase: true, }, - ignore: ['FOOBAR\\.js'], + ignore: [String.raw`FOOBAR\.js`], }, ]), testCaseWithOptions('src/foo/FOOBAR.js', undefined, [ @@ -233,7 +233,7 @@ test({ snakeCase: true, pascalCase: true, }, - ignore: ['FOOBAR\\.js', 'BaRbAz\\.js'], + ignore: [String.raw`FOOBAR\.js`, String.raw`BaRbAz\.js`], }, ]), testCaseWithOptions('src/foo/BaRbAz.js', undefined, [ @@ -439,12 +439,12 @@ test({ testCaseWithOptions( 'src/foo/barBaz.js', 'Filename is not in kebab case. Rename it to `bar-baz.js`.', - [{case: 'kebabCase', ignore: ['FOOBAR\\.js']}], + [{case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}], ), testCaseWithOptions( 'src/foo/barBaz.js', 'Filename is not in kebab case. Rename it to `bar-baz.js`.', - [{case: 'kebabCase', ignore: ['/FOOBAR\\.js/']}], + [{case: 'kebabCase', ignore: [String.raw`/FOOBAR\.js/`]}], ), testCaseWithOptions( 'src/foo/barBaz.js', @@ -454,7 +454,7 @@ test({ testCaseWithOptions( 'src/foo/fooBar.js', 'Filename is not in kebab case. Rename it to `foo-bar.js`.', - [{case: 'kebabCase', ignore: ['FOOBAR\\.js']}], + [{case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`]}], ), testCaseWithOptions( 'src/foo/fooBar.js', @@ -464,7 +464,7 @@ test({ testCaseWithOptions( 'src/foo/fooBar.js', 'Filename is not in kebab case. Rename it to `foo-bar.js`.', - [{case: 'kebabCase', ignore: ['FOOBAR\\.js', 'foobar\\.js']}], + [{case: 'kebabCase', ignore: [String.raw`FOOBAR\.js`, String.raw`foobar\.js`]}], ), testCaseWithOptions( 'src/foo/fooBar.js', @@ -480,7 +480,7 @@ test({ camelCase: true, snakeCase: true, }, - ignore: ['FOOBAR\\.js'], + ignore: [String.raw`FOOBAR\.js`], }, ], ), @@ -506,7 +506,7 @@ test({ camelCase: true, snakeCase: true, }, - ignore: ['BaRbAz\\.js'], + ignore: [String.raw`BaRbAz\.js`], }, ], ), diff --git a/test/no-console-spaces.mjs b/test/no-console-spaces.mjs index 810bd4dbc4..1e9f220185 100644 --- a/test/no-console-spaces.mjs +++ b/test/no-console-spaces.mjs @@ -30,8 +30,8 @@ test({ 'console.log(\' \', "def");', 'console.log("abc ", "def");', - 'console.log("abc\\t", "def");', - 'console.log("abc\\n", "def");', + String.raw`console.log("abc\t", "def");`, + String.raw`console.log("abc\n", "def");`, 'console.log(" abc", "def");', 'console.log(" abc", "def");', diff --git a/test/no-hex-escape.mjs b/test/no-hex-escape.mjs index ff6b7832fb..9d3d1400f4 100644 --- a/test/no-hex-escape.mjs +++ b/test/no-hex-escape.mjs @@ -9,19 +9,19 @@ const error = { const tests = { valid: [ 'const foo = \'foo\'', - 'const foo = \'\\u00b1\'', - 'const foo = \'\\u00b1\\u00b1\'', - 'const foo = \'foo\\u00b1\'', - 'const foo = \'foo\\u00b1foo\'', - 'const foo = \'\\u00b1foo\'', - 'const foo = \'\\\\xb1\'', - 'const foo = \'\\\\\\\\xb1\'', - 'const foo = \'foo\\\\xb1\'', - 'const foo = \'foo\\\\\\\\xb1\'', - 'const foo = \'\\\\xd8\\\\x3d\\\\xdc\\\\xa9\'', - 'const foo = \'foo\\\\x12foo\\\\x34\'', - 'const foo = \'\\\\\\\\xd8\\\\\\\\x3d\\\\\\\\xdc\\\\\\\\xa9\'', - 'const foo = \'foo\\\\\\\\x12foo\\\\\\\\x34\'', + String.raw`const foo = '\u00b1'`, + String.raw`const foo = '\u00b1\u00b1'`, + String.raw`const foo = 'foo\u00b1'`, + String.raw`const foo = 'foo\u00b1foo'`, + String.raw`const foo = '\u00b1foo'`, + String.raw`const foo = '\\xb1'`, + String.raw`const foo = '\\\\xb1'`, + String.raw`const foo = 'foo\\xb1'`, + String.raw`const foo = 'foo\\\\xb1'`, + String.raw`const foo = '\\xd8\\x3d\\xdc\\xa9'`, + String.raw`const foo = 'foo\\x12foo\\x34'`, + String.raw`const foo = '\\\\xd8\\\\x3d\\\\xdc\\\\xa9'`, + String.raw`const foo = 'foo\\\\x12foo\\\\x34'`, 'const foo = 42', 'const foo = `foo`', 'const foo = `\\u00b1`', @@ -42,79 +42,79 @@ const tests = { ], invalid: [ { - code: 'const foo = \'\\xb1\'', + code: String.raw`const foo = '\xb1'`, errors: [error], - output: 'const foo = \'\\u00b1\'', + output: String.raw`const foo = '\u00b1'`, }, { - code: 'const foo = \'\\\\\\xb1\'', + code: String.raw`const foo = '\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\u00b1\'', + output: String.raw`const foo = '\\\u00b1'`, }, { - code: 'const foo = \'\\xb1\\xb1\'', + code: String.raw`const foo = '\xb1\xb1'`, errors: [error], - output: 'const foo = \'\\u00b1\\u00b1\'', + output: String.raw`const foo = '\u00b1\u00b1'`, }, { - code: 'const foo = \'\\\\\\xb1\\\\\\xb1\'', + code: String.raw`const foo = '\\\xb1\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\u00b1\\\\\\u00b1\'', + output: String.raw`const foo = '\\\u00b1\\\u00b1'`, }, { - code: 'const foo = \'\\\\\\xb1\\\\\\\\xb1\'', + code: String.raw`const foo = '\\\xb1\\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\u00b1\\\\\\\\xb1\'', + output: String.raw`const foo = '\\\u00b1\\\\xb1'`, }, { - code: 'const foo = \'\\\\\\\\\\xb1\\\\\\xb1\'', + code: String.raw`const foo = '\\\\\xb1\\\xb1'`, errors: [error], - output: 'const foo = \'\\\\\\\\\\u00b1\\\\\\u00b1\'', + output: String.raw`const foo = '\\\\\u00b1\\\u00b1'`, }, { - code: 'const foo = \'\\xb1foo\'', + code: String.raw`const foo = '\xb1foo'`, errors: [error], - output: 'const foo = \'\\u00b1foo\'', + output: String.raw`const foo = '\u00b1foo'`, }, { - code: 'const foo = \'\\xd8\\x3d\\xdc\\xa9\'', + code: String.raw`const foo = '\xd8\x3d\xdc\xa9'`, errors: [error], - output: 'const foo = \'\\u00d8\\u003d\\u00dc\\u00a9\'', + output: String.raw`const foo = '\u00d8\u003d\u00dc\u00a9'`, }, { - code: 'const foo = \'foo\\xb1\'', + code: String.raw`const foo = 'foo\xb1'`, errors: [error], - output: 'const foo = \'foo\\u00b1\'', + output: String.raw`const foo = 'foo\u00b1'`, }, { - code: 'const foo = \'foo\\\\\\xb1\'', + code: String.raw`const foo = 'foo\\\xb1'`, errors: [error], - output: 'const foo = \'foo\\\\\\u00b1\'', + output: String.raw`const foo = 'foo\\\u00b1'`, }, { - code: 'const foo = \'foo\\\\\\\\\\xb1\'', + code: String.raw`const foo = 'foo\\\\\xb1'`, errors: [error], - output: 'const foo = \'foo\\\\\\\\\\u00b1\'', + output: String.raw`const foo = 'foo\\\\\u00b1'`, }, { - code: 'const foo = \'foo\\x12foo\\x34\'', + code: String.raw`const foo = 'foo\x12foo\x34'`, errors: [error], - output: 'const foo = \'foo\\u0012foo\\u0034\'', + output: String.raw`const foo = 'foo\u0012foo\u0034'`, }, { - code: 'const foo = \'42\\x1242\\x34\'', + code: String.raw`const foo = '42\x1242\x34'`, errors: [error], - output: 'const foo = \'42\\u001242\\u0034\'', + output: String.raw`const foo = '42\u001242\u0034'`, }, { - code: 'const foo = \'42\\\\\\x1242\\\\\\x34\'', + code: String.raw`const foo = '42\\\x1242\\\x34'`, errors: [error], - output: 'const foo = \'42\\\\\\u001242\\\\\\u0034\'', + output: String.raw`const foo = '42\\\u001242\\\u0034'`, }, { - code: 'const foo = /^[\\x20-\\x7E]*$/', + code: String.raw`const foo = /^[\x20-\x7E]*$/`, errors: [error], - output: 'const foo = /^[\\u0020-\\u007E]*$/', + output: String.raw`const foo = /^[\u0020-\u007E]*$/`, }, // Test template literals { @@ -215,6 +215,6 @@ test.typescript(avoidTestTitleConflict(tests, 'typescript')); test.snapshot({ valid: [], invalid: [ - 'const foo = "\\xb1"', + String.raw`const foo = "\xb1"`, ], }); diff --git a/test/prefer-string-raw.mjs b/test/prefer-string-raw.mjs new file mode 100644 index 0000000000..2f818eac66 --- /dev/null +++ b/test/prefer-string-raw.mjs @@ -0,0 +1,40 @@ +import outdent from 'outdent'; +import {getTester} from './utils/test.mjs'; + +const {test} = getTester(import.meta); + +test.snapshot({ + valid: [ + String.raw`a = '\''`, + // Cannot use `String.raw` + String.raw`'a\\b'`, + String.raw`import foo from "./foo\\bar.js";`, + String.raw`export {foo} from "./foo\\bar.js";`, + String.raw`export * from "./foo\\bar.js";`, + String.raw`a = {'a\\b': ''}`, + outdent` + a = "\\\\a \\ + b" + `, + String.raw`a = 'a\\b\u{51}c'`, + 'a = "a\\\\b`"', + // eslint-disable-next-line no-template-curly-in-string + 'a = "a\\\\b${foo}"', + { + code: String.raw``, + languageOptions: { + parserOptions: { + ecmaFeatures: { + jsx: true, + }, + }, + }, + }, + ], + invalid: [ + String.raw`a = 'a\\b'`, + String.raw`a = {['a\\b']: b}`, + String.raw`function a() {return'a\\b'}`, + String.raw`const foo = "foo \\x46";`, + ], +}); diff --git a/test/prefer-string-replace-all.mjs b/test/prefer-string-replace-all.mjs index 6fb1158feb..bc742d4fe1 100644 --- a/test/prefer-string-replace-all.mjs +++ b/test/prefer-string-replace-all.mjs @@ -14,8 +14,8 @@ test.snapshot({ // Not 2 arguments 'foo.replace(/a/g)', 'foo.replaceAll(/a/g)', - 'foo.replace(/\\\\./g)', - 'foo.replaceAll(/\\\\./g)', + String.raw`foo.replace(/\\./g)`, + String.raw`foo.replaceAll(/\\./g)`, // Not `CallExpression` 'new foo.replace(/a/g, bar)', 'new foo.replaceAll(/a/g, bar)', @@ -61,11 +61,11 @@ test.snapshot({ ) `, // Quotes - 'foo.replace(/"\'/g, \'\\\'\')', + String.raw`foo.replace(/"'/g, '\'')`, // Escaped symbols - 'foo.replace(/\\./g, bar)', - 'foo.replace(/\\\\\\./g, bar)', - 'foo.replace(/\\|/g, bar)', + String.raw`foo.replace(/\./g, bar)`, + String.raw`foo.replace(/\\\./g, bar)`, + String.raw`foo.replace(/\|/g, bar)`, // `u` flag 'foo.replace(/a/gu, bar)', 'foo.replace(/a/ug, bar)', @@ -74,10 +74,10 @@ test.snapshot({ 'foo.replace(/a?/g, bar)', 'foo.replace(/.*/g, bar)', 'foo.replace(/a|b/g, bar)', - 'foo.replace(/\\W/g, bar)', - 'foo.replace(/\\u{61}/g, bar)', - 'foo.replace(/\\u{61}/gu, bar)', - 'foo.replace(/\\u{61}/gv, bar)', + String.raw`foo.replace(/\W/g, bar)`, + String.raw`foo.replace(/\u{61}/g, bar)`, + String.raw`foo.replace(/\u{61}/gu, bar)`, + String.raw`foo.replace(/\u{61}/gv, bar)`, 'foo.replace(/]/g, "bar")', // Extra flag 'foo.replace(/a/gi, bar)', @@ -92,20 +92,20 @@ test.snapshot({ 'foo.replace(/[a]/g, _)', 'foo.replace(/a{1/g, _)', 'foo.replace(/a{1}/g, _)', - 'foo.replace(/\\u0022/g, _)', - 'foo.replace(/\\u0027/g, _)', - 'foo.replace(/\\cM\\cj/g, _)', - 'foo.replace(/\\x22/g, _)', - 'foo.replace(/\\x27/g, _)', - 'foo.replace(/\\uD83D\\ude00/g, _)', - 'foo.replace(/\\u{1f600}/gu, _)', - 'foo.replace(/\\n/g, _)', - 'foo.replace(/\\u{20}/gu, _)', - 'foo.replace(/\\u{20}/gv, _)', + String.raw`foo.replace(/\u0022/g, _)`, + String.raw`foo.replace(/\u0027/g, _)`, + String.raw`foo.replace(/\cM\cj/g, _)`, + String.raw`foo.replace(/\x22/g, _)`, + String.raw`foo.replace(/\x27/g, _)`, + String.raw`foo.replace(/\uD83D\ude00/g, _)`, + String.raw`foo.replace(/\u{1f600}/gu, _)`, + String.raw`foo.replace(/\n/g, _)`, + String.raw`foo.replace(/\u{20}/gu, _)`, + String.raw`foo.replace(/\u{20}/gv, _)`, 'foo.replaceAll(/a]/g, _)', - 'foo.replaceAll(/\\r\\n\\u{1f600}/gu, _)', - 'foo.replaceAll(/\\r\\n\\u{1f600}/gv, _)', + String.raw`foo.replaceAll(/\r\n\u{1f600}/gu, _)`, + String.raw`foo.replaceAll(/\r\n\u{1f600}/gv, _)`, `foo.replaceAll(/a${' very'.repeat(30)} long string/g, _)`, // Invalid RegExp #2010 diff --git a/test/prevent-abbreviations.mjs b/test/prevent-abbreviations.mjs index aced656e0b..17d411bd24 100644 --- a/test/prevent-abbreviations.mjs +++ b/test/prevent-abbreviations.mjs @@ -1921,7 +1921,7 @@ test({ /^e_/, // eslint-disable-next-line prefer-regex-literals new RegExp('_e$', 'i'), - '\\.e2e\\.', + String.raw`\.e2e\.`, ], }, ], diff --git a/test/relative-url-style.mjs b/test/relative-url-style.mjs index f567726c8c..1c896312de 100644 --- a/test/relative-url-style.mjs +++ b/test/relative-url-style.mjs @@ -28,8 +28,8 @@ test.snapshot({ // We don't check cooked value 'new URL(`\\u002E/${foo}`, base)', // We don't check escaped string - 'new URL("\\u002E/foo", base)', - 'new URL(\'\\u002E/foo\', base)', + String.raw`new URL("\u002E/foo", base)`, + String.raw`new URL('\u002E/foo', base)`, ], invalid: [ 'new URL("./foo", base)', @@ -67,9 +67,9 @@ test.snapshot({ 'new URL("/foo", base)', 'new URL("../foo", base)', 'new URL(".././foo", base)', - 'new URL("C:\\foo", base)', - 'new URL("\\u002E/foo", base)', - 'new URL("\\u002Ffoo", base)', + String.raw`new URL("C:\foo", base)`, + String.raw`new URL("\u002E/foo", base)`, + String.raw`new URL("\u002Ffoo", base)`, ].map(code => ({code, options: alwaysAddDotSlashOptions})), invalid: [ 'new URL("foo", base)', diff --git a/test/run-rules-on-codebase/lint.mjs b/test/run-rules-on-codebase/lint.mjs index 643b5b27c8..098641b8ab 100644 --- a/test/run-rules-on-codebase/lint.mjs +++ b/test/run-rules-on-codebase/lint.mjs @@ -29,6 +29,9 @@ const configs = [ unicorn: eslintPluginUnicorn, }, rules: eslintPluginUnicorn.configs.all.rules, + linterOptions: { + reportUnusedDisableDirectives: false, + }, }, { ignores: [ @@ -44,6 +47,9 @@ const configs = [ rules: { // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/1109#issuecomment-782689255 'unicorn/consistent-destructuring': 'off', + // https://github.com/sindresorhus/eslint-plugin-unicorn/issues/2341 + 'unicorn/escape-case': 'off', + 'unicorn/no-hex-escape': 'off', // Buggy 'unicorn/custom-error-definition': 'off', 'unicorn/consistent-function-scoping': 'off', diff --git a/test/snapshots/prefer-string-raw.mjs.md b/test/snapshots/prefer-string-raw.mjs.md new file mode 100644 index 0000000000..8a475cf888 --- /dev/null +++ b/test/snapshots/prefer-string-raw.mjs.md @@ -0,0 +1,89 @@ +# Snapshot report for `test/prefer-string-raw.mjs` + +The actual snapshot is saved in `prefer-string-raw.mjs.snap`. + +Generated by [AVA](https://avajs.dev). + +## invalid(1): a = 'a\\b' + +> Input + + `โŠ + 1 | a = 'a\\\\b'โŠ + ` + +> Output + + `โŠ + 1 | a = String.raw\`a\\b\`โŠ + ` + +> Error 1/1 + + `โŠ + > 1 | a = 'a\\\\b'โŠ + | ^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.โŠ + ` + +## invalid(2): a = {['a\\b']: b} + +> Input + + `โŠ + 1 | a = {['a\\\\b']: b}โŠ + ` + +> Output + + `โŠ + 1 | a = {[String.raw\`a\\b\`]: b}โŠ + ` + +> Error 1/1 + + `โŠ + > 1 | a = {['a\\\\b']: b}โŠ + | ^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.โŠ + ` + +## invalid(3): function a() {return'a\\b'} + +> Input + + `โŠ + 1 | function a() {return'a\\\\b'}โŠ + ` + +> Output + + `โŠ + 1 | function a() {return String.raw\`a\\b\`}โŠ + ` + +> Error 1/1 + + `โŠ + > 1 | function a() {return'a\\\\b'}โŠ + | ^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.โŠ + ` + +## invalid(4): const foo = "foo \\x46"; + +> Input + + `โŠ + 1 | const foo = "foo \\\\x46";โŠ + ` + +> Output + + `โŠ + 1 | const foo = String.raw\`foo \\x46\`;โŠ + ` + +> Error 1/1 + + `โŠ + > 1 | const foo = "foo \\\\x46";โŠ + | ^^^^^^^^^^^ \`String.raw\` should be used to avoid escaping \`\\\`.โŠ + ` diff --git a/test/snapshots/prefer-string-raw.mjs.snap b/test/snapshots/prefer-string-raw.mjs.snap new file mode 100644 index 0000000000000000000000000000000000000000..93f30083b58f405c3c7578fa09f89c6f3bfb4523 GIT binary patch literal 446 zcmV;v0YUyjRzV?Tb`3eKscS^9@Fmvikhc$#i~b|Ls!=CRPR`^HmIFi+_WMaw9<;AD1S ztT$BhVqoF@bF=?Yvu0`+whM8_L&XFKQn6u>QkA8AdP