| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 | /** * @fileoverview Rule to flag use of variables before they are defined * @author Ilya Volodin */"use strict";//------------------------------------------------------------------------------// Helpers//------------------------------------------------------------------------------const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/;const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/;/** * Parses a given value as options. * * @param {any} options - A value to parse. * @returns {Object} The parsed options. */function parseOptions(options) {    let functions = true;    let classes = true;    let variables = true;    if (typeof options === "string") {        functions = (options !== "nofunc");    } else if (typeof options === "object" && options !== null) {        functions = options.functions !== false;        classes = options.classes !== false;        variables = options.variables !== false;    }    return { functions, classes, variables };}/** * Checks whether or not a given variable is a function declaration. * * @param {eslint-scope.Variable} variable - A variable to check. * @returns {boolean} `true` if the variable is a function declaration. */function isFunction(variable) {    return variable.defs[0].type === "FunctionName";}/** * Checks whether or not a given variable is a class declaration in an upper function scope. * * @param {eslint-scope.Variable} variable - A variable to check. * @param {eslint-scope.Reference} reference - A reference to check. * @returns {boolean} `true` if the variable is a class declaration. */function isOuterClass(variable, reference) {    return (        variable.defs[0].type === "ClassName" &&        variable.scope.variableScope !== reference.from.variableScope    );}/** * Checks whether or not a given variable is a variable declaration in an upper function scope. * @param {eslint-scope.Variable} variable - A variable to check. * @param {eslint-scope.Reference} reference - A reference to check. * @returns {boolean} `true` if the variable is a variable declaration. */function isOuterVariable(variable, reference) {    return (        variable.defs[0].type === "Variable" &&        variable.scope.variableScope !== reference.from.variableScope    );}/** * Checks whether or not a given location is inside of the range of a given node. * * @param {ASTNode} node - An node to check. * @param {number} location - A location to check. * @returns {boolean} `true` if the location is inside of the range of the node. */function isInRange(node, location) {    return node && node.range[0] <= location && location <= node.range[1];}/** * Checks whether or not a given reference is inside of the initializers of a given variable. * * This returns `true` in the following cases: * *     var a = a *     var [a = a] = list *     var {a = a} = obj *     for (var a in a) {} *     for (var a of a) {} * * @param {Variable} variable - A variable to check. * @param {Reference} reference - A reference to check. * @returns {boolean} `true` if the reference is inside of the initializers. */function isInInitializer(variable, reference) {    if (variable.scope !== reference.from) {        return false;    }    let node = variable.identifiers[0].parent;    const location = reference.identifier.range[1];    while (node) {        if (node.type === "VariableDeclarator") {            if (isInRange(node.init, location)) {                return true;            }            if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&                isInRange(node.parent.parent.right, location)            ) {                return true;            }            break;        } else if (node.type === "AssignmentPattern") {            if (isInRange(node.right, location)) {                return true;            }        } else if (SENTINEL_TYPE.test(node.type)) {            break;        }        node = node.parent;    }    return false;}//------------------------------------------------------------------------------// Rule Definition//------------------------------------------------------------------------------module.exports = {    meta: {        docs: {            description: "disallow the use of variables before they are defined",            category: "Variables",            recommended: false,            url: "https://eslint.org/docs/rules/no-use-before-define"        },        schema: [            {                oneOf: [                    {                        enum: ["nofunc"]                    },                    {                        type: "object",                        properties: {                            functions: { type: "boolean" },                            classes: { type: "boolean" },                            variables: { type: "boolean" }                        },                        additionalProperties: false                    }                ]            }        ]    },    create(context) {        const options = parseOptions(context.options[0]);        /**         * Determines whether a given use-before-define case should be reported according to the options.         * @param {eslint-scope.Variable} variable The variable that gets used before being defined         * @param {eslint-scope.Reference} reference The reference to the variable         * @returns {boolean} `true` if the usage should be reported         */        function isForbidden(variable, reference) {            if (isFunction(variable)) {                return options.functions;            }            if (isOuterClass(variable, reference)) {                return options.classes;            }            if (isOuterVariable(variable, reference)) {                return options.variables;            }            return true;        }        /**         * Finds and validates all variables in a given scope.         * @param {Scope} scope The scope object.         * @returns {void}         * @private         */        function findVariablesInScope(scope) {            scope.references.forEach(reference => {                const variable = reference.resolved;                /*                 * Skips when the reference is:                 * - initialization's.                 * - referring to an undefined variable.                 * - referring to a global environment variable (there're no identifiers).                 * - located preceded by the variable (except in initializers).                 * - allowed by options.                 */                if (reference.init ||                    !variable ||                    variable.identifiers.length === 0 ||                    (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||                    !isForbidden(variable, reference)                ) {                    return;                }                // Reports.                context.report({                    node: reference.identifier,                    message: "'{{name}}' was used before it was defined.",                    data: reference.identifier                });            });        }        /**         * Validates variables inside of a node's scope.         * @param {ASTNode} node The node to check.         * @returns {void}         * @private         */        function findVariables() {            const scope = context.getScope();            findVariablesInScope(scope);        }        const ruleDefinition = {            "Program:exit"(node) {                const scope = context.getScope(),                    ecmaFeatures = context.parserOptions.ecmaFeatures || {};                findVariablesInScope(scope);                // both Node.js and Modules have an extra scope                if (ecmaFeatures.globalReturn || node.sourceType === "module") {                    findVariablesInScope(scope.childScopes[0]);                }            }        };        if (context.parserOptions.ecmaVersion >= 6) {            ruleDefinition["BlockStatement:exit"] =                ruleDefinition["SwitchStatement:exit"] = findVariables;            ruleDefinition["ArrowFunctionExpression:exit"] = function(node) {                if (node.body.type !== "BlockStatement") {                    findVariables();                }            };        } else {            ruleDefinition["FunctionExpression:exit"] =                ruleDefinition["FunctionDeclaration:exit"] =                ruleDefinition["ArrowFunctionExpression:exit"] = findVariables;        }        return ruleDefinition;    }};
 |