311 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			311 lines
		
	
	
		
			12 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /**
 | |
|  * @fileoverview Rule to flag non-quoted property names in object literals.
 | |
|  * @author Mathias Bynens <http://mathiasbynens.be/>
 | |
|  * @deprecated in ESLint v8.53.0
 | |
|  */
 | |
| "use strict";
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Requirements
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| const espree = require("espree");
 | |
| const astUtils = require("./utils/ast-utils");
 | |
| const keywords = require("./utils/keywords");
 | |
| 
 | |
| //------------------------------------------------------------------------------
 | |
| // Rule Definition
 | |
| //------------------------------------------------------------------------------
 | |
| 
 | |
| /** @type {import('../shared/types').Rule} */
 | |
| module.exports = {
 | |
|     meta: {
 | |
|         deprecated: true,
 | |
|         replacedBy: [],
 | |
|         type: "suggestion",
 | |
| 
 | |
|         docs: {
 | |
|             description: "Require quotes around object literal property names",
 | |
|             recommended: false,
 | |
|             url: "https://eslint.org/docs/latest/rules/quote-props"
 | |
|         },
 | |
| 
 | |
|         schema: {
 | |
|             anyOf: [
 | |
|                 {
 | |
|                     type: "array",
 | |
|                     items: [
 | |
|                         {
 | |
|                             enum: ["always", "as-needed", "consistent", "consistent-as-needed"]
 | |
|                         }
 | |
|                     ],
 | |
|                     minItems: 0,
 | |
|                     maxItems: 1
 | |
|                 },
 | |
|                 {
 | |
|                     type: "array",
 | |
|                     items: [
 | |
|                         {
 | |
|                             enum: ["always", "as-needed", "consistent", "consistent-as-needed"]
 | |
|                         },
 | |
|                         {
 | |
|                             type: "object",
 | |
|                             properties: {
 | |
|                                 keywords: {
 | |
|                                     type: "boolean"
 | |
|                                 },
 | |
|                                 unnecessary: {
 | |
|                                     type: "boolean"
 | |
|                                 },
 | |
|                                 numbers: {
 | |
|                                     type: "boolean"
 | |
|                                 }
 | |
|                             },
 | |
|                             additionalProperties: false
 | |
|                         }
 | |
|                     ],
 | |
|                     minItems: 0,
 | |
|                     maxItems: 2
 | |
|                 }
 | |
|             ]
 | |
|         },
 | |
| 
 | |
|         fixable: "code",
 | |
|         messages: {
 | |
|             requireQuotesDueToReservedWord: "Properties should be quoted as '{{property}}' is a reserved word.",
 | |
|             inconsistentlyQuotedProperty: "Inconsistently quoted property '{{key}}' found.",
 | |
|             unnecessarilyQuotedProperty: "Unnecessarily quoted property '{{property}}' found.",
 | |
|             unquotedReservedProperty: "Unquoted reserved word '{{property}}' used as key.",
 | |
|             unquotedNumericProperty: "Unquoted number literal '{{property}}' used as key.",
 | |
|             unquotedPropertyFound: "Unquoted property '{{property}}' found.",
 | |
|             redundantQuoting: "Properties shouldn't be quoted as all quotes are redundant."
 | |
|         }
 | |
|     },
 | |
| 
 | |
|     create(context) {
 | |
| 
 | |
|         const MODE = context.options[0],
 | |
|             KEYWORDS = context.options[1] && context.options[1].keywords,
 | |
|             CHECK_UNNECESSARY = !context.options[1] || context.options[1].unnecessary !== false,
 | |
|             NUMBERS = context.options[1] && context.options[1].numbers,
 | |
| 
 | |
|             sourceCode = context.sourceCode;
 | |
| 
 | |
| 
 | |
|         /**
 | |
|          * Checks whether a certain string constitutes an ES3 token
 | |
|          * @param {string} tokenStr The string to be checked.
 | |
|          * @returns {boolean} `true` if it is an ES3 token.
 | |
|          */
 | |
|         function isKeyword(tokenStr) {
 | |
|             return keywords.includes(tokenStr);
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Checks if an espree-tokenized key has redundant quotes (i.e. whether quotes are unnecessary)
 | |
|          * @param {string} rawKey The raw key value from the source
 | |
|          * @param {espreeTokens} tokens The espree-tokenized node key
 | |
|          * @param {boolean} [skipNumberLiterals=false] Indicates whether number literals should be checked
 | |
|          * @returns {boolean} Whether or not a key has redundant quotes.
 | |
|          * @private
 | |
|          */
 | |
|         function areQuotesRedundant(rawKey, tokens, skipNumberLiterals) {
 | |
|             return tokens.length === 1 && tokens[0].start === 0 && tokens[0].end === rawKey.length &&
 | |
|                 (["Identifier", "Keyword", "Null", "Boolean"].includes(tokens[0].type) ||
 | |
|                 (tokens[0].type === "Numeric" && !skipNumberLiterals && String(+tokens[0].value) === tokens[0].value));
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Returns a string representation of a property node with quotes removed
 | |
|          * @param {ASTNode} key Key AST Node, which may or may not be quoted
 | |
|          * @returns {string} A replacement string for this property
 | |
|          */
 | |
|         function getUnquotedKey(key) {
 | |
|             return key.type === "Identifier" ? key.name : key.value;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Returns a string representation of a property node with quotes added
 | |
|          * @param {ASTNode} key Key AST Node, which may or may not be quoted
 | |
|          * @returns {string} A replacement string for this property
 | |
|          */
 | |
|         function getQuotedKey(key) {
 | |
|             if (key.type === "Literal" && typeof key.value === "string") {
 | |
| 
 | |
|                 // If the key is already a string literal, don't replace the quotes with double quotes.
 | |
|                 return sourceCode.getText(key);
 | |
|             }
 | |
| 
 | |
|             // Otherwise, the key is either an identifier or a number literal.
 | |
|             return `"${key.type === "Identifier" ? key.name : key.value}"`;
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Ensures that a property's key is quoted only when necessary
 | |
|          * @param {ASTNode} node Property AST node
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function checkUnnecessaryQuotes(node) {
 | |
|             const key = node.key;
 | |
| 
 | |
|             if (node.method || node.computed || node.shorthand) {
 | |
|                 return;
 | |
|             }
 | |
| 
 | |
|             if (key.type === "Literal" && typeof key.value === "string") {
 | |
|                 let tokens;
 | |
| 
 | |
|                 try {
 | |
|                     tokens = espree.tokenize(key.value);
 | |
|                 } catch {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 if (tokens.length !== 1) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 const isKeywordToken = isKeyword(tokens[0].value);
 | |
| 
 | |
|                 if (isKeywordToken && KEYWORDS) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 if (CHECK_UNNECESSARY && areQuotesRedundant(key.value, tokens, NUMBERS)) {
 | |
|                     context.report({
 | |
|                         node,
 | |
|                         messageId: "unnecessarilyQuotedProperty",
 | |
|                         data: { property: key.value },
 | |
|                         fix: fixer => fixer.replaceText(key, getUnquotedKey(key))
 | |
|                     });
 | |
|                 }
 | |
|             } else if (KEYWORDS && key.type === "Identifier" && isKeyword(key.name)) {
 | |
|                 context.report({
 | |
|                     node,
 | |
|                     messageId: "unquotedReservedProperty",
 | |
|                     data: { property: key.name },
 | |
|                     fix: fixer => fixer.replaceText(key, getQuotedKey(key))
 | |
|                 });
 | |
|             } else if (NUMBERS && key.type === "Literal" && astUtils.isNumericLiteral(key)) {
 | |
|                 context.report({
 | |
|                     node,
 | |
|                     messageId: "unquotedNumericProperty",
 | |
|                     data: { property: key.value },
 | |
|                     fix: fixer => fixer.replaceText(key, getQuotedKey(key))
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Ensures that a property's key is quoted
 | |
|          * @param {ASTNode} node Property AST node
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function checkOmittedQuotes(node) {
 | |
|             const key = node.key;
 | |
| 
 | |
|             if (!node.method && !node.computed && !node.shorthand && !(key.type === "Literal" && typeof key.value === "string")) {
 | |
|                 context.report({
 | |
|                     node,
 | |
|                     messageId: "unquotedPropertyFound",
 | |
|                     data: { property: key.name || key.value },
 | |
|                     fix: fixer => fixer.replaceText(key, getQuotedKey(key))
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         /**
 | |
|          * Ensures that an object's keys are consistently quoted, optionally checks for redundancy of quotes
 | |
|          * @param {ASTNode} node Property AST node
 | |
|          * @param {boolean} checkQuotesRedundancy Whether to check quotes' redundancy
 | |
|          * @returns {void}
 | |
|          */
 | |
|         function checkConsistency(node, checkQuotesRedundancy) {
 | |
|             const quotedProps = [],
 | |
|                 unquotedProps = [];
 | |
|             let keywordKeyName = null,
 | |
|                 necessaryQuotes = false;
 | |
| 
 | |
|             node.properties.forEach(property => {
 | |
|                 const key = property.key;
 | |
| 
 | |
|                 if (!key || property.method || property.computed || property.shorthand) {
 | |
|                     return;
 | |
|                 }
 | |
| 
 | |
|                 if (key.type === "Literal" && typeof key.value === "string") {
 | |
| 
 | |
|                     quotedProps.push(property);
 | |
| 
 | |
|                     if (checkQuotesRedundancy) {
 | |
|                         let tokens;
 | |
| 
 | |
|                         try {
 | |
|                             tokens = espree.tokenize(key.value);
 | |
|                         } catch {
 | |
|                             necessaryQuotes = true;
 | |
|                             return;
 | |
|                         }
 | |
| 
 | |
|                         necessaryQuotes = necessaryQuotes || !areQuotesRedundant(key.value, tokens) || KEYWORDS && isKeyword(tokens[0].value);
 | |
|                     }
 | |
|                 } else if (KEYWORDS && checkQuotesRedundancy && key.type === "Identifier" && isKeyword(key.name)) {
 | |
|                     unquotedProps.push(property);
 | |
|                     necessaryQuotes = true;
 | |
|                     keywordKeyName = key.name;
 | |
|                 } else {
 | |
|                     unquotedProps.push(property);
 | |
|                 }
 | |
|             });
 | |
| 
 | |
|             if (checkQuotesRedundancy && quotedProps.length && !necessaryQuotes) {
 | |
|                 quotedProps.forEach(property => {
 | |
|                     context.report({
 | |
|                         node: property,
 | |
|                         messageId: "redundantQuoting",
 | |
|                         fix: fixer => fixer.replaceText(property.key, getUnquotedKey(property.key))
 | |
|                     });
 | |
|                 });
 | |
|             } else if (unquotedProps.length && keywordKeyName) {
 | |
|                 unquotedProps.forEach(property => {
 | |
|                     context.report({
 | |
|                         node: property,
 | |
|                         messageId: "requireQuotesDueToReservedWord",
 | |
|                         data: { property: keywordKeyName },
 | |
|                         fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
 | |
|                     });
 | |
|                 });
 | |
|             } else if (quotedProps.length && unquotedProps.length) {
 | |
|                 unquotedProps.forEach(property => {
 | |
|                     context.report({
 | |
|                         node: property,
 | |
|                         messageId: "inconsistentlyQuotedProperty",
 | |
|                         data: { key: property.key.name || property.key.value },
 | |
|                         fix: fixer => fixer.replaceText(property.key, getQuotedKey(property.key))
 | |
|                     });
 | |
|                 });
 | |
|             }
 | |
|         }
 | |
| 
 | |
|         return {
 | |
|             Property(node) {
 | |
|                 if (MODE === "always" || !MODE) {
 | |
|                     checkOmittedQuotes(node);
 | |
|                 }
 | |
|                 if (MODE === "as-needed") {
 | |
|                     checkUnnecessaryQuotes(node);
 | |
|                 }
 | |
|             },
 | |
|             ObjectExpression(node) {
 | |
|                 if (MODE === "consistent") {
 | |
|                     checkConsistency(node, false);
 | |
|                 }
 | |
|                 if (MODE === "consistent-as-needed") {
 | |
|                     checkConsistency(node, true);
 | |
|                 }
 | |
|             }
 | |
|         };
 | |
| 
 | |
|     }
 | |
| };
 |