149 lines
5.5 KiB
JavaScript
149 lines
5.5 KiB
JavaScript
|
/**
|
||
|
* @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
|
||
|
});
|
||
|
}
|
||
|
}
|
||
|
};
|
||
|
}
|
||
|
};
|