You can not select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
					
					
						
							229 lines
						
					
					
						
							7.4 KiB
						
					
					
				
			
		
		
	
	
							229 lines
						
					
					
						
							7.4 KiB
						
					
					
				/** | 
						|
 * @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; | 
						|
 | 
						|
    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: { | 
						|
        type: "problem", | 
						|
 | 
						|
        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 | 
						|
                }); | 
						|
            }); | 
						|
 | 
						|
            scope.childScopes.forEach(findVariablesInScope); | 
						|
        } | 
						|
 | 
						|
        return { | 
						|
            Program() { | 
						|
                findVariablesInScope(context.getScope()); | 
						|
            } | 
						|
        }; | 
						|
    } | 
						|
};
 | 
						|
 |