2 * @fileoverview Rule to disallow `\8` and `\9` escape sequences in string literals.
3 * @author Milos Djermanovic
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const QUICK_TEST_REGEX = /\\[89]/u;
15 * Returns unicode escape sequence that represents the given character.
16 * @param {string} character A single code unit.
17 * @returns {string} "\uXXXX" sequence.
19 function getUnicodeEscape(character) {
20 return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
23 //------------------------------------------------------------------------------
25 //------------------------------------------------------------------------------
32 description: "disallow `\\8` and `\\9` escape sequences in string literals",
33 category: "Best Practices",
35 url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape",
42 decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.",
45 refactor: "Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.",
46 escapeBackslash: "Replace '{{original}}' with '{{replacement}}' to include the actual backslash character."
51 const sourceCode = context.getSourceCode();
54 * Creates a new Suggestion object.
55 * @param {string} messageId "refactor" or "escapeBackslash".
56 * @param {int[]} range The range to replace.
57 * @param {string} replacement New text for the range.
58 * @returns {Object} Suggestion
60 function createSuggestion(messageId, range, replacement) {
64 original: sourceCode.getText().slice(...range),
68 return fixer.replaceTextRange(range, replacement);
75 if (typeof node.value !== "string") {
79 if (!QUICK_TEST_REGEX.test(node.raw)) {
83 const regex = /(?:[^\\]|(?<previousEscape>\\.))*?(?<decimalEscape>\\[89])/suy;
86 while ((match = regex.exec(node.raw))) {
87 const { previousEscape, decimalEscape } = match.groups;
88 const decimalEscapeRangeEnd = node.range[0] + match.index + match[0].length;
89 const decimalEscapeRangeStart = decimalEscapeRangeEnd - decimalEscape.length;
90 const decimalEscapeRange = [decimalEscapeRangeStart, decimalEscapeRangeEnd];
93 // When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape`
94 if (previousEscape === "\\0") {
97 * Now we have a NULL escape "\0" immediately followed by a decimal escape, e.g.: "\0\8".
98 * Fixing this to "\08" would turn "\0" into a legacy octal escape. To avoid producing
99 * an octal escape while fixing a decimal escape, we provide different suggestions.
102 createSuggestion( // "\0\8" -> "\u00008"
104 [decimalEscapeRangeStart - previousEscape.length, decimalEscapeRangeEnd],
105 `${getUnicodeEscape("\0")}${decimalEscape[1]}`
107 createSuggestion( // "\8" -> "\u0038"
110 getUnicodeEscape(decimalEscape[1])
115 createSuggestion( // "\8" -> "8"
124 createSuggestion( // "\8" -> "\\8"
134 start: sourceCode.getLocFromIndex(decimalEscapeRangeStart),
135 end: sourceCode.getLocFromIndex(decimalEscapeRangeEnd)
137 messageId: "decimalEscape",