2 * @fileoverview Rule to flag use of variables before they are defined
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
13 const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
16 * Parses a given value as options.
17 * @param {any} options A value to parse.
18 * @returns {Object} The parsed options.
20 function parseOptions(options) {
25 if (typeof options === "string") {
26 functions = (options !== "nofunc");
27 } else if (typeof options === "object" && options !== null) {
28 functions = options.functions !== false;
29 classes = options.classes !== false;
30 variables = options.variables !== false;
33 return { functions, classes, variables };
37 * Checks whether or not a given variable is a function declaration.
38 * @param {eslint-scope.Variable} variable A variable to check.
39 * @returns {boolean} `true` if the variable is a function declaration.
41 function isFunction(variable) {
42 return variable.defs[0].type === "FunctionName";
46 * Checks whether or not a given variable is a class declaration in an upper function scope.
47 * @param {eslint-scope.Variable} variable A variable to check.
48 * @param {eslint-scope.Reference} reference A reference to check.
49 * @returns {boolean} `true` if the variable is a class declaration.
51 function isOuterClass(variable, reference) {
53 variable.defs[0].type === "ClassName" &&
54 variable.scope.variableScope !== reference.from.variableScope
59 * Checks whether or not a given variable is a variable declaration in an upper function scope.
60 * @param {eslint-scope.Variable} variable A variable to check.
61 * @param {eslint-scope.Reference} reference A reference to check.
62 * @returns {boolean} `true` if the variable is a variable declaration.
64 function isOuterVariable(variable, reference) {
66 variable.defs[0].type === "Variable" &&
67 variable.scope.variableScope !== reference.from.variableScope
72 * Checks whether or not a given location is inside of the range of a given node.
73 * @param {ASTNode} node An node to check.
74 * @param {number} location A location to check.
75 * @returns {boolean} `true` if the location is inside of the range of the node.
77 function isInRange(node, location) {
78 return node && node.range[0] <= location && location <= node.range[1];
82 * Checks whether or not a given reference is inside of the initializers of a given variable.
84 * This returns `true` in the following cases:
91 * @param {Variable} variable A variable to check.
92 * @param {Reference} reference A reference to check.
93 * @returns {boolean} `true` if the reference is inside of the initializers.
95 function isInInitializer(variable, reference) {
96 if (variable.scope !== reference.from) {
100 let node = variable.identifiers[0].parent;
101 const location = reference.identifier.range[1];
104 if (node.type === "VariableDeclarator") {
105 if (isInRange(node.init, location)) {
108 if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
109 isInRange(node.parent.parent.right, location)
114 } else if (node.type === "AssignmentPattern") {
115 if (isInRange(node.right, location)) {
118 } else if (SENTINEL_TYPE.test(node.type)) {
128 //------------------------------------------------------------------------------
130 //------------------------------------------------------------------------------
137 description: "disallow the use of variables before they are defined",
138 category: "Variables",
140 url: "https://eslint.org/docs/rules/no-use-before-define"
152 functions: { type: "boolean" },
153 classes: { type: "boolean" },
154 variables: { type: "boolean" }
156 additionalProperties: false
163 usedBeforeDefined: "'{{name}}' was used before it was defined."
168 const options = parseOptions(context.options[0]);
171 * Determines whether a given use-before-define case should be reported according to the options.
172 * @param {eslint-scope.Variable} variable The variable that gets used before being defined
173 * @param {eslint-scope.Reference} reference The reference to the variable
174 * @returns {boolean} `true` if the usage should be reported
176 function isForbidden(variable, reference) {
177 if (isFunction(variable)) {
178 return options.functions;
180 if (isOuterClass(variable, reference)) {
181 return options.classes;
183 if (isOuterVariable(variable, reference)) {
184 return options.variables;
190 * Finds and validates all variables in a given scope.
191 * @param {Scope} scope The scope object.
195 function findVariablesInScope(scope) {
196 scope.references.forEach(reference => {
197 const variable = reference.resolved;
200 * Skips when the reference is:
201 * - initialization's.
202 * - referring to an undefined variable.
203 * - referring to a global environment variable (there're no identifiers).
204 * - located preceded by the variable (except in initializers).
205 * - allowed by options.
207 if (reference.init ||
209 variable.identifiers.length === 0 ||
210 (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||
211 !isForbidden(variable, reference)
218 node: reference.identifier,
219 messageId: "usedBeforeDefined",
220 data: reference.identifier
224 scope.childScopes.forEach(findVariablesInScope);
229 findVariablesInScope(context.getScope());