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/latest/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.sourceCode;
 | |
| 
 | |
|         /**
 | |
|          * 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
 | |
|                     });
 | |
|                 }
 | |
|             }
 | |
|         };
 | |
|     }
 | |
| };
 |