UNPKG

13.6 kBJavaScriptView Raw
1var babylonToEspree = require("./babylon-to-espree");
2var Module = require("module");
3var path = require("path");
4var parse = require("babylon").parse;
5var t = require("babel-types");
6var tt = require("babylon").tokTypes;
7var traverse = require("babel-traverse").default;
8var codeFrame = require("babel-code-frame");
9
10var hasPatched = false;
11var eslintOptions = {};
12
13function getModules() {
14 try {
15 // avoid importing a local copy of eslint, try to find a peer dependency
16 var eslintLoc = Module._resolveFilename("eslint", module.parent);
17 } catch (err) {
18 try {
19 // avoids breaking in jest where module.parent is undefined
20 eslintLoc = require.resolve("eslint");
21 } catch (err) {
22 throw new ReferenceError("couldn't resolve eslint");
23 }
24 }
25
26 // get modules relative to what eslint will load
27 var eslintMod = new Module(eslintLoc);
28 eslintMod.filename = eslintLoc;
29 eslintMod.paths = Module._nodeModulePaths(path.dirname(eslintLoc));
30
31 try {
32 var escope = eslintMod.require("eslint-scope");
33 var Definition = eslintMod.require("eslint-scope/lib/definition").Definition;
34 var referencer = eslintMod.require("eslint-scope/lib/referencer");
35 } catch (err) {
36 escope = eslintMod.require("escope");
37 Definition = eslintMod.require("escope/lib/definition").Definition;
38 referencer = eslintMod.require("escope/lib/referencer");
39 }
40
41 var estraverse = eslintMod.require("estraverse");
42
43 if (referencer.__esModule) referencer = referencer.default;
44
45 return {
46 Definition,
47 escope,
48 estraverse,
49 referencer,
50 };
51}
52
53function monkeypatch(modules) {
54 var Definition = modules.Definition;
55 var escope = modules.escope;
56 var estraverse = modules.estraverse;
57 var referencer = modules.referencer;
58
59 Object.assign(estraverse.VisitorKeys, t.VISITOR_KEYS);
60 estraverse.VisitorKeys.MethodDefinition.push("decorators");
61 estraverse.VisitorKeys.Property.push("decorators");
62
63 var analyze = escope.analyze;
64 escope.analyze = function (ast, opts) {
65 opts.ecmaVersion = eslintOptions.ecmaVersion;
66 opts.sourceType = eslintOptions.sourceType;
67 if (eslintOptions.globalReturn !== undefined) {
68 opts.nodejsScope = eslintOptions.globalReturn;
69 }
70
71 var results = analyze.call(this, ast, opts);
72 return results;
73 };
74
75 // if there are decorators, then visit each
76 function visitDecorators(node) {
77 if (!node.decorators) {
78 return;
79 }
80 for (var i = 0; i < node.decorators.length; i++) {
81 if (node.decorators[i].expression) {
82 this.visit(node.decorators[i]);
83 }
84 }
85 }
86
87 // iterate through part of t.VISITOR_KEYS
88 var flowFlippedAliasKeys = t.FLIPPED_ALIAS_KEYS.Flow.concat([
89 "ArrayPattern",
90 "ClassDeclaration",
91 "ClassExpression",
92 "FunctionDeclaration",
93 "FunctionExpression",
94 "Identifier",
95 "ObjectPattern",
96 "RestElement"
97 ]);
98 var visitorKeysMap = Object.keys(t.VISITOR_KEYS).reduce(function(acc, key) {
99 var value = t.VISITOR_KEYS[key];
100 if (flowFlippedAliasKeys.indexOf(value) === -1) {
101 acc[key] = value;
102 }
103 return acc;
104 }, {});
105
106 var propertyTypes = {
107 // loops
108 callProperties: { type: "loop", values: ["value"] },
109 indexers: { type: "loop", values: ["key", "value"] },
110 properties: { type: "loop", values: ["argument", "value"] },
111 types: { type: "loop" },
112 params: { type: "loop" },
113 // single property
114 argument: { type: "single" },
115 elementType: { type: "single" },
116 qualification: { type: "single" },
117 rest: { type: "single" },
118 returnType: { type: "single" },
119 // others
120 typeAnnotation: { type: "typeAnnotation" },
121 typeParameters: { type: "typeParameters" },
122 id: { type: "id" }
123 };
124
125 function visitTypeAnnotation(node) {
126 // get property to check (params, id, etc...)
127 var visitorValues = visitorKeysMap[node.type];
128 if (!visitorValues) {
129 return;
130 }
131
132 // can have multiple properties
133 for (var i = 0; i < visitorValues.length; i++) {
134 var visitorValue = visitorValues[i];
135 var propertyType = propertyTypes[visitorValue];
136 var nodeProperty = node[visitorValue];
137 // check if property or type is defined
138 if (propertyType == null || nodeProperty == null) {
139 continue;
140 }
141 if (propertyType.type === "loop") {
142 for (var j = 0; j < nodeProperty.length; j++) {
143 if (Array.isArray(propertyType.values)) {
144 for (var k = 0; k < propertyType.values.length; k++) {
145 var loopPropertyNode = nodeProperty[j][propertyType.values[k]];
146 if (loopPropertyNode) {
147 checkIdentifierOrVisit.call(this, loopPropertyNode);
148 }
149 }
150 } else {
151 checkIdentifierOrVisit.call(this, nodeProperty[j]);
152 }
153 }
154 } else if (propertyType.type === "single") {
155 checkIdentifierOrVisit.call(this, nodeProperty);
156 } else if (propertyType.type === "typeAnnotation") {
157 visitTypeAnnotation.call(this, node.typeAnnotation);
158 } else if (propertyType.type === "typeParameters") {
159 for (var l = 0; l < node.typeParameters.params.length; l++) {
160 checkIdentifierOrVisit.call(this, node.typeParameters.params[l]);
161 }
162 } else if (propertyType.type === "id") {
163 if (node.id.type === "Identifier") {
164 checkIdentifierOrVisit.call(this, node.id);
165 } else {
166 visitTypeAnnotation.call(this, node.id);
167 }
168 }
169 }
170 }
171
172 function checkIdentifierOrVisit(node) {
173 if (node.typeAnnotation) {
174 visitTypeAnnotation.call(this, node.typeAnnotation);
175 } else if (node.type === "Identifier") {
176 this.visit(node);
177 } else {
178 visitTypeAnnotation.call(this, node);
179 }
180 }
181
182 function nestTypeParamScope(manager, node) {
183 var parentScope = manager.__currentScope;
184 var scope = new escope.Scope(manager, "type-parameters", parentScope, node, false);
185 manager.__nestScope(scope);
186 for (var j = 0; j < node.typeParameters.params.length; j++) {
187 var name = node.typeParameters.params[j];
188 scope.__define(name, new Definition("TypeParameter", name, name));
189 if (name.typeAnnotation) {
190 checkIdentifierOrVisit.call(this, name);
191 }
192 }
193 scope.__define = function() {
194 return parentScope.__define.apply(parentScope, arguments);
195 };
196 return scope;
197 }
198
199 // visit decorators that are in: ClassDeclaration / ClassExpression
200 var visitClass = referencer.prototype.visitClass;
201 referencer.prototype.visitClass = function(node) {
202 visitDecorators.call(this, node);
203 var typeParamScope;
204 if (node.typeParameters) {
205 typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
206 }
207 // visit flow type: ClassImplements
208 if (node.implements) {
209 for (var i = 0; i < node.implements.length; i++) {
210 checkIdentifierOrVisit.call(this, node.implements[i]);
211 }
212 }
213 if (node.superTypeParameters) {
214 for (var k = 0; k < node.superTypeParameters.params.length; k++) {
215 checkIdentifierOrVisit.call(this, node.superTypeParameters.params[k]);
216 }
217 }
218 visitClass.call(this, node);
219 if (typeParamScope) {
220 this.close(node);
221 }
222 };
223
224 // visit decorators that are in: Property / MethodDefinition
225 var visitProperty = referencer.prototype.visitProperty;
226 referencer.prototype.visitProperty = function(node) {
227 if (node.value && node.value.type === "TypeCastExpression") {
228 visitTypeAnnotation.call(this, node.value);
229 }
230 visitDecorators.call(this, node);
231 visitProperty.call(this, node);
232 };
233
234 // visit ClassProperty as a Property.
235 referencer.prototype.ClassProperty = function(node) {
236 if (node.typeAnnotation) {
237 visitTypeAnnotation.call(this, node.typeAnnotation);
238 }
239 this.visitProperty(node);
240 };
241
242 // visit flow type in FunctionDeclaration, FunctionExpression, ArrowFunctionExpression
243 var visitFunction = referencer.prototype.visitFunction;
244 referencer.prototype.visitFunction = function(node) {
245 var typeParamScope;
246 if (node.typeParameters) {
247 typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
248 }
249 if (node.returnType) {
250 checkIdentifierOrVisit.call(this, node.returnType);
251 }
252 // only visit if function parameters have types
253 if (node.params) {
254 for (var i = 0; i < node.params.length; i++) {
255 var param = node.params[i];
256 if (param.typeAnnotation) {
257 checkIdentifierOrVisit.call(this, param);
258 } else if (t.isAssignmentPattern(param)) {
259 if (param.left.typeAnnotation) {
260 checkIdentifierOrVisit.call(this, param.left);
261 }
262 }
263 }
264 }
265 // set ArrayPattern/ObjectPattern visitor keys back to their original. otherwise
266 // escope will traverse into them and include the identifiers within as declarations
267 estraverse.VisitorKeys.ObjectPattern = ["properties"];
268 estraverse.VisitorKeys.ArrayPattern = ["elements"];
269 visitFunction.call(this, node);
270 // set them back to normal...
271 estraverse.VisitorKeys.ObjectPattern = t.VISITOR_KEYS.ObjectPattern;
272 estraverse.VisitorKeys.ArrayPattern = t.VISITOR_KEYS.ArrayPattern;
273 if (typeParamScope) {
274 this.close(node);
275 }
276 };
277
278 // visit flow type in VariableDeclaration
279 var variableDeclaration = referencer.prototype.VariableDeclaration;
280 referencer.prototype.VariableDeclaration = function(node) {
281 if (node.declarations) {
282 for (var i = 0; i < node.declarations.length; i++) {
283 var id = node.declarations[i].id;
284 var typeAnnotation = id.typeAnnotation;
285 if (typeAnnotation) {
286 checkIdentifierOrVisit.call(this, typeAnnotation);
287 }
288 }
289 }
290 variableDeclaration.call(this, node);
291 };
292
293 function createScopeVariable (node, name) {
294 this.currentScope().variableScope.__define(name,
295 new Definition(
296 "Variable",
297 name,
298 node,
299 null,
300 null,
301 null
302 )
303 );
304 }
305
306 referencer.prototype.InterfaceDeclaration = function(node) {
307 createScopeVariable.call(this, node, node.id);
308 var typeParamScope;
309 if (node.typeParameters) {
310 typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
311 }
312 // TODO: Handle mixins
313 for (var i = 0; i < node.extends.length; i++) {
314 visitTypeAnnotation.call(this, node.extends[i]);
315 }
316 visitTypeAnnotation.call(this, node.body);
317 if (typeParamScope) {
318 this.close(node);
319 }
320 };
321
322 referencer.prototype.TypeAlias = function(node) {
323 createScopeVariable.call(this, node, node.id);
324 var typeParamScope;
325 if (node.typeParameters) {
326 typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
327 }
328 if (node.right) {
329 visitTypeAnnotation.call(this, node.right);
330 }
331 if (typeParamScope) {
332 this.close(node);
333 }
334 };
335
336 referencer.prototype.DeclareModule =
337 referencer.prototype.DeclareFunction =
338 referencer.prototype.DeclareVariable =
339 referencer.prototype.DeclareClass = function(node) {
340 if (node.id) {
341 createScopeVariable.call(this, node, node.id);
342 }
343
344 var typeParamScope;
345 if (node.typeParameters) {
346 typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
347 }
348 if (typeParamScope) {
349 this.close(node);
350 }
351 };
352}
353
354exports.parse = function (code, options) {
355 options = options || {};
356 eslintOptions.ecmaVersion = options.ecmaVersion = options.ecmaVersion || 6;
357 eslintOptions.sourceType = options.sourceType = options.sourceType || "module";
358 eslintOptions.allowImportExportEverywhere = options.allowImportExportEverywhere = options.allowImportExportEverywhere || false;
359 if (options.sourceType === "module") {
360 eslintOptions.globalReturn = false;
361 } else {
362 delete eslintOptions.globalReturn;
363 }
364
365 if (!hasPatched) {
366 hasPatched = true;
367 try {
368 monkeypatch(getModules());
369 } catch (err) {
370 console.error(err.stack);
371 process.exit(1);
372 }
373 }
374
375 return exports.parseNoPatch(code, options);
376};
377
378exports.parseNoPatch = function (code, options) {
379 var opts = {
380 codeFrame: options.hasOwnProperty("codeFrame") ? options.codeFrame : true,
381 sourceType: options.sourceType,
382 allowImportExportEverywhere: options.allowImportExportEverywhere, // consistent with espree
383 allowReturnOutsideFunction: true,
384 allowSuperOutsideMethod: true,
385 plugins: [
386 "flow",
387 "jsx",
388 "asyncFunctions",
389 "asyncGenerators",
390 "classConstructorCall",
391 "classProperties",
392 "decorators",
393 "doExpressions",
394 "exponentiationOperator",
395 "exportExtensions",
396 "functionBind",
397 "functionSent",
398 "objectRestSpread",
399 "trailingFunctionCommas",
400 "dynamicImport"
401 ]
402 };
403
404 var ast;
405 try {
406 ast = parse(code, opts);
407 } catch (err) {
408 if (err instanceof SyntaxError) {
409
410 err.lineNumber = err.loc.line;
411 err.column = err.loc.column;
412
413 if (opts.codeFrame) {
414 err.lineNumber = err.loc.line;
415 err.column = err.loc.column + 1;
416
417 // remove trailing "(LINE:COLUMN)" acorn message and add in esprima syntax error message start
418 err.message = "Line " + err.lineNumber + ": " + err.message.replace(/ \((\d+):(\d+)\)$/, "") +
419 // add codeframe
420 "\n\n" +
421 codeFrame(code, err.lineNumber, err.column, { highlightCode: true });
422 }
423 }
424
425 throw err;
426 }
427
428 babylonToEspree(ast, traverse, tt, code);
429
430 return ast;
431};