]> git.proxmox.com Git - pve-eslint.git/blob - eslint/lib/rules/no-nonoctal-decimal-escape.js
3edd269faeb33deb1c263296dbcb631ecba06de9
[pve-eslint.git] / eslint / lib / rules / no-nonoctal-decimal-escape.js
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
12 const 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 */
19 function getUnicodeEscape(character) {
20 return `\\u${character.charCodeAt(0).toString(16).padStart(4, "0")}`;
21 }
22
23 //------------------------------------------------------------------------------
24 // Rule Definition
25 //------------------------------------------------------------------------------
26
27 /** @type {import('../shared/types').Rule} */
28 module.exports = {
29 meta: {
30 type: "suggestion",
31
32 docs: {
33 description: "disallow `\\8` and `\\9` escape sequences in string literals",
34 recommended: true,
35 url: "https://eslint.org/docs/rules/no-nonoctal-decimal-escape"
36 },
37
38 hasSuggestions: true,
39
40 schema: [],
41
42 messages: {
43 decimalEscape: "Don't use '{{decimalEscape}}' escape sequence.",
44
45 // suggestions
46 refactor: "Replace '{{original}}' with '{{replacement}}'. This maintains the current functionality.",
47 escapeBackslash: "Replace '{{original}}' with '{{replacement}}' to include the actual backslash character."
48 }
49 },
50
51 create(context) {
52 const sourceCode = context.getSourceCode();
53
54 /**
55 * Creates a new Suggestion object.
56 * @param {string} messageId "refactor" or "escapeBackslash".
57 * @param {int[]} range The range to replace.
58 * @param {string} replacement New text for the range.
59 * @returns {Object} Suggestion
60 */
61 function createSuggestion(messageId, range, replacement) {
62 return {
63 messageId,
64 data: {
65 original: sourceCode.getText().slice(...range),
66 replacement
67 },
68 fix(fixer) {
69 return fixer.replaceTextRange(range, replacement);
70 }
71 };
72 }
73
74 return {
75 Literal(node) {
76 if (typeof node.value !== "string") {
77 return;
78 }
79
80 if (!QUICK_TEST_REGEX.test(node.raw)) {
81 return;
82 }
83
84 const regex = /(?:[^\\]|(?<previousEscape>\\.))*?(?<decimalEscape>\\[89])/suy;
85 let match;
86
87 while ((match = regex.exec(node.raw))) {
88 const { previousEscape, decimalEscape } = match.groups;
89 const decimalEscapeRangeEnd = node.range[0] + match.index + match[0].length;
90 const decimalEscapeRangeStart = decimalEscapeRangeEnd - decimalEscape.length;
91 const decimalEscapeRange = [decimalEscapeRangeStart, decimalEscapeRangeEnd];
92 const suggest = [];
93
94 // When `regex` is matched, `previousEscape` can only capture characters adjacent to `decimalEscape`
95 if (previousEscape === "\\0") {
96
97 /*
98 * Now we have a NULL escape "\0" immediately followed by a decimal escape, e.g.: "\0\8".
99 * Fixing this to "\08" would turn "\0" into a legacy octal escape. To avoid producing
100 * an octal escape while fixing a decimal escape, we provide different suggestions.
101 */
102 suggest.push(
103 createSuggestion( // "\0\8" -> "\u00008"
104 "refactor",
105 [decimalEscapeRangeStart - previousEscape.length, decimalEscapeRangeEnd],
106 `${getUnicodeEscape("\0")}${decimalEscape[1]}`
107 ),
108 createSuggestion( // "\8" -> "\u0038"
109 "refactor",
110 decimalEscapeRange,
111 getUnicodeEscape(decimalEscape[1])
112 )
113 );
114 } else {
115 suggest.push(
116 createSuggestion( // "\8" -> "8"
117 "refactor",
118 decimalEscapeRange,
119 decimalEscape[1]
120 )
121 );
122 }
123
124 suggest.push(
125 createSuggestion( // "\8" -> "\\8"
126 "escapeBackslash",
127 decimalEscapeRange,
128 `\\${decimalEscape}`
129 )
130 );
131
132 context.report({
133 node,
134 loc: {
135 start: sourceCode.getLocFromIndex(decimalEscapeRangeStart),
136 end: sourceCode.getLocFromIndex(decimalEscapeRangeEnd)
137 },
138 messageId: "decimalEscape",
139 data: {
140 decimalEscape
141 },
142 suggest
143 });
144 }
145 }
146 };
147 }
148 };