| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 | /** * @author Nicholas C. Zakas * See LICENSE file in root directory for full license. */"use strict"const path = require("path")const { READ, ReferenceTracker, getStringIfConstant } = require("eslint-utils")/** * Get the first char of the specified template element. * @param {TemplateLiteral} node The `TemplateLiteral` node to get. * @param {number} i The number of template elements to get first char. * @param {Set<Node>} sepNodes The nodes of `path.sep`. * @param {import("escope").Scope} globalScope The global scope object. * @param {string[]} outNextChars The array to collect chars. * @returns {void} */function collectFirstCharsOfTemplateElement(    node,    i,    sepNodes,    globalScope,    outNextChars) {    const element = node.quasis[i].value.cooked    if (element == null) {        return    }    if (element !== "") {        outNextChars.push(element[0])        return    }    if (node.expressions.length > i) {        collectFirstChars(            node.expressions[i],            sepNodes,            globalScope,            outNextChars        )    }}/** * Get the first char of a given node. * @param {TemplateLiteral} node The `TemplateLiteral` node to get. * @param {Set<Node>} sepNodes The nodes of `path.sep`. * @param {import("escope").Scope} globalScope The global scope object. * @param {string[]} outNextChars The array to collect chars. * @returns {void} */function collectFirstChars(node, sepNodes, globalScope, outNextChars) {    switch (node.type) {        case "AssignmentExpression":            collectFirstChars(node.right, sepNodes, globalScope, outNextChars)            break        case "BinaryExpression":            collectFirstChars(node.left, sepNodes, globalScope, outNextChars)            break        case "ConditionalExpression":            collectFirstChars(                node.consequent,                sepNodes,                globalScope,                outNextChars            )            collectFirstChars(                node.alternate,                sepNodes,                globalScope,                outNextChars            )            break        case "LogicalExpression":            collectFirstChars(node.left, sepNodes, globalScope, outNextChars)            collectFirstChars(node.right, sepNodes, globalScope, outNextChars)            break        case "SequenceExpression":            collectFirstChars(                node.expressions[node.expressions.length - 1],                sepNodes,                globalScope,                outNextChars            )            break        case "TemplateLiteral":            collectFirstCharsOfTemplateElement(                node,                0,                sepNodes,                globalScope,                outNextChars            )            break        case "Identifier":        case "MemberExpression":            if (sepNodes.has(node)) {                outNextChars.push(path.sep)                break            }        // fallthrough        default: {            const str = getStringIfConstant(node, globalScope)            if (str) {                outNextChars.push(str[0])            }        }    }}/** * Check if a char is a path separator or not. * @param {string} c The char to check. * @returns {boolean} `true` if the char is a path separator. */function isPathSeparator(c) {    return c === "/" || c === path.sep}/** * Check if the given Identifier node is followed by string concatenation with a * path separator. * @param {Identifier} node The `__dirname` or `__filename` node to check. * @param {Set<Node>} sepNodes The nodes of `path.sep`. * @param {import("escope").Scope} globalScope The global scope object. * @returns {boolean} `true` if the given Identifier node is followed by string * concatenation with a path separator. */function isConcat(node, sepNodes, globalScope) {    const parent = node.parent    const nextChars = []    if (        parent.type === "BinaryExpression" &&        parent.operator === "+" &&        parent.left === node    ) {        collectFirstChars(            parent.right,            sepNodes,            globalScope,            /* out */ nextChars        )    } else if (parent.type === "TemplateLiteral") {        collectFirstCharsOfTemplateElement(            parent,            parent.expressions.indexOf(node) + 1,            sepNodes,            globalScope,            /* out */ nextChars        )    }    return nextChars.some(isPathSeparator)}module.exports = {    meta: {        type: "suggestion",        docs: {            description:                "disallow string concatenation with `__dirname` and `__filename`",            category: "Possible Errors",            recommended: false,            url:                "https://github.com/mysticatea/eslint-plugin-node/blob/v11.1.0/docs/rules/no-path-concat.md",        },        fixable: null,        schema: [],        messages: {            usePathFunctions:                "Use path.join() or path.resolve() instead of string concatenation.",        },    },    create(context) {        return {            "Program:exit"() {                const globalScope = context.getScope()                const tracker = new ReferenceTracker(globalScope)                const sepNodes = new Set()                // Collect `paht.sep` references                for (const { node } of tracker.iterateCjsReferences({                    path: { sep: { [READ]: true } },                })) {                    sepNodes.add(node)                }                for (const { node } of tracker.iterateEsmReferences({                    path: { sep: { [READ]: true } },                })) {                    sepNodes.add(node)                }                // Verify `__dirname` and `__filename`                for (const { node } of tracker.iterateGlobalReferences({                    __dirname: { [READ]: true },                    __filename: { [READ]: true },                })) {                    if (isConcat(node, sepNodes, globalScope)) {                        context.report({                            node: node.parent,                            messageId: "usePathFunctions",                        })                    }                }            },        }    },}
 |