UNPKG

10.6 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
5}) : (function(o, m, k, k2) {
6 if (k2 === undefined) k2 = k;
7 o[k2] = m[k];
8}));
9var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
10 Object.defineProperty(o, "default", { enumerable: true, value: v });
11}) : function(o, v) {
12 o["default"] = v;
13});
14var __importStar = (this && this.__importStar) || function (mod) {
15 if (mod && mod.__esModule) return mod;
16 var result = {};
17 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
18 __setModuleDefault(result, mod);
19 return result;
20};
21Object.defineProperty(exports, "__esModule", { value: true });
22const utils_1 = require("@typescript-eslint/utils");
23const scope_manager_1 = require("@typescript-eslint/scope-manager");
24const util = __importStar(require("../util"));
25const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/;
26/**
27 * Parses a given value as options.
28 */
29function parseOptions(options) {
30 let functions = true;
31 let classes = true;
32 let enums = true;
33 let variables = true;
34 let typedefs = true;
35 let ignoreTypeReferences = true;
36 if (typeof options === 'string') {
37 functions = options !== 'nofunc';
38 }
39 else if (typeof options === 'object' && options !== null) {
40 functions = options.functions !== false;
41 classes = options.classes !== false;
42 enums = options.enums !== false;
43 variables = options.variables !== false;
44 typedefs = options.typedefs !== false;
45 ignoreTypeReferences = options.ignoreTypeReferences !== false;
46 }
47 return {
48 functions,
49 classes,
50 enums,
51 variables,
52 typedefs,
53 ignoreTypeReferences,
54 };
55}
56/**
57 * Checks whether or not a given variable is a function declaration.
58 */
59function isFunction(variable) {
60 return variable.defs[0].type === scope_manager_1.DefinitionType.FunctionName;
61}
62/**
63 * Checks whether or not a given variable is a type declaration.
64 */
65function isTypedef(variable) {
66 return variable.defs[0].type === scope_manager_1.DefinitionType.Type;
67}
68/**
69 * Checks whether or not a given variable is a enum declaration.
70 */
71function isOuterEnum(variable, reference) {
72 return (variable.defs[0].type == scope_manager_1.DefinitionType.TSEnumName &&
73 variable.scope.variableScope !== reference.from.variableScope);
74}
75/**
76 * Checks whether or not a given variable is a class declaration in an upper function scope.
77 */
78function isOuterClass(variable, reference) {
79 return (variable.defs[0].type === scope_manager_1.DefinitionType.ClassName &&
80 variable.scope.variableScope !== reference.from.variableScope);
81}
82/**
83 * Checks whether or not a given variable is a variable declaration in an upper function scope.
84 */
85function isOuterVariable(variable, reference) {
86 return (variable.defs[0].type === scope_manager_1.DefinitionType.Variable &&
87 variable.scope.variableScope !== reference.from.variableScope);
88}
89/**
90 * Recursively checks whether or not a given reference has a type query declaration among it's parents
91 */
92function referenceContainsTypeQuery(node) {
93 switch (node.type) {
94 case utils_1.AST_NODE_TYPES.TSTypeQuery:
95 return true;
96 case utils_1.AST_NODE_TYPES.TSQualifiedName:
97 case utils_1.AST_NODE_TYPES.Identifier:
98 if (!node.parent) {
99 return false;
100 }
101 return referenceContainsTypeQuery(node.parent);
102 default:
103 // if we find a different node, there's no chance that we're in a TSTypeQuery
104 return false;
105 }
106}
107/**
108 * Checks whether or not a given reference is a type reference.
109 */
110function isTypeReference(reference) {
111 return (reference.isTypeReference ||
112 referenceContainsTypeQuery(reference.identifier));
113}
114/**
115 * Checks whether or not a given location is inside of the range of a given node.
116 */
117function isInRange(node, location) {
118 return !!node && node.range[0] <= location && location <= node.range[1];
119}
120/**
121 * Decorators are transpiled such that the decorator is placed after the class declaration
122 * So it is considered safe
123 */
124function isClassRefInClassDecorator(variable, reference) {
125 if (variable.defs[0].type !== scope_manager_1.DefinitionType.ClassName) {
126 return false;
127 }
128 if (!variable.defs[0].node.decorators ||
129 variable.defs[0].node.decorators.length === 0) {
130 return false;
131 }
132 for (const deco of variable.defs[0].node.decorators) {
133 if (reference.identifier.range[0] >= deco.range[0] &&
134 reference.identifier.range[1] <= deco.range[1]) {
135 return true;
136 }
137 }
138 return false;
139}
140/**
141 * Checks whether or not a given reference is inside of the initializers of a given variable.
142 *
143 * @returns `true` in the following cases:
144 * - var a = a
145 * - var [a = a] = list
146 * - var {a = a} = obj
147 * - for (var a in a) {}
148 * - for (var a of a) {}
149 */
150function isInInitializer(variable, reference) {
151 var _a;
152 if (variable.scope !== reference.from) {
153 return false;
154 }
155 let node = variable.identifiers[0].parent;
156 const location = reference.identifier.range[1];
157 while (node) {
158 if (node.type === utils_1.AST_NODE_TYPES.VariableDeclarator) {
159 if (isInRange(node.init, location)) {
160 return true;
161 }
162 if (((_a = node.parent) === null || _a === void 0 ? void 0 : _a.parent) &&
163 (node.parent.parent.type === utils_1.AST_NODE_TYPES.ForInStatement ||
164 node.parent.parent.type === utils_1.AST_NODE_TYPES.ForOfStatement) &&
165 isInRange(node.parent.parent.right, location)) {
166 return true;
167 }
168 break;
169 }
170 else if (node.type === utils_1.AST_NODE_TYPES.AssignmentPattern) {
171 if (isInRange(node.right, location)) {
172 return true;
173 }
174 }
175 else if (SENTINEL_TYPE.test(node.type)) {
176 break;
177 }
178 node = node.parent;
179 }
180 return false;
181}
182exports.default = util.createRule({
183 name: 'no-use-before-define',
184 meta: {
185 type: 'problem',
186 docs: {
187 description: 'Disallow the use of variables before they are defined',
188 recommended: false,
189 extendsBaseRule: true,
190 },
191 messages: {
192 noUseBeforeDefine: "'{{name}}' was used before it was defined.",
193 },
194 schema: [
195 {
196 oneOf: [
197 {
198 enum: ['nofunc'],
199 },
200 {
201 type: 'object',
202 properties: {
203 functions: { type: 'boolean' },
204 classes: { type: 'boolean' },
205 enums: { type: 'boolean' },
206 variables: { type: 'boolean' },
207 typedefs: { type: 'boolean' },
208 ignoreTypeReferences: { type: 'boolean' },
209 },
210 additionalProperties: false,
211 },
212 ],
213 },
214 ],
215 },
216 defaultOptions: [
217 {
218 functions: true,
219 classes: true,
220 enums: true,
221 variables: true,
222 typedefs: true,
223 ignoreTypeReferences: true,
224 },
225 ],
226 create(context, optionsWithDefault) {
227 const options = parseOptions(optionsWithDefault[0]);
228 /**
229 * Determines whether a given use-before-define case should be reported according to the options.
230 * @param variable The variable that gets used before being defined
231 * @param reference The reference to the variable
232 */
233 function isForbidden(variable, reference) {
234 if (options.ignoreTypeReferences && isTypeReference(reference)) {
235 return false;
236 }
237 if (isFunction(variable)) {
238 return options.functions;
239 }
240 if (isOuterClass(variable, reference)) {
241 return options.classes;
242 }
243 if (isOuterVariable(variable, reference)) {
244 return options.variables;
245 }
246 if (isOuterEnum(variable, reference)) {
247 return options.enums;
248 }
249 if (isTypedef(variable)) {
250 return options.typedefs;
251 }
252 return true;
253 }
254 /**
255 * Finds and validates all variables in a given scope.
256 */
257 function findVariablesInScope(scope) {
258 scope.references.forEach(reference => {
259 const variable = reference.resolved;
260 // Skips when the reference is:
261 // - initializations.
262 // - referring to an undefined variable.
263 // - referring to a global environment variable (there're no identifiers).
264 // - located preceded by the variable (except in initializers).
265 // - allowed by options.
266 if (reference.init ||
267 !variable ||
268 variable.identifiers.length === 0 ||
269 (variable.identifiers[0].range[1] <= reference.identifier.range[1] &&
270 !isInInitializer(variable, reference)) ||
271 !isForbidden(variable, reference) ||
272 isClassRefInClassDecorator(variable, reference) ||
273 reference.from.type === utils_1.TSESLint.Scope.ScopeType.functionType) {
274 return;
275 }
276 // Reports.
277 context.report({
278 node: reference.identifier,
279 messageId: 'noUseBeforeDefine',
280 data: {
281 name: reference.identifier.name,
282 },
283 });
284 });
285 scope.childScopes.forEach(findVariablesInScope);
286 }
287 return {
288 Program() {
289 findVariablesInScope(context.getScope());
290 },
291 };
292 },
293});
294//# sourceMappingURL=no-use-before-define.js.map
\No newline at end of file