| /** |
| * @fileoverview Rule to disallow `\8` and `\9` escape sequences in string literals. |
| * @author Milos Djermanovic |
| */ |
| |
| "use strict"; |
| |
| //------------------------------------------------------------------------------ |
| // Helpers |
| //------------------------------------------------------------------------------ |
| |
| const QUICK_TEST_REGEX = /\\[89]/u; |
| |
| /** |
| * Returns unicode escape sequence that represents the given character. |
| * @param {string} character A single code unit. |
| * @returns {string} "\uXXXX" sequence. |
| */ |
| function getUnicodeEscape(character) { |
| return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`; |
| } |
| |
| //------------------------------------------------------------------------------ |
| // Rule Definition |
| //------------------------------------------------------------------------------ |
| |
| /** @type {import('../shared/types').Rule} */ |
| module.exports = { |
| meta: { |
| type: "suggestion", |
| |
| docs: { |
| description: "disallow `\\8` and `\\9` escape sequences in string literals", |
| recommended: true, |
| url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape" |
| }, |
| |
| hasSuggestions: true, |
| |
| schema: [], |
| |
| messages: { |
| decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.", |
| |
| // suggestions |
| refactor: "Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.", |
| escapeBackslash: "Replace '{{original}}' with '{{replacement}}' to include the actual backslash character." |
| } |
| }, |
| |
| create(context) { |
| const sourceCode = context.getSourceCode(); |
| |
| /** |
| * Creates a new Suggestion object. |
| * @param {string} messageId "refactor" or "escapeBackslash". |
| * @param {int[]} range The range to replace. |
| * @param {string} replacement New text for the range. |
| * @returns {Object} Suggestion |
| */ |
| function createSuggestion(messageId, range, replacement) { |
| return { |
| messageId, |
| data: { |
| original: sourceCode.getText().slice(...range), |
| replacement |
| }, |
| fix(fixer) { |
| return fixer.replaceTextRange(range, replacement); |
| } |
| }; |
| } |
| |
| return { |
| Literal(node) { |
| if (typeof node.value !== "string") { |
| return; |
| } |
| |
| if (!QUICK_TEST_REGEX.test(node.raw)) { |
| return; |
| } |
| |
| const regex = /(?:[^\\]|(?<previousEscape>\\.))*?(?<decimalEscape>\\[89])/suy; |
| let match; |
| |
| while ((match = regex.exec(node.raw))) { |
| const { previousEscape, decimalEscape } = match.groups; |
| const decimalEscapeRangeEnd = node.range[0] + match.index + match[0].length; |
| const decimalEscapeRangeStart = decimalEscapeRangeEnd - decimalEscape.length; |
| const decimalEscapeRange = [decimalEscapeRangeStart, decimalEscapeRangeEnd]; |
| const suggest = []; |
| |
| // When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape` |
| if (previousEscape === "\\0") { |
| |
| /* |
| * Now we have a NULL escape "\0" immediately followed by a decimal escape, e.g.: "\0\8". |
| * Fixing this to "\08" would turn "\0" into a legacy octal escape. To avoid producing |
| * an octal escape while fixing a decimal escape, we provide different suggestions. |
| */ |
| suggest.push( |
| createSuggestion( // "\0\8" -> "\u00008" |
| "refactor", |
| [decimalEscapeRangeStart - previousEscape.length, decimalEscapeRangeEnd], |
| `${getUnicodeEscape("\0")}${decimalEscape[1]}` |
| ), |
| createSuggestion( // "\8" -> "\u0038" |
| "refactor", |
| decimalEscapeRange, |
| getUnicodeEscape(decimalEscape[1]) |
| ) |
| ); |
| } else { |
| suggest.push( |
| createSuggestion( // "\8" -> "8" |
| "refactor", |
| decimalEscapeRange, |
| decimalEscape[1] |
| ) |
| ); |
| } |
| |
| suggest.push( |
| createSuggestion( // "\8" -> "\\8" |
| "escapeBackslash", |
| decimalEscapeRange, |
| `\\${decimalEscape}` |
| ) |
| ); |
| |
| context.report({ |
| node, |
| loc: { |
| start: sourceCode.getLocFromIndex(decimalEscapeRangeStart), |
| end: sourceCode.getLocFromIndex(decimalEscapeRangeEnd) |
| }, |
| messageId: "decimalEscape", |
| data: { |
| decimalEscape |
| }, |
| suggest |
| }); |
| } |
| } |
| }; |
| } |
| }; |