Skip to content

Commit

Permalink
Use new scopeManager/visitorKeys APIs (babel#542)
Browse files Browse the repository at this point in the history
  • Loading branch information
mysticatea authored and not-an-aardvark committed Dec 24, 2017
1 parent 1f220c2 commit dbc6546
Show file tree
Hide file tree
Showing 16 changed files with 592 additions and 158 deletions.
330 changes: 330 additions & 0 deletions lib/analyze-scope.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,330 @@
"use strict";

const t = require("@babel/types");
const escope = require("eslint-scope");
const Definition = require("eslint-scope/lib/definition").Definition;
const OriginalPatternVisitor = require("eslint-scope/lib/pattern-visitor");
const OriginalReferencer = require("eslint-scope/lib/referencer");
const fallback = require("eslint-visitor-keys").getKeys;
const childVisitorKeys = require("./visitor-keys");

const flowFlippedAliasKeys = t.FLIPPED_ALIAS_KEYS.Flow.concat([
"ArrayPattern",
"ClassDeclaration",
"ClassExpression",
"FunctionDeclaration",
"FunctionExpression",
"Identifier",
"ObjectPattern",
"RestElement",
]);
const visitorKeysMap = Object.keys(t.VISITOR_KEYS).reduce(function(acc, key) {
const value = t.VISITOR_KEYS[key];
if (flowFlippedAliasKeys.indexOf(value) === -1) {
acc[key] = value;
}
return acc;
}, {});

const propertyTypes = {
// loops
callProperties: { type: "loop", values: ["value"] },
indexers: { type: "loop", values: ["key", "value"] },
properties: { type: "loop", values: ["argument", "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" },
};

class PatternVisitor extends OriginalPatternVisitor {
ArrayPattern(node) {
node.elements.forEach(this.visit, this);
}

ObjectPattern(node) {
node.properties.forEach(this.visit, this);
}
}

class Referencer extends OriginalReferencer {
// inherits.
visitPattern(node, options, callback) {
if (!node) {
return;
}

// Visit type annotations.
this._checkIdentifierOrVisit(node.typeAnnotation);
if (t.isAssignmentPattern(node)) {
this._checkIdentifierOrVisit(node.left.typeAnnotation);
}

// Overwrite `super.visitPattern(node, options, callback)` in order to not visit `ArrayPattern#typeAnnotation` and `ObjectPattern#typeAnnotation`.
if (typeof options === "function") {
callback = options;
options = { processRightHandNodes: false };
}

const visitor = new PatternVisitor(this.options, node, callback);
visitor.visit(node);

// Process the right hand nodes recursively.
if (options.processRightHandNodes) {
visitor.rightHandNodes.forEach(this.visit, this);
}
}

// inherits.
visitClass(node) {
// Decorators.
this._visitArray(node.decorators);

// Flow type parameters.
const typeParamScope = this._nestTypeParamScope(node);

// Flow super types.
this._visitTypeAnnotation(node.implements);
this._visitTypeAnnotation(
node.superTypeParameters && node.superTypeParameters.params
);

// Basic.
super.visitClass(node);

// Close the type parameter scope.
if (typeParamScope) {
this.close(node);
}
}

// inherits.
visitFunction(node) {
const typeParamScope = this._nestTypeParamScope(node);

// Flow return types.
this._checkIdentifierOrVisit(node.returnType);

// Basic.
super.visitFunction(node);

// Close the type parameter scope.
if (typeParamScope) {
this.close(node);
}
}

// inherits.
visitProperty(node) {
if (node.value && node.value.type === "TypeCastExpression") {
this._visitTypeAnnotation(node.value);
}
this._visitArray(node.decorators);
super.visitProperty(node);
}

InterfaceDeclaration(node) {
this._createScopeVariable(node, node.id);

const typeParamScope = this._nestTypeParamScope(node);

// TODO: Handle mixins
this._visitArray(node.extends);
this.visit(node.body);

if (typeParamScope) {
this.close(node);
}
}

TypeAlias(node) {
this._createScopeVariable(node, node.id);

const typeParamScope = this._nestTypeParamScope(node);

this.visit(node.right);

if (typeParamScope) {
this.close(node);
}
}

ClassProperty(node) {
this._visitClassProperty(node);
}

ClassPrivateProperty(node) {
this._visitClassProperty(node);
}

DeclareModule(node) {
this._visitDeclareX(node);
}

DeclareFunction(node) {
this._visitDeclareX(node);
}

DeclareVariable(node) {
this._visitDeclareX(node);
}

DeclareClass(node) {
this._visitDeclareX(node);
}

_visitClassProperty(node) {
this._visitTypeAnnotation(node.typeAnnotation);
this.visitProperty(node);
}

_visitDeclareX(node) {
if (node.id) {
this._createScopeVariable(node, node.id);
}

const typeParamScope = this._nestTypeParamScope(node);
if (typeParamScope) {
this.close(node);
}
}

_createScopeVariable(node, name) {
this.currentScope().variableScope.__define(
name,
new Definition("Variable", name, node, null, null, null)
);
}

_nestTypeParamScope(node) {
if (!node.typeParameters) {
return null;
}

const parentScope = this.scopeManager.__currentScope;
const scope = new escope.Scope(
this.scopeManager,
"type-parameters",
parentScope,
node,
false
);

this.scopeManager.__nestScope(scope);
for (let j = 0; j < node.typeParameters.params.length; j++) {
const name = node.typeParameters.params[j];
scope.__define(name, new Definition("TypeParameter", name, name));
if (name.typeAnnotation) {
this._checkIdentifierOrVisit(name);
}
}
scope.__define = function() {
return parentScope.__define.apply(parentScope, arguments);
};

return scope;
}

_visitTypeAnnotation(node) {
if (!node) {
return;
}
if (Array.isArray(node)) {
node.forEach(this._visitTypeAnnotation, this);
return;
}

// get property to check (params, id, etc...)
const visitorValues = visitorKeysMap[node.type];
if (!visitorValues) {
return;
}

// can have multiple properties
for (let i = 0; i < visitorValues.length; i++) {
const visitorValue = visitorValues[i];
const propertyType = propertyTypes[visitorValue];
const nodeProperty = node[visitorValue];
// check if property or type is defined
if (propertyType == null || nodeProperty == null) {
continue;
}
if (propertyType.type === "loop") {
for (let j = 0; j < nodeProperty.length; j++) {
if (Array.isArray(propertyType.values)) {
for (let k = 0; k < propertyType.values.length; k++) {
const loopPropertyNode = nodeProperty[j][propertyType.values[k]];
if (loopPropertyNode) {
this._checkIdentifierOrVisit(loopPropertyNode);
}
}
} else {
this._checkIdentifierOrVisit(nodeProperty[j]);
}
}
} else if (propertyType.type === "single") {
this._checkIdentifierOrVisit(nodeProperty);
} else if (propertyType.type === "typeAnnotation") {
this._visitTypeAnnotation(node.typeAnnotation);
} else if (propertyType.type === "typeParameters") {
for (let l = 0; l < node.typeParameters.params.length; l++) {
this._checkIdentifierOrVisit(node.typeParameters.params[l]);
}
} else if (propertyType.type === "id") {
if (node.id.type === "Identifier") {
this._checkIdentifierOrVisit(node.id);
} else {
this._visitTypeAnnotation(node.id);
}
}
}
}

_checkIdentifierOrVisit(node) {
if (node && node.typeAnnotation) {
this._visitTypeAnnotation(node.typeAnnotation);
} else if (node && node.type === "Identifier") {
this.visit(node);
} else {
this._visitTypeAnnotation(node);
}
}

_visitArray(nodeList) {
if (nodeList) {
for (const node of nodeList) {
this.visit(node);
}
}
}
}

module.exports = function(ast, parserOptions) {
const options = {
optimistic: false,
directive: false,
nodejsScope:
ast.sourceType === "script" &&
(parserOptions.ecmaFeatures &&
parserOptions.ecmaFeatures.globalReturn) === true,
impliedStrict: false,
sourceType: ast.sourceType,
ecmaVersion: parserOptions.ecmaVersion || 6,
childVisitorKeys,
fallback,
};
const scopeManager = new escope.ScopeManager(options);
const referencer = new Referencer(options, scopeManager);

referencer.visit(ast);

return scopeManager;
};
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
17 changes: 17 additions & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"use strict";

let patched = false;

exports.parse = function(code, options) {
patched = true;
return require("./parse-with-patch")(code, options);
};

exports.parseForESLint = function(code, options) {
if (!patched && options.eslintVisitorKeys && options.eslintScopeManager) {
return require("./parse-with-scope")(code, options);
}

patched = true;
return { ast: require("./parse-with-patch")(code, options) };
};
Loading

0 comments on commit dbc6546

Please sign in to comment.