Daniel Tschinder cf456bfe4a Fix escope to take sourceType and ecmaVersion from options (babel/babel-eslint#288)
escope was hardcoded to sourcetype: "module" and ecmaVersion: "6"
This changes it to take the configuration from the eslint options and still
defaulting to "module" and "6".
This is done by having to global variables, as monkeypatch is only triggered once.
To fix scoping issues, the same logic as in eslint is applied. It disables the nodejs scope
if the sourceType is module.
2016-04-20 02:56:15 +02:00

448 lines
14 KiB
JavaScript

var babylonToEspree = require("./babylon-to-espree");
var assign = require("lodash.assign");
var pick = require("lodash.pick");
var Module = require("module");
var path = require("path");
var parse = require("babylon").parse;
var t = require("babel-types");
var tt = require("babylon").tokTypes;
var traverse = require("babel-traverse").default;
var estraverse;
var hasPatched = false;
var eslintOptions = {};
function createModule(filename) {
var mod = new Module(filename);
mod.filename = filename;
mod.paths = Module._nodeModulePaths(path.dirname(filename));
return mod;
}
function monkeypatch() {
if (hasPatched) return;
hasPatched = true;
var eslintLoc;
try {
// avoid importing a local copy of eslint, try to find a peer dependency
eslintLoc = Module._resolveFilename("eslint", module.parent);
} catch (err) {
try {
// avoids breaking in jest where module.parent is undefined
eslintLoc = require.resolve("eslint");
} catch (err) {
throw new ReferenceError("couldn't resolve eslint");
}
}
// get modules relative to what eslint will load
var eslintMod = createModule(eslintLoc);
var escopeLoc = Module._resolveFilename("escope", eslintMod);
var escopeMod = createModule(escopeLoc);
// npm 3: monkeypatch estraverse if it's in escope
var estraverseRelative = escopeMod;
try {
var esrecurseLoc = Module._resolveFilename("esrecurse", eslintMod);
estraverseRelative = createModule(esrecurseLoc);
} catch (err) {}
// contains all the instances of estraverse so we can modify them if necessary
var estraverses = [];
// monkeypatch estraverse
estraverse = estraverseRelative.require("estraverse");
estraverses.push(estraverse);
assign(estraverse.VisitorKeys, t.VISITOR_KEYS);
// monkeypatch estraverse-fb (only for eslint < 2.3.0)
try {
var estraverseFb = eslintMod.require("estraverse-fb");
estraverses.push(estraverseFb);
assign(estraverseFb.VisitorKeys, t.VISITOR_KEYS);
} catch (err) {
// Ignore: ESLint v2.3.0 does not have estraverse-fb
}
// ESLint v1.9.0 uses estraverse directly to work around https://github.com/npm/npm/issues/9663
var estraverseOfEslint = eslintMod.require("estraverse");
if (estraverseOfEslint !== estraverseFb) {
estraverses.push(estraverseOfEslint);
assign(estraverseOfEslint.VisitorKeys, t.VISITOR_KEYS);
}
// monkeypatch escope
var escope = require(escopeLoc);
var analyze = escope.analyze;
escope.analyze = function (ast, opts) {
opts.ecmaVersion = eslintOptions.ecmaVersion;
opts.sourceType = eslintOptions.sourceType;
if (eslintOptions.globalReturn !== undefined) {
opts.nodejsScope = eslintOptions.globalReturn;
}
var results = analyze.call(this, ast, opts);
return results;
};
// monkeypatch escope/referencer
var referencerLoc;
try {
referencerLoc = Module._resolveFilename("./referencer", escopeMod);
} catch (err) {
throw new ReferenceError("couldn't resolve escope/referencer");
}
var referencerMod = createModule(referencerLoc);
var referencer = require(referencerLoc);
if (referencer.__esModule) {
referencer = referencer.default;
}
// reference Definition
var definitionLoc;
try {
definitionLoc = Module._resolveFilename("./definition", referencerMod);
} catch (err) {
throw new ReferenceError("couldn't resolve escope/definition");
}
var Definition = require(definitionLoc).Definition;
// if there are decorators, then visit each
function visitDecorators(node) {
if (!node.decorators) {
return;
}
for (var i = 0; i < node.decorators.length; i++) {
if (node.decorators[i].expression) {
this.visit(node.decorators[i]);
}
}
}
// iterate through part of t.VISITOR_KEYS
var visitorKeysMap = pick(t.VISITOR_KEYS, function(k) {
return t.FLIPPED_ALIAS_KEYS.Flow.concat([
"ArrayPattern",
"ClassDeclaration",
"ClassExpression",
"FunctionDeclaration",
"FunctionExpression",
"Identifier",
"ObjectPattern",
"RestElement"
]).indexOf(k) === -1;
});
var propertyTypes = {
// loops
callProperties: { type: "loop", values: ["value"] },
indexers: { type: "loop", values: ["key", "value"] },
properties: { type: "loop", values: ["value"] },
types: { type: "loop" },
params: { type: "loop" },
// single property
argument: { type: "single" },
elementType: { type: "single" },
qualification: { type: "single" },
rest: { type: "single" },
returnType: { type: "single" },
// others
typeAnnotation: { type: "typeAnnotation" },
typeParameters: { type: "typeParameters" },
id: { type: "id" }
};
function visitTypeAnnotation(node) {
// get property to check (params, id, etc...)
var visitorValues = visitorKeysMap[node.type];
if (!visitorValues) {
return;
}
// can have multiple properties
for (var i = 0; i < visitorValues.length; i++) {
var visitorValue = visitorValues[i];
var propertyType = propertyTypes[visitorValue];
var nodeProperty = node[visitorValue];
// check if property or type is defined
if (propertyType == null || nodeProperty == null) {
continue;
}
if (propertyType.type === "loop") {
for (var j = 0; j < nodeProperty.length; j++) {
if (Array.isArray(propertyType.values)) {
for (var k = 0; k < propertyType.values.length; k++) {
checkIdentifierOrVisit.call(this, nodeProperty[j][propertyType.values[k]]);
}
} else {
checkIdentifierOrVisit.call(this, nodeProperty[j]);
}
}
} else if (propertyType.type === "single") {
checkIdentifierOrVisit.call(this, nodeProperty);
} else if (propertyType.type === "typeAnnotation") {
visitTypeAnnotation.call(this, node.typeAnnotation);
} else if (propertyType.type === "typeParameters") {
for (var l = 0; l < node.typeParameters.params.length; l++) {
checkIdentifierOrVisit.call(this, node.typeParameters.params[l]);
}
} else if (propertyType.type === "id") {
if (node.id.type === "Identifier") {
checkIdentifierOrVisit.call(this, node.id);
} else {
visitTypeAnnotation.call(this, node.id);
}
}
}
}
function checkIdentifierOrVisit(node) {
if (node.typeAnnotation) {
visitTypeAnnotation.call(this, node.typeAnnotation);
} else if (node.type === "Identifier") {
this.visit(node);
} else {
visitTypeAnnotation.call(this, node);
}
}
function nestTypeParamScope(manager, node) {
var parentScope = manager.__currentScope;
var scope = new escope.Scope(manager, "type-parameters", parentScope, node, false);
manager.__nestScope(scope);
for (var j = 0; j < node.typeParameters.params.length; j++) {
var name = node.typeParameters.params[j];
scope.__define(name, new Definition("TypeParameter", name, name));
}
scope.__define = function() {
return parentScope.__define.apply(parentScope, arguments);
};
return scope;
}
// visit decorators that are in: ClassDeclaration / ClassExpression
var visitClass = referencer.prototype.visitClass;
referencer.prototype.visitClass = function(node) {
visitDecorators.call(this, node);
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope(this.scopeManager, node);
}
// visit flow type: ClassImplements
if (node.implements) {
for (var i = 0; i < node.implements.length; i++) {
checkIdentifierOrVisit.call(this, node.implements[i]);
}
}
if (node.superTypeParameters) {
for (var k = 0; k < node.superTypeParameters.params.length; k++) {
checkIdentifierOrVisit.call(this, node.superTypeParameters.params[k]);
}
}
visitClass.call(this, node);
if (typeParamScope) {
this.close(node);
}
};
// visit decorators that are in: Property / MethodDefinition
var visitProperty = referencer.prototype.visitProperty;
referencer.prototype.visitProperty = function(node) {
if (node.value && node.value.type === "TypeCastExpression") {
visitTypeAnnotation.call(this, node.value);
}
visitDecorators.call(this, node);
visitProperty.call(this, node);
};
// visit flow type in FunctionDeclaration, FunctionExpression, ArrowFunctionExpression
var visitFunction = referencer.prototype.visitFunction;
referencer.prototype.visitFunction = function(node) {
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope(this.scopeManager, node);
}
if (node.returnType) {
checkIdentifierOrVisit.call(this, node.returnType);
}
// only visit if function parameters have types
if (node.params) {
for (var i = 0; i < node.params.length; i++) {
var param = node.params[i];
if (param.typeAnnotation) {
checkIdentifierOrVisit.call(this, param);
} else if (t.isAssignmentPattern(param)) {
if (param.left.typeAnnotation) {
checkIdentifierOrVisit.call(this, param.left);
}
}
}
}
// set ArrayPattern/ObjectPattern visitor keys back to their original. otherwise
// escope will traverse into them and include the identifiers within as declarations
estraverses.forEach(function (estraverse) {
estraverse.VisitorKeys.ObjectPattern = ["properties"];
estraverse.VisitorKeys.ArrayPattern = ["elements"];
});
visitFunction.call(this, node);
// set them back to normal...
estraverses.forEach(function (estraverse) {
estraverse.VisitorKeys.ObjectPattern = t.VISITOR_KEYS.ObjectPattern;
estraverse.VisitorKeys.ArrayPattern = t.VISITOR_KEYS.ArrayPattern;
});
if (typeParamScope) {
this.close(node);
}
};
// visit flow type in VariableDeclaration
var variableDeclaration = referencer.prototype.VariableDeclaration;
referencer.prototype.VariableDeclaration = function(node) {
if (node.declarations) {
for (var i = 0; i < node.declarations.length; i++) {
var id = node.declarations[i].id;
var typeAnnotation = id.typeAnnotation;
if (typeAnnotation) {
checkIdentifierOrVisit.call(this, typeAnnotation);
}
}
}
variableDeclaration.call(this, node);
};
function createScopeVariable (node, name) {
this.currentScope().variableScope.__define(name,
new Definition(
"Variable",
name,
node,
null,
null,
null
)
);
}
referencer.prototype.TypeAlias = function(node) {
createScopeVariable.call(this, node, node.id);
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope(this.scopeManager, node);
}
if (node.right) {
visitTypeAnnotation.call(this, node.right);
}
if (typeParamScope) {
this.close(node);
}
};
referencer.prototype.DeclareModule =
referencer.prototype.DeclareFunction =
referencer.prototype.DeclareVariable =
referencer.prototype.DeclareClass = function(node) {
if (node.id) {
createScopeVariable.call(this, node, node.id);
}
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope(this.scopeManager, node);
}
if (typeParamScope) {
this.close(node);
}
};
}
exports.parse = function (code, options) {
options = options || {};
eslintOptions.ecmaVersion = options.ecmaVersion = options.ecmaVersion || 6;
eslintOptions.sourceType = options.sourceType = options.sourceType || "module";
if (options.sourceType === "module") {
eslintOptions.globalReturn = false;
} else {
delete eslintOptions.globalReturn;
}
try {
monkeypatch();
} catch (err) {
console.error(err.stack);
process.exit(1);
}
return exports.parseNoPatch(code, options);
}
exports.parseNoPatch = function (code, options) {
var opts = {
sourceType: options.sourceType,
strictMode: true,
allowImportExportEverywhere: false, // consistent with espree
allowReturnOutsideFunction: true,
allowSuperOutsideMethod: true,
plugins: [
"flow",
"jsx",
"asyncFunctions",
"asyncGenerators",
"classConstructorCall",
"classProperties",
"decorators",
"doExpressions",
"exponentiationOperator",
"exportExtensions",
"functionBind",
"functionSent",
"objectRestSpread",
"trailingFunctionCommas"
]
};
var ast;
try {
ast = parse(code, opts);
} catch (err) {
if (err instanceof SyntaxError) {
err.lineNumber = err.loc.line;
err.column = err.loc.column + 1;
// remove trailing "(LINE:COLUMN)" acorn message and add in esprima syntax error message start
err.message = "Line " + err.lineNumber + ": " + err.message.replace(/ \((\d+):(\d+)\)$/, "");
}
throw err;
}
// remove EOF token, eslint doesn't use this for anything and it interferes with some rules
// see https://github.com/babel/babel-eslint/issues/2 for more info
// todo: find a more elegant way to do this
ast.tokens.pop();
// convert tokens
ast.tokens = babylonToEspree.toTokens(ast.tokens, tt, code);
// add comments
babylonToEspree.convertComments(ast.comments);
// transform esprima and acorn divergent nodes
babylonToEspree.toAST(ast, traverse, code);
// ast.program.tokens = ast.tokens;
// ast.program.comments = ast.comments;
// ast = ast.program;
// remove File
ast.type = "Program";
ast.sourceType = ast.program.sourceType;
ast.directives = ast.program.directives;
ast.body = ast.program.body;
delete ast.program;
delete ast._paths;
babylonToEspree.attachComments(ast, ast.comments, ast.tokens);
return ast;
}