.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / eslint / lib / rules / no-use-before-define.js
1 /**
2  * @fileoverview Rule to flag use of variables before they are defined
3  * @author Ilya Volodin
4  */
5
6 "use strict";
7
8 //------------------------------------------------------------------------------
9 // Helpers
10 //------------------------------------------------------------------------------
11
12 const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/u;
13 const FOR_IN_OF_TYPE = /^For(?:In|Of)Statement$/u;
14
15 /**
16  * Parses a given value as options.
17  * @param {any} options A value to parse.
18  * @returns {Object} The parsed options.
19  */
20 function parseOptions(options) {
21     let functions = true;
22     let classes = true;
23     let variables = 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         variables = options.variables !== false;
31     }
32
33     return { functions, classes, variables };
34 }
35
36 /**
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.
40  */
41 function isFunction(variable) {
42     return variable.defs[0].type === "FunctionName";
43 }
44
45 /**
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.
50  */
51 function isOuterClass(variable, reference) {
52     return (
53         variable.defs[0].type === "ClassName" &&
54         variable.scope.variableScope !== reference.from.variableScope
55     );
56 }
57
58 /**
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.
63  */
64 function isOuterVariable(variable, reference) {
65     return (
66         variable.defs[0].type === "Variable" &&
67         variable.scope.variableScope !== reference.from.variableScope
68     );
69 }
70
71 /**
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.
76  */
77 function isInRange(node, location) {
78     return node && node.range[0] <= location && location <= node.range[1];
79 }
80
81 /**
82  * Checks whether or not a given reference is inside of the initializers of a given variable.
83  *
84  * This returns `true` in the following cases:
85  *
86  *     var a = a
87  *     var [a = a] = list
88  *     var {a = a} = obj
89  *     for (var a in a) {}
90  *     for (var a of a) {}
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  */
95 function isInInitializer(variable, reference) {
96     if (variable.scope !== reference.from) {
97         return false;
98     }
99
100     let node = variable.identifiers[0].parent;
101     const 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             if (FOR_IN_OF_TYPE.test(node.parent.parent.type) &&
109                 isInRange(node.parent.parent.right, location)
110             ) {
111                 return true;
112             }
113             break;
114         } else if (node.type === "AssignmentPattern") {
115             if (isInRange(node.right, location)) {
116                 return true;
117             }
118         } else if (SENTINEL_TYPE.test(node.type)) {
119             break;
120         }
121
122         node = node.parent;
123     }
124
125     return false;
126 }
127
128 //------------------------------------------------------------------------------
129 // Rule Definition
130 //------------------------------------------------------------------------------
131
132 module.exports = {
133     meta: {
134         type: "problem",
135
136         docs: {
137             description: "disallow the use of variables before they are defined",
138             category: "Variables",
139             recommended: false,
140             url: "https://eslint.org/docs/rules/no-use-before-define"
141         },
142
143         schema: [
144             {
145                 oneOf: [
146                     {
147                         enum: ["nofunc"]
148                     },
149                     {
150                         type: "object",
151                         properties: {
152                             functions: { type: "boolean" },
153                             classes: { type: "boolean" },
154                             variables: { type: "boolean" }
155                         },
156                         additionalProperties: false
157                     }
158                 ]
159             }
160         ],
161
162         messages: {
163             usedBeforeDefined: "'{{name}}' was used before it was defined."
164         }
165     },
166
167     create(context) {
168         const options = parseOptions(context.options[0]);
169
170         /**
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
175          */
176         function isForbidden(variable, reference) {
177             if (isFunction(variable)) {
178                 return options.functions;
179             }
180             if (isOuterClass(variable, reference)) {
181                 return options.classes;
182             }
183             if (isOuterVariable(variable, reference)) {
184                 return options.variables;
185             }
186             return true;
187         }
188
189         /**
190          * Finds and validates all variables in a given scope.
191          * @param {Scope} scope The scope object.
192          * @returns {void}
193          * @private
194          */
195         function findVariablesInScope(scope) {
196             scope.references.forEach(reference => {
197                 const variable = reference.resolved;
198
199                 /*
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.
206                  */
207                 if (reference.init ||
208                     !variable ||
209                     variable.identifiers.length === 0 ||
210                     (variable.identifiers[0].range[1] < reference.identifier.range[1] && !isInInitializer(variable, reference)) ||
211                     !isForbidden(variable, reference)
212                 ) {
213                     return;
214                 }
215
216                 // Reports.
217                 context.report({
218                     node: reference.identifier,
219                     messageId: "usedBeforeDefined",
220                     data: reference.identifier
221                 });
222             });
223
224             scope.childScopes.forEach(findVariablesInScope);
225         }
226
227         return {
228             Program() {
229                 findVariablesInScope(context.getScope());
230             }
231         };
232     }
233 };