UNPKG

5.59 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to disallow `\8` and `\9` escape sequences in string literals.
3 * @author Milos Djermanovic
4 */
5
6"use strict";
7
8//------------------------------------------------------------------------------
9// Helpers
10//------------------------------------------------------------------------------
11
12const QUICK_TEST_REGEX = /\\[89]/u;
13
14/**
15 * Returns unicode escape sequence that represents the given character.
16 * @param {string} character A single code unit.
17 * @returns {string} "\uXXXX" sequence.
18 */
19function getUnicodeEscape(character) {
20 return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
21}
22
23//------------------------------------------------------------------------------
24// Rule Definition
25//------------------------------------------------------------------------------
26
27module.exports = {
28 meta: {
29 type: "suggestion",
30
31 docs: {
32 description: "disallow `\\8` and `\\9` escape sequences in string literals",
33 category: "Best Practices",
34 recommended: false,
35 url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape",
36 suggestion: true
37 },
38
39 schema: [],
40
41 messages: {
42 decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.",
43
44 // suggestions
45 refactor: "Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.",
46 escapeBackslash: "Replace '{{original}}' with '{{replacement}}' to include the actual backslash character."
47 }
48 },
49
50 create(context) {
51 const sourceCode = context.getSourceCode();
52
53 /**
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
59 */
60 function createSuggestion(messageId, range, replacement) {
61 return {
62 messageId,
63 data: {
64 original: sourceCode.getText().slice(...range),
65 replacement
66 },
67 fix(fixer) {
68 return fixer.replaceTextRange(range, replacement);
69 }
70 };
71 }
72
73 return {
74 Literal(node) {
75 if (typeof node.value !== "string") {
76 return;
77 }
78
79 if (!QUICK_TEST_REGEX.test(node.raw)) {
80 return;
81 }
82
83 const regex = /(?:[^\\]|(?<previousEscape>\\.))*?(?<decimalEscape>\\[89])/suy;
84 let match;
85
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];
91 const suggest = [];
92
93 // When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape`
94 if (previousEscape === "\\0") {
95
96 /*
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.
100 */
101 suggest.push(
102 createSuggestion( // "\0\8" -> "\u00008"
103 "refactor",
104 [decimalEscapeRangeStart - previousEscape.length, decimalEscapeRangeEnd],
105 `${getUnicodeEscape("\0")}${decimalEscape[1]}`
106 ),
107 createSuggestion( // "\8" -> "\u0038"
108 "refactor",
109 decimalEscapeRange,
110 getUnicodeEscape(decimalEscape[1])
111 )
112 );
113 } else {
114 suggest.push(
115 createSuggestion( // "\8" -> "8"
116 "refactor",
117 decimalEscapeRange,
118 decimalEscape[1]
119 )
120 );
121 }
122
123 suggest.push(
124 createSuggestion( // "\8" -> "\\8"
125 "escapeBackslash",
126 decimalEscapeRange,
127 `\\${decimalEscape}`
128 )
129 );
130
131 context.report({
132 node,
133 loc: {
134 start: sourceCode.getLocFromIndex(decimalEscapeRangeStart),
135 end: sourceCode.getLocFromIndex(decimalEscapeRangeEnd)
136 },
137 messageId: "decimalEscape",
138 data: {
139 decimalEscape
140 },
141 suggest
142 });
143 }
144 }
145 };
146 }
147};