UNPKG

7.36 kBJavaScriptView Raw
1/**
2 * @fileoverview Rule to flag use of variables before they are defined
3 * @author Ilya Volodin
4 * @copyright 2013 Ilya Volodin. All rights reserved.
5 */
6
7"use strict";
8
9//------------------------------------------------------------------------------
10// Helpers
11//------------------------------------------------------------------------------
12
13var SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/;
14
15/**
16 * Parses a given value as options.
17 *
18 * @param {any} options - A value to parse.
19 * @returns {object} The parsed options.
20 */
21function parseOptions(options) {
22 var functions = true;
23 var classes = true;
24
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 }
31
32 return {functions: functions, classes: classes};
33}
34
35/**
36 * @returns {boolean} `false`.
37 */
38function alwaysFalse() {
39 return false;
40}
41
42/**
43 * Checks whether or not a given variable is a function declaration.
44 *
45 * @param {escope.Variable} variable - A variable to check.
46 * @returns {boolean} `true` if the variable is a function declaration.
47 */
48function isFunction(variable) {
49 return variable.defs[0].type === "FunctionName";
50}
51
52/**
53 * Checks whether or not a given variable is a class declaration in an upper function scope.
54 *
55 * @param {escope.Variable} variable - A variable to check.
56 * @param {escope.Reference} reference - A reference to check.
57 * @returns {boolean} `true` if the variable is a class declaration.
58 */
59function isOuterClass(variable, reference) {
60 return (
61 variable.defs[0].type === "ClassName" &&
62 variable.scope.variableScope !== reference.from.variableScope
63 );
64}
65
66/**
67 * Checks whether or not a given variable is a function declaration or a class declaration in an upper function scope.
68 *
69 * @param {escope.Variable} variable - A variable to check.
70 * @param {escope.Reference} reference - A reference to check.
71 * @returns {boolean} `true` if the variable is a function declaration or a class declaration.
72 */
73function isFunctionOrOuterClass(variable, reference) {
74 return isFunction(variable, reference) || isOuterClass(variable, reference);
75}
76
77/**
78 * Checks whether or not a given location is inside of the range of a given node.
79 *
80 * @param {ASTNode} node - An node to check.
81 * @param {number} location - A location to check.
82 * @returns {boolean} `true` if the location is inside of the range of the node.
83 */
84function isInRange(node, location) {
85 return node && node.range[0] <= location && location <= node.range[1];
86}
87
88/**
89 * Checks whether or not a given reference is inside of the initializers of a given variable.
90 *
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.
94 */
95function isInInitializer(variable, reference) {
96 if (variable.scope !== reference.from) {
97 return false;
98 }
99
100 var node = variable.identifiers[0].parent;
101 var location = reference.identifier.range[1];
102
103 while (node) {
104 if (node.type === "VariableDeclarator") {
105 if (isInRange(node.init, location)) {
106 return true;
107 }
108 break;
109 } else if (node.type === "AssignmentPattern") {
110 if (isInRange(node.right, location)) {
111 return true;
112 }
113 } else if (SENTINEL_TYPE.test(node.type)) {
114 break;
115 }
116
117 node = node.parent;
118 }
119
120 return false;
121}
122
123//------------------------------------------------------------------------------
124// Rule Definition
125//------------------------------------------------------------------------------
126
127module.exports = function(context) {
128 var options = parseOptions(context.options[0]);
129
130 // Defines a function which checks whether or not a reference is allowed according to the option.
131 var isAllowed;
132 if (options.functions && options.classes) {
133 isAllowed = alwaysFalse;
134 } else if (options.functions) {
135 isAllowed = isOuterClass;
136 } else if (options.classes) {
137 isAllowed = isFunction;
138 } else {
139 isAllowed = isFunctionOrOuterClass;
140 }
141
142 /**
143 * Finds and validates all variables in a given scope.
144 * @param {Scope} scope The scope object.
145 * @returns {void}
146 * @private
147 */
148 function findVariablesInScope(scope) {
149 scope.references.forEach(function(reference) {
150 var variable = reference.resolved;
151
152 // Skips when the reference is:
153 // - initialization's.
154 // - referring to an undefined variable.
155 // - referring to a global environment variable (there're no identifiers).
156 // - located preceded by the variable (except in initializers).
157 // - allowed by options.
158 if (reference.init ||
159 !variable ||
160 variable.identifiers.length === 0 ||
161 (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||
162 isAllowed(variable, reference)
163 ) {
164 return;
165 }
166
167 // Reports.
168 context.report({
169 node: reference.identifier,
170 message: "'{{name}}' was used before it was defined",
171 data: reference.identifier
172 });
173 });
174 }
175
176 /**
177 * Validates variables inside of a node's scope.
178 * @param {ASTNode} node The node to check.
179 * @returns {void}
180 * @private
181 */
182 function findVariables() {
183 var scope = context.getScope();
184 findVariablesInScope(scope);
185 }
186
187 var ruleDefinition = {
188 "Program:exit": function(node) {
189 var scope = context.getScope(),
190 ecmaFeatures = context.parserOptions.ecmaFeatures || {};
191
192 findVariablesInScope(scope);
193
194 // both Node.js and Modules have an extra scope
195 if (ecmaFeatures.globalReturn || node.sourceType === "module") {
196 findVariablesInScope(scope.childScopes[0]);
197 }
198 }
199 };
200
201 if (context.parserOptions.ecmaVersion >= 6) {
202 ruleDefinition["BlockStatement:exit"] =
203 ruleDefinition["SwitchStatement:exit"] = findVariables;
204
205 ruleDefinition["ArrowFunctionExpression:exit"] = function(node) {
206 if (node.body.type !== "BlockStatement") {
207 findVariables(node);
208 }
209 };
210 } else {
211 ruleDefinition["FunctionExpression:exit"] =
212 ruleDefinition["FunctionDeclaration:exit"] =
213 ruleDefinition["ArrowFunctionExpression:exit"] = findVariables;
214 }
215
216 return ruleDefinition;
217};
218
219module.exports.schema = [
220 {
221 "oneOf": [
222 {
223 "enum": ["nofunc"]
224 },
225 {
226 "type": "object",
227 "properties": {
228 "functions": {"type": "boolean"},
229 "classes": {"type": "boolean"}
230 },
231 "additionalProperties": false
232 }
233 ]
234 }
235];