.
This commit is contained in:
		
							
								
								
									
										275
									
								
								qwen/nodejs/node_modules/eslint/lib/rules/prefer-template.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										275
									
								
								qwen/nodejs/node_modules/eslint/lib/rules/prefer-template.js
									
									
									
										generated
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,275 @@
 | 
			
		||||
/**
 | 
			
		||||
 * @fileoverview A rule to suggest using template literals instead of string concatenation.
 | 
			
		||||
 * @author Toru Nagashima
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
"use strict";
 | 
			
		||||
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
// Requirements
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
const astUtils = require("./utils/ast-utils");
 | 
			
		||||
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
// Helpers
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks whether or not a given node is a concatenation.
 | 
			
		||||
 * @param {ASTNode} node A node to check.
 | 
			
		||||
 * @returns {boolean} `true` if the node is a concatenation.
 | 
			
		||||
 */
 | 
			
		||||
function isConcatenation(node) {
 | 
			
		||||
    return node.type === "BinaryExpression" && node.operator === "+";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the top binary expression node for concatenation in parents of a given node.
 | 
			
		||||
 * @param {ASTNode} node A node to get.
 | 
			
		||||
 * @returns {ASTNode} the top binary expression node in parents of a given node.
 | 
			
		||||
 */
 | 
			
		||||
function getTopConcatBinaryExpression(node) {
 | 
			
		||||
    let currentNode = node;
 | 
			
		||||
 | 
			
		||||
    while (isConcatenation(currentNode.parent)) {
 | 
			
		||||
        currentNode = currentNode.parent;
 | 
			
		||||
    }
 | 
			
		||||
    return currentNode;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks whether or not a node contains a string literal with an octal or non-octal decimal escape sequence
 | 
			
		||||
 * @param {ASTNode} node A node to check
 | 
			
		||||
 * @returns {boolean} `true` if at least one string literal within the node contains
 | 
			
		||||
 * an octal or non-octal decimal escape sequence
 | 
			
		||||
 */
 | 
			
		||||
function hasOctalOrNonOctalDecimalEscapeSequence(node) {
 | 
			
		||||
    if (isConcatenation(node)) {
 | 
			
		||||
        return (
 | 
			
		||||
            hasOctalOrNonOctalDecimalEscapeSequence(node.left) ||
 | 
			
		||||
            hasOctalOrNonOctalDecimalEscapeSequence(node.right)
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // No need to check TemplateLiterals – would throw parsing error
 | 
			
		||||
    if (node.type === "Literal" && typeof node.value === "string") {
 | 
			
		||||
        return astUtils.hasOctalOrNonOctalDecimalEscapeSequence(node.raw);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks whether or not a given binary expression has string literals.
 | 
			
		||||
 * @param {ASTNode} node A node to check.
 | 
			
		||||
 * @returns {boolean} `true` if the node has string literals.
 | 
			
		||||
 */
 | 
			
		||||
function hasStringLiteral(node) {
 | 
			
		||||
    if (isConcatenation(node)) {
 | 
			
		||||
 | 
			
		||||
        // `left` is deeper than `right` normally.
 | 
			
		||||
        return hasStringLiteral(node.right) || hasStringLiteral(node.left);
 | 
			
		||||
    }
 | 
			
		||||
    return astUtils.isStringLiteral(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Checks whether or not a given binary expression has non string literals.
 | 
			
		||||
 * @param {ASTNode} node A node to check.
 | 
			
		||||
 * @returns {boolean} `true` if the node has non string literals.
 | 
			
		||||
 */
 | 
			
		||||
function hasNonStringLiteral(node) {
 | 
			
		||||
    if (isConcatenation(node)) {
 | 
			
		||||
 | 
			
		||||
        // `left` is deeper than `right` normally.
 | 
			
		||||
        return hasNonStringLiteral(node.right) || hasNonStringLiteral(node.left);
 | 
			
		||||
    }
 | 
			
		||||
    return !astUtils.isStringLiteral(node);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Determines whether a given node will start with a template curly expression (`${}`) when being converted to a template literal.
 | 
			
		||||
 * @param {ASTNode} node The node that will be fixed to a template literal
 | 
			
		||||
 * @returns {boolean} `true` if the node will start with a template curly.
 | 
			
		||||
 */
 | 
			
		||||
function startsWithTemplateCurly(node) {
 | 
			
		||||
    if (node.type === "BinaryExpression") {
 | 
			
		||||
        return startsWithTemplateCurly(node.left);
 | 
			
		||||
    }
 | 
			
		||||
    if (node.type === "TemplateLiteral") {
 | 
			
		||||
        return node.expressions.length && node.quasis.length && node.quasis[0].range[0] === node.quasis[0].range[1];
 | 
			
		||||
    }
 | 
			
		||||
    return node.type !== "Literal" || typeof node.value !== "string";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Determines whether a given node end with a template curly expression (`${}`) when being converted to a template literal.
 | 
			
		||||
 * @param {ASTNode} node The node that will be fixed to a template literal
 | 
			
		||||
 * @returns {boolean} `true` if the node will end with a template curly.
 | 
			
		||||
 */
 | 
			
		||||
function endsWithTemplateCurly(node) {
 | 
			
		||||
    if (node.type === "BinaryExpression") {
 | 
			
		||||
        return startsWithTemplateCurly(node.right);
 | 
			
		||||
    }
 | 
			
		||||
    if (node.type === "TemplateLiteral") {
 | 
			
		||||
        return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].range[0] === node.quasis[node.quasis.length - 1].range[1];
 | 
			
		||||
    }
 | 
			
		||||
    return node.type !== "Literal" || typeof node.value !== "string";
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
// Rule Definition
 | 
			
		||||
//------------------------------------------------------------------------------
 | 
			
		||||
 | 
			
		||||
/** @type {import('../shared/types').Rule} */
 | 
			
		||||
module.exports = {
 | 
			
		||||
    meta: {
 | 
			
		||||
        type: "suggestion",
 | 
			
		||||
 | 
			
		||||
        docs: {
 | 
			
		||||
            description: "Require template literals instead of string concatenation",
 | 
			
		||||
            recommended: false,
 | 
			
		||||
            url: "https://eslint.org/docs/latest/rules/prefer-template"
 | 
			
		||||
        },
 | 
			
		||||
 | 
			
		||||
        schema: [],
 | 
			
		||||
        fixable: "code",
 | 
			
		||||
 | 
			
		||||
        messages: {
 | 
			
		||||
            unexpectedStringConcatenation: "Unexpected string concatenation."
 | 
			
		||||
        }
 | 
			
		||||
    },
 | 
			
		||||
 | 
			
		||||
    create(context) {
 | 
			
		||||
        const sourceCode = context.sourceCode;
 | 
			
		||||
        let done = Object.create(null);
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Gets the non-token text between two nodes, ignoring any other tokens that appear between the two tokens.
 | 
			
		||||
         * @param {ASTNode} node1 The first node
 | 
			
		||||
         * @param {ASTNode} node2 The second node
 | 
			
		||||
         * @returns {string} The text between the nodes, excluding other tokens
 | 
			
		||||
         */
 | 
			
		||||
        function getTextBetween(node1, node2) {
 | 
			
		||||
            const allTokens = [node1].concat(sourceCode.getTokensBetween(node1, node2)).concat(node2);
 | 
			
		||||
            const sourceText = sourceCode.getText();
 | 
			
		||||
 | 
			
		||||
            return allTokens.slice(0, -1).reduce((accumulator, token, index) => accumulator + sourceText.slice(token.range[1], allTokens[index + 1].range[0]), "");
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns a template literal form of the given node.
 | 
			
		||||
         * @param {ASTNode} currentNode A node that should be converted to a template literal
 | 
			
		||||
         * @param {string} textBeforeNode Text that should appear before the node
 | 
			
		||||
         * @param {string} textAfterNode Text that should appear after the node
 | 
			
		||||
         * @returns {string} A string form of this node, represented as a template literal
 | 
			
		||||
         */
 | 
			
		||||
        function getTemplateLiteral(currentNode, textBeforeNode, textAfterNode) {
 | 
			
		||||
            if (currentNode.type === "Literal" && typeof currentNode.value === "string") {
 | 
			
		||||
 | 
			
		||||
                /*
 | 
			
		||||
                 * If the current node is a string literal, escape any instances of ${ or ` to prevent them from being interpreted
 | 
			
		||||
                 * as a template placeholder. However, if the code already contains a backslash before the ${ or `
 | 
			
		||||
                 * for some reason, don't add another backslash, because that would change the meaning of the code (it would cause
 | 
			
		||||
                 * an actual backslash character to appear before the dollar sign).
 | 
			
		||||
                 */
 | 
			
		||||
                return `\`${currentNode.raw.slice(1, -1).replace(/\\*(\$\{|`)/gu, matched => {
 | 
			
		||||
                    if (matched.lastIndexOf("\\") % 2) {
 | 
			
		||||
                        return `\\${matched}`;
 | 
			
		||||
                    }
 | 
			
		||||
                    return matched;
 | 
			
		||||
 | 
			
		||||
                // Unescape any quotes that appear in the original Literal that no longer need to be escaped.
 | 
			
		||||
                }).replace(new RegExp(`\\\\${currentNode.raw[0]}`, "gu"), currentNode.raw[0])}\``;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (currentNode.type === "TemplateLiteral") {
 | 
			
		||||
                return sourceCode.getText(currentNode);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (isConcatenation(currentNode) && hasStringLiteral(currentNode)) {
 | 
			
		||||
                const plusSign = sourceCode.getFirstTokenBetween(currentNode.left, currentNode.right, token => token.value === "+");
 | 
			
		||||
                const textBeforePlus = getTextBetween(currentNode.left, plusSign);
 | 
			
		||||
                const textAfterPlus = getTextBetween(plusSign, currentNode.right);
 | 
			
		||||
                const leftEndsWithCurly = endsWithTemplateCurly(currentNode.left);
 | 
			
		||||
                const rightStartsWithCurly = startsWithTemplateCurly(currentNode.right);
 | 
			
		||||
 | 
			
		||||
                if (leftEndsWithCurly) {
 | 
			
		||||
 | 
			
		||||
                    // If the left side of the expression ends with a template curly, add the extra text to the end of the curly bracket.
 | 
			
		||||
                    // `foo${bar}` /* comment */ + 'baz' --> `foo${bar /* comment */  }${baz}`
 | 
			
		||||
                    return getTemplateLiteral(currentNode.left, textBeforeNode, textBeforePlus + textAfterPlus).slice(0, -1) +
 | 
			
		||||
                        getTemplateLiteral(currentNode.right, null, textAfterNode).slice(1);
 | 
			
		||||
                }
 | 
			
		||||
                if (rightStartsWithCurly) {
 | 
			
		||||
 | 
			
		||||
                    // Otherwise, if the right side of the expression starts with a template curly, add the text there.
 | 
			
		||||
                    // 'foo' /* comment */ + `${bar}baz` --> `foo${ /* comment */  bar}baz`
 | 
			
		||||
                    return getTemplateLiteral(currentNode.left, textBeforeNode, null).slice(0, -1) +
 | 
			
		||||
                        getTemplateLiteral(currentNode.right, textBeforePlus + textAfterPlus, textAfterNode).slice(1);
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                /*
 | 
			
		||||
                 * Otherwise, these nodes should not be combined into a template curly, since there is nowhere to put
 | 
			
		||||
                 * the text between them.
 | 
			
		||||
                 */
 | 
			
		||||
                return `${getTemplateLiteral(currentNode.left, textBeforeNode, null)}${textBeforePlus}+${textAfterPlus}${getTemplateLiteral(currentNode.right, textAfterNode, null)}`;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return `\`\${${textBeforeNode || ""}${sourceCode.getText(currentNode)}${textAfterNode || ""}}\``;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Returns a fixer object that converts a non-string binary expression to a template literal
 | 
			
		||||
         * @param {SourceCodeFixer} fixer The fixer object
 | 
			
		||||
         * @param {ASTNode} node A node that should be converted to a template literal
 | 
			
		||||
         * @returns {Object} A fix for this binary expression
 | 
			
		||||
         */
 | 
			
		||||
        function fixNonStringBinaryExpression(fixer, node) {
 | 
			
		||||
            const topBinaryExpr = getTopConcatBinaryExpression(node.parent);
 | 
			
		||||
 | 
			
		||||
            if (hasOctalOrNonOctalDecimalEscapeSequence(topBinaryExpr)) {
 | 
			
		||||
                return null;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            return fixer.replaceText(topBinaryExpr, getTemplateLiteral(topBinaryExpr, null, null));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /**
 | 
			
		||||
         * Reports if a given node is string concatenation with non string literals.
 | 
			
		||||
         * @param {ASTNode} node A node to check.
 | 
			
		||||
         * @returns {void}
 | 
			
		||||
         */
 | 
			
		||||
        function checkForStringConcat(node) {
 | 
			
		||||
            if (!astUtils.isStringLiteral(node) || !isConcatenation(node.parent)) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            const topBinaryExpr = getTopConcatBinaryExpression(node.parent);
 | 
			
		||||
 | 
			
		||||
            // Checks whether or not this node had been checked already.
 | 
			
		||||
            if (done[topBinaryExpr.range[0]]) {
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
            done[topBinaryExpr.range[0]] = true;
 | 
			
		||||
 | 
			
		||||
            if (hasNonStringLiteral(topBinaryExpr)) {
 | 
			
		||||
                context.report({
 | 
			
		||||
                    node: topBinaryExpr,
 | 
			
		||||
                    messageId: "unexpectedStringConcatenation",
 | 
			
		||||
                    fix: fixer => fixNonStringBinaryExpression(fixer, node)
 | 
			
		||||
                });
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return {
 | 
			
		||||
            Program() {
 | 
			
		||||
                done = Object.create(null);
 | 
			
		||||
            },
 | 
			
		||||
 | 
			
		||||
            Literal: checkForStringConcat,
 | 
			
		||||
            TemplateLiteral: checkForStringConcat
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
};
 | 
			
		||||
		Reference in New Issue
	
	Block a user