From 0fb5fd402334098dc44cbfbb8ab25919da04843d Mon Sep 17 00:00:00 2001 From: Teddy Katz Date: Mon, 1 Apr 2019 16:45:19 -0400 Subject: [PATCH] Breaking: interpret rule options as unicode regexes (fixes #11423) (#11516) This updates the core rules that interpret user-provided options as regular expressions. Those rules now interpret the options as unicode regexes, which avoids some cases of unexpected behavior with astral symbols. --- lib/rules/camelcase.js | 2 +- lib/rules/capitalized-comments.js | 2 +- lib/rules/default-case.js | 2 +- lib/rules/dot-notation.js | 2 +- lib/rules/handle-callback-err.js | 2 +- lib/rules/id-match.js | 2 +- lib/rules/line-comment-position.js | 2 +- lib/rules/lines-around-comment.js | 2 +- lib/rules/max-len.js | 2 +- lib/rules/new-cap.js | 4 +-- lib/rules/no-fallthrough.js | 2 +- lib/rules/no-unused-vars.js | 6 ++--- lib/rules/no-warning-comments.js | 7 +++--- lib/rules/spaced-comment.js | 8 +++--- lib/rules/valid-jsdoc.js | 2 +- tests/lib/rules/max-len.js | 6 +++++ tests/lib/rules/no-unused-vars.js | 40 +++++++++++++++--------------- 17 files changed, 50 insertions(+), 43 deletions(-) diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js index 4647cc17e59..cdc16fab9f9 100644 --- a/lib/rules/camelcase.js +++ b/lib/rules/camelcase.js @@ -90,7 +90,7 @@ module.exports = { */ function isAllowed(name) { return allow.findIndex( - entry => name === entry || name.match(new RegExp(entry)) // eslint-disable-line require-unicode-regexp + entry => name === entry || name.match(new RegExp(entry, "u")) ) !== -1; } diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js index 137be9e9337..036616b3369 100644 --- a/lib/rules/capitalized-comments.js +++ b/lib/rules/capitalized-comments.js @@ -91,7 +91,7 @@ function createRegExpForIgnorePatterns(normalizedOptions) { const ignorePatternStr = normalizedOptions[key].ignorePattern; if (ignorePatternStr) { - const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`); // eslint-disable-line require-unicode-regexp + const regExp = RegExp(`^\\s*(?:${ignorePatternStr})`, "u"); normalizedOptions[key].ignorePatternRegExp = regExp; } diff --git a/lib/rules/default-case.js b/lib/rules/default-case.js index c8d0af3926f..821e0d72bd1 100644 --- a/lib/rules/default-case.js +++ b/lib/rules/default-case.js @@ -39,7 +39,7 @@ module.exports = { create(context) { const options = context.options[0] || {}; const commentPattern = options.commentPattern - ? new RegExp(options.commentPattern) // eslint-disable-line require-unicode-regexp + ? new RegExp(options.commentPattern, "u") : DEFAULT_COMMENT_PATTERN; const sourceCode = context.getSourceCode(); diff --git a/lib/rules/dot-notation.js b/lib/rules/dot-notation.js index e25ab12b771..883c9090ff1 100644 --- a/lib/rules/dot-notation.js +++ b/lib/rules/dot-notation.js @@ -61,7 +61,7 @@ module.exports = { let allowPattern; if (options.allowPattern) { - allowPattern = new RegExp(options.allowPattern); // eslint-disable-line require-unicode-regexp + allowPattern = new RegExp(options.allowPattern, "u"); } /** diff --git a/lib/rules/handle-callback-err.js b/lib/rules/handle-callback-err.js index 4d08e4fec1b..640946699e7 100644 --- a/lib/rules/handle-callback-err.js +++ b/lib/rules/handle-callback-err.js @@ -52,7 +52,7 @@ module.exports = { */ function matchesConfiguredErrorName(name) { if (isPattern(errorArgument)) { - const regexp = new RegExp(errorArgument); // eslint-disable-line require-unicode-regexp + const regexp = new RegExp(errorArgument, "u"); return regexp.test(name); } diff --git a/lib/rules/id-match.js b/lib/rules/id-match.js index 48c1087674d..b97a497fd4f 100644 --- a/lib/rules/id-match.js +++ b/lib/rules/id-match.js @@ -53,7 +53,7 @@ module.exports = { // Options //-------------------------------------------------------------------------- const pattern = context.options[0] || "^.+$", - regexp = new RegExp(pattern); // eslint-disable-line require-unicode-regexp + regexp = new RegExp(pattern, "u"); const options = context.options[1] || {}, properties = !!options.properties, diff --git a/lib/rules/line-comment-position.js b/lib/rules/line-comment-position.js index 0023a1f11f3..9a91d43027a 100644 --- a/lib/rules/line-comment-position.js +++ b/lib/rules/line-comment-position.js @@ -78,7 +78,7 @@ module.exports = { const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; const fallThroughRegExp = /^\s*falls?\s?through/u; - const customIgnoreRegExp = new RegExp(ignorePattern); // eslint-disable-line require-unicode-regexp + const customIgnoreRegExp = new RegExp(ignorePattern, "u"); const sourceCode = context.getSourceCode(); //-------------------------------------------------------------------------- diff --git a/lib/rules/lines-around-comment.js b/lib/rules/lines-around-comment.js index f2f9e3f9d45..9d6ce461627 100644 --- a/lib/rules/lines-around-comment.js +++ b/lib/rules/lines-around-comment.js @@ -130,7 +130,7 @@ module.exports = { const options = Object.assign({}, context.options[0]); const ignorePattern = options.ignorePattern; const defaultIgnoreRegExp = astUtils.COMMENTS_IGNORE_PATTERN; - const customIgnoreRegExp = new RegExp(ignorePattern); // eslint-disable-line require-unicode-regexp + const customIgnoreRegExp = new RegExp(ignorePattern, "u"); const applyDefaultIgnorePatterns = options.applyDefaultIgnorePatterns !== false; options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true; diff --git a/lib/rules/max-len.js b/lib/rules/max-len.js index 6e7dd6f1ac1..438e38b0a56 100644 --- a/lib/rules/max-len.js +++ b/lib/rules/max-len.js @@ -153,7 +153,7 @@ module.exports = { let ignorePattern = options.ignorePattern || null; if (ignorePattern) { - ignorePattern = new RegExp(ignorePattern); // eslint-disable-line require-unicode-regexp + ignorePattern = new RegExp(ignorePattern, "u"); } //-------------------------------------------------------------------------- diff --git a/lib/rules/new-cap.js b/lib/rules/new-cap.js index 9e88981ce55..dcae238d9b9 100644 --- a/lib/rules/new-cap.js +++ b/lib/rules/new-cap.js @@ -136,10 +136,10 @@ module.exports = { const skipProperties = config.properties === false; const newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {}); - const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern) : null; // eslint-disable-line require-unicode-regexp + const newIsCapExceptionPattern = config.newIsCapExceptionPattern ? new RegExp(config.newIsCapExceptionPattern, "u") : null; const capIsNewExceptions = calculateCapIsNewExceptions(config); - const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern) : null; // eslint-disable-line require-unicode-regexp + const capIsNewExceptionPattern = config.capIsNewExceptionPattern ? new RegExp(config.capIsNewExceptionPattern, "u") : null; const listeners = {}; diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js index 241e07258e1..c6a71b6c8c6 100644 --- a/lib/rules/no-fallthrough.js +++ b/lib/rules/no-fallthrough.js @@ -95,7 +95,7 @@ module.exports = { let fallthroughCommentPattern = null; if (options.commentPattern) { - fallthroughCommentPattern = new RegExp(options.commentPattern); // eslint-disable-line require-unicode-regexp + fallthroughCommentPattern = new RegExp(options.commentPattern, "u"); } else { fallthroughCommentPattern = DEFAULT_FALLTHROUGH_COMMENT; } diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js index d4c4c6355c2..1482a0238ca 100644 --- a/lib/rules/no-unused-vars.js +++ b/lib/rules/no-unused-vars.js @@ -88,15 +88,15 @@ module.exports = { config.caughtErrors = firstOption.caughtErrors || config.caughtErrors; if (firstOption.varsIgnorePattern) { - config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern); // eslint-disable-line require-unicode-regexp + config.varsIgnorePattern = new RegExp(firstOption.varsIgnorePattern, "u"); } if (firstOption.argsIgnorePattern) { - config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern); // eslint-disable-line require-unicode-regexp + config.argsIgnorePattern = new RegExp(firstOption.argsIgnorePattern, "u"); } if (firstOption.caughtErrorsIgnorePattern) { - config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern); // eslint-disable-line require-unicode-regexp + config.caughtErrorsIgnorePattern = new RegExp(firstOption.caughtErrorsIgnorePattern, "u"); } } } diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js index 1d1b55460bc..0882f61262e 100644 --- a/lib/rules/no-warning-comments.js +++ b/lib/rules/no-warning-comments.js @@ -5,6 +5,7 @@ "use strict"; +const { escapeRegExp } = require("lodash"); const astUtils = require("../util/ast-utils"); //------------------------------------------------------------------------------ @@ -58,7 +59,7 @@ module.exports = { * @returns {RegExp} The term converted to a RegExp */ function convertToRegExp(term) { - const escaped = term.replace(/[-/\\$^*+?.()|[\]{}]/gu, "\\$&"); + const escaped = escapeRegExp(term); const wordBoundary = "\\b"; const eitherOrWordBoundary = `|${wordBoundary}`; let prefix; @@ -95,7 +96,7 @@ module.exports = { * ^\s*TERM\b. This checks the word boundary * at the beginning of the comment. */ - return new RegExp(prefix + escaped + suffix, "i"); // eslint-disable-line require-unicode-regexp + return new RegExp(prefix + escaped + suffix, "iu"); } /* @@ -103,7 +104,7 @@ module.exports = { * \bTERM\b|\bTERM\b, this checks the entire comment * for the term. */ - return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "i"); // eslint-disable-line require-unicode-regexp + return new RegExp(prefix + escaped + suffix + eitherOrWordBoundary + term + wordBoundary, "iu"); } const warningRegExps = warningTerms.map(convertToRegExp); diff --git a/lib/rules/spaced-comment.js b/lib/rules/spaced-comment.js index 6e469ef9403..e6b6e1b6534 100644 --- a/lib/rules/spaced-comment.js +++ b/lib/rules/spaced-comment.js @@ -126,7 +126,7 @@ function createAlwaysStylePattern(markers, exceptions) { pattern += "?"; // or nothing. pattern += createExceptionsPattern(exceptions); - return new RegExp(pattern); // eslint-disable-line require-unicode-regexp + return new RegExp(pattern, "u"); } /** @@ -142,7 +142,7 @@ function createAlwaysStylePattern(markers, exceptions) { function createNeverStylePattern(markers) { const pattern = `^(${markers.map(escape).join("|")})?[ \t]+`; - return new RegExp(pattern); // eslint-disable-line require-unicode-regexp + return new RegExp(pattern, "u"); } //------------------------------------------------------------------------------ @@ -250,9 +250,9 @@ module.exports = { // Create RegExp object for valid patterns. rule[type] = { beginRegex: requireSpace ? createAlwaysStylePattern(markers, exceptions) : createNeverStylePattern(markers), - endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`) : new RegExp(endNeverPattern), // eslint-disable-line require-unicode-regexp + endRegex: balanced && requireSpace ? new RegExp(`${createExceptionsPattern(exceptions)}$`, "u") : new RegExp(endNeverPattern, "u"), hasExceptions: exceptions.length > 0, - markers: new RegExp(`^(${markers.map(escape).join("|")})`) // eslint-disable-line require-unicode-regexp + markers: new RegExp(`^(${markers.map(escape).join("|")})`, "u") }; return rule; diff --git a/lib/rules/valid-jsdoc.js b/lib/rules/valid-jsdoc.js index 3250d9233c7..9ec6938e35a 100644 --- a/lib/rules/valid-jsdoc.js +++ b/lib/rules/valid-jsdoc.js @@ -482,7 +482,7 @@ module.exports = { } if (options.matchDescription) { - const regex = new RegExp(options.matchDescription); // eslint-disable-line require-unicode-regexp + const regex = new RegExp(options.matchDescription, "u"); if (!regex.test(jsdoc.description)) { context.report({ node: jsdocNode, messageId: "unsatisfiedDesc" }); diff --git a/tests/lib/rules/max-len.js b/tests/lib/rules/max-len.js index a7080f47708..41b0efe9bd5 100644 --- a/tests/lib/rules/max-len.js +++ b/tests/lib/rules/max-len.js @@ -178,6 +178,12 @@ ruleTester.run("max-len", rule, { { code: "'🙂😀😆😎😊😜😉👍'", options: [10] + }, + + // Astral symbols in pattern (only matched by unicode regexes) + { + code: "var longNameLongName = '𝌆𝌆'", + options: [5, { ignorePattern: "𝌆{2}" }] } ], diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index 3012dec6368..ba8e57dcc35 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -330,33 +330,33 @@ ruleTester.run("no-unused-vars", rule, { { code: "var _a; var b;", options: [{ vars: "all", varsIgnorePattern: "^_" }], - errors: [{ message: "'b' is defined but never used. Allowed unused vars must match /^_/.", line: 1, column: 13 }] + errors: [{ message: "'b' is defined but never used. Allowed unused vars must match /^_/u.", line: 1, column: 13 }] }, { code: "var a; function foo() { var _b; var c_; } foo();", options: [{ vars: "local", varsIgnorePattern: "^_" }], - errors: [{ message: "'c_' is defined but never used. Allowed unused vars must match /^_/.", line: 1, column: 37 }] + errors: [{ message: "'c_' is defined but never used. Allowed unused vars must match /^_/u.", line: 1, column: 37 }] }, { code: "function foo(a, _b) { } foo();", options: [{ args: "all", argsIgnorePattern: "^_" }], - errors: [{ message: "'a' is defined but never used. Allowed unused args must match /^_/.", line: 1, column: 14 }] + errors: [{ message: "'a' is defined but never used. Allowed unused args must match /^_/u.", line: 1, column: 14 }] }, { code: "function foo(a, _b, c) { return a; } foo();", options: [{ args: "after-used", argsIgnorePattern: "^_" }], - errors: [{ message: "'c' is defined but never used. Allowed unused args must match /^_/.", line: 1, column: 21 }] + errors: [{ message: "'c' is defined but never used. Allowed unused args must match /^_/u.", line: 1, column: 21 }] }, { code: "function foo(_a) { } foo();", options: [{ args: "all", argsIgnorePattern: "[iI]gnored" }], - errors: [{ message: "'_a' is defined but never used. Allowed unused args must match /[iI]gnored/.", line: 1, column: 14 }] + errors: [{ message: "'_a' is defined but never used. Allowed unused args must match /[iI]gnored/u.", line: 1, column: 14 }] }, { code: "var [ firstItemIgnored, secondItem ] = items;", options: [{ vars: "all", varsIgnorePattern: "[iI]gnored" }], parserOptions: { ecmaVersion: 6 }, - errors: [{ message: "'secondItem' is assigned a value but never used. Allowed unused vars must match /[iI]gnored/.", line: 1, column: 25 }] + errors: [{ message: "'secondItem' is assigned a value but never used. Allowed unused vars must match /[iI]gnored/u.", line: 1, column: 25 }] }, // for-in loops (see #2342) @@ -524,14 +524,14 @@ ruleTester.run("no-unused-vars", rule, { { code: "try{}catch(err){};", options: [{ caughtErrors: "all", caughtErrorsIgnorePattern: "^ignore" }], - errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/." }] + errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/u." }] }, // multiple try catch with one success { code: "try{}catch(ignoreErr){}try{}catch(err){};", options: [{ caughtErrors: "all", caughtErrorsIgnorePattern: "^ignore" }], - errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/." }] + errors: [{ message: "'err' is defined but never used. Allowed unused args must match /^ignore/u." }] }, // multiple try catch both fail @@ -539,8 +539,8 @@ ruleTester.run("no-unused-vars", rule, { code: "try{}catch(error){}try{}catch(err){};", options: [{ caughtErrors: "all", caughtErrorsIgnorePattern: "^ignore" }], errors: [ - { message: "'error' is defined but never used. Allowed unused args must match /^ignore/." }, - { message: "'err' is defined but never used. Allowed unused args must match /^ignore/." } + { message: "'error' is defined but never used. Allowed unused args must match /^ignore/u." }, + { message: "'err' is defined but never used. Allowed unused args must match /^ignore/u." } ] }, @@ -613,10 +613,10 @@ ruleTester.run("no-unused-vars", rule, { options: [{ argsIgnorePattern: "c" }], errors: [ { - message: "'a' is defined but never used. Allowed unused args must match /c/." + message: "'a' is defined but never used. Allowed unused args must match /c/u." }, { - message: "'b' is defined but never used. Allowed unused args must match /c/." + message: "'b' is defined but never used. Allowed unused args must match /c/u." } ] }, @@ -626,10 +626,10 @@ ruleTester.run("no-unused-vars", rule, { parserOptions: { ecmaVersion: 6 }, errors: [ { - message: "'a' is defined but never used. Allowed unused args must match /[cd]/." + message: "'a' is defined but never used. Allowed unused args must match /[cd]/u." }, { - message: "'b' is defined but never used. Allowed unused args must match /[cd]/." + message: "'b' is defined but never used. Allowed unused args must match /[cd]/u." } ] }, @@ -639,13 +639,13 @@ ruleTester.run("no-unused-vars", rule, { parserOptions: { ecmaVersion: 6 }, errors: [ { - message: "'a' is defined but never used. Allowed unused args must match /c/." + message: "'a' is defined but never used. Allowed unused args must match /c/u." }, { - message: "'b' is defined but never used. Allowed unused args must match /c/." + message: "'b' is defined but never used. Allowed unused args must match /c/u." }, { - message: "'d' is defined but never used. Allowed unused args must match /c/." + message: "'d' is defined but never used. Allowed unused args must match /c/u." } ] }, @@ -655,13 +655,13 @@ ruleTester.run("no-unused-vars", rule, { parserOptions: { ecmaVersion: 6 }, errors: [ { - message: "'a' is defined but never used. Allowed unused args must match /d/." + message: "'a' is defined but never used. Allowed unused args must match /d/u." }, { - message: "'b' is defined but never used. Allowed unused args must match /d/." + message: "'b' is defined but never used. Allowed unused args must match /d/u." }, { - message: "'c' is defined but never used. Allowed unused args must match /d/." + message: "'c' is defined but never used. Allowed unused args must match /d/u." } ] },