| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347 | /** * @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)$/u;const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;/** * 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;    let allowNamedExports = false;    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;        allowNamedExports = !!options.allowNamedExports;    }    return { functions, classes, variables, allowNamedExports };}/** * 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 location is inside of the range of a class static initializer. * Static initializers are static blocks and initializers of static fields. * @param {ASTNode} node `ClassBody` node to check static initializers. * @param {number} location A location to check. * @returns {boolean} `true` if the location is inside of a class static initializer. */function isInClassStaticInitializerRange(node, location) {    return node.body.some(classMember => (        (            classMember.type === "StaticBlock" &&            isInRange(classMember, location)        ) ||        (            classMember.type === "PropertyDefinition" &&            classMember.static &&            classMember.value &&            isInRange(classMember.value, location)        )    ));}/** * Checks whether a given scope is the scope of a class static initializer. * Static initializers are static blocks and initializers of static fields. * @param {eslint-scope.Scope} scope A scope to check. * @returns {boolean} `true` if the scope is a class static initializer scope. */function isClassStaticInitializerScope(scope) {    if (scope.type === "class-static-block") {        return true;    }    if (scope.type === "class-field-initializer") {        // `scope.block` is PropertyDefinition#value node        const propertyDefinition = scope.block.parent;        return propertyDefinition.static;    }    return false;}/** * Checks whether a given reference is evaluated in an execution context * that isn't the one where the variable it refers to is defined. * Execution contexts are: * - top-level * - functions * - class field initializers (implicit functions) * - class static blocks (implicit functions) * Static class field initializers and class static blocks are automatically run during the class definition evaluation, * and therefore we'll consider them as a part of the parent execution context. * Example: * *   const x = 1; * *   x; // returns `false` *   () => x; // returns `true` * *   class C { *       field = x; // returns `true` *       static field = x; // returns `false` * *       method() { *           x; // returns `true` *       } * *       static method() { *           x; // returns `true` *       } * *       static { *           x; // returns `false` *       } *   } * @param {eslint-scope.Reference} reference A reference to check. * @returns {boolean} `true` if the reference is from a separate execution context. */function isFromSeparateExecutionContext(reference) {    const variable = reference.resolved;    let scope = reference.from;    // Scope#variableScope represents execution context    while (variable.scope.variableScope !== scope.variableScope) {        if (isClassStaticInitializerScope(scope.variableScope)) {            scope = scope.variableScope.upper;        } else {            return true;        }    }    return false;}/** * Checks whether or not a given reference is evaluated during the initialization of its 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) {} *     var C = class { [C]; }; *     var C = class { static foo = C; }; *     var C = class { static { foo = C; } }; *     class C extends C {} *     class C extends (class { static foo = C; }) {} *     class C { [C]; } * @param {Reference} reference A reference to check. * @returns {boolean} `true` if the reference is evaluated during the initialization. */function isEvaluatedDuringInitialization(reference) {    if (isFromSeparateExecutionContext(reference)) {        /*         * Even if the reference appears in the initializer, it isn't evaluated during the initialization.         * For example, `const x = () => x;` is valid.         */        return false;    }    const location = reference.identifier.range[1];    const definition = reference.resolved.defs[0];    if (definition.type === "ClassName") {        // `ClassDeclaration` or `ClassExpression`        const classDefinition = definition.node;        return (            isInRange(classDefinition, location) &&            /*             * Class binding is initialized before running static initializers.             * For example, `class C { static foo = C; static { bar = C; } }` is valid.             */            !isInClassStaticInitializerRange(classDefinition.body, location)        );    }    let node = definition.name.parent;    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//------------------------------------------------------------------------------/** @type {import('../shared/types').Rule} */module.exports = {    meta: {        type: "problem",        docs: {            description: "Disallow the use of variables before they are defined",            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" },                            allowNamedExports: { type: "boolean" }                        },                        additionalProperties: false                    }                ]            }        ],        messages: {            usedBeforeDefined: "'{{name}}' was used before it was defined."        }    },    create(context) {        const options = parseOptions(context.options[0]);        /**         * Determines whether a given reference should be checked.         *         * Returns `false` if the reference is:         * - initialization's (e.g., `let a = 1`).         * - referring to an undefined variable (i.e., if it's an unresolved reference).         * - referring to a variable that is defined, but not in the given source code         *   (e.g., global environment variable or `arguments` in functions).         * - allowed by options.         * @param {eslint-scope.Reference} reference The reference         * @returns {boolean} `true` if the reference should be checked         */        function shouldCheck(reference) {            if (reference.init) {                return false;            }            const { identifier } = reference;            if (                options.allowNamedExports &&                identifier.parent.type === "ExportSpecifier" &&                identifier.parent.local === identifier            ) {                return false;            }            const variable = reference.resolved;            if (!variable || variable.defs.length === 0) {                return false;            }            const definitionType = variable.defs[0].type;            if (!options.functions && definitionType === "FunctionName") {                return false;            }            if (                (                    !options.variables && definitionType === "Variable" ||                    !options.classes && definitionType === "ClassName"                ) &&                // don't skip checking the reference if it's in the same execution context, because of TDZ                isFromSeparateExecutionContext(reference)            ) {                return false;            }            return true;        }        /**         * Finds and validates all references in a given scope and its child scopes.         * @param {eslint-scope.Scope} scope The scope object.         * @returns {void}         */        function checkReferencesInScope(scope) {            scope.references.filter(shouldCheck).forEach(reference => {                const variable = reference.resolved;                const definitionIdentifier = variable.defs[0].name;                if (                    reference.identifier.range[1] < definitionIdentifier.range[1] ||                    isEvaluatedDuringInitialization(reference)                ) {                    context.report({                        node: reference.identifier,                        messageId: "usedBeforeDefined",                        data: reference.identifier                    });                }            });            scope.childScopes.forEach(checkReferencesInScope);        }        return {            Program() {                checkReferencesInScope(context.getScope());            }        };    }};
 |