Skip to content
This repository has been archived by the owner on Aug 18, 2021. It is now read-only.

Breaking: Replace parse with parseForESLint and refactor codebase #509

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
517 changes: 78 additions & 439 deletions index.js

Large diffs are not rendered by default.

212 changes: 212 additions & 0 deletions lib/enhanced-referencer.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
"use strict";

var t = require("babel-types");
var eslintScope = require("eslint-scope");

var extendedVisitorKeys = require("./extended-visitor-keys");
var utils = require("./utils");
var visitTypeAnnotation = utils.visitTypeAnnotation;
var checkIdentifierOrVisit = utils.checkIdentifierOrVisit;
var visitDecorators = utils.visitDecorators;
var nestTypeParamScope = utils.nestTypeParamScope;
var createScopeVariable = utils.createScopeVariable;

/**
* Export an EnhancedReferencer class which will be used during the custom
* scope analysis logic in parseForESLint() when visiting AST nodes.
*/
module.exports = class EnhancedReferencer extends eslintScope.Referencer {
// visit decorators that are in: ClassDeclaration / ClassExpression
visitClass(node) {
visitDecorators.call(this, node);
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, 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]);
}
}
// Execute default Referencer logic
super.visitClass(node);
if (typeParamScope) {
this.close(node);
}
}

// visit decorators that are in: Property / MethodDefinition
visitProperty(node) {
if (node.value && node.value.type === "TypeCastExpression") {
visitTypeAnnotation.call(this, node.value);
}
visitDecorators.call(this, node);
// Execute default Referencer logic
super.visitProperty(node);
}

// visit ClassProperty as a Property.
ClassProperty(node) {
if (node.typeAnnotation) {
visitTypeAnnotation.call(this, node.typeAnnotation);
}
this.visitProperty(node);
}

// visit ClassPrivateProperty as a Property.
ClassPrivateProperty(node) {
if (node.typeAnnotation) {
visitTypeAnnotation.call(this, node.typeAnnotation);
}
this.visitProperty(node);
}

Decorator(node) {
if (node.expression) {
this.visit(node.expression);
}
}

// visit flow type in FunctionDeclaration, FunctionExpression, ArrowFunctionExpression
visitFunction(node) {
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, 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
// eslint-scope will traverse into them and include the identifiers within as declarations
extendedVisitorKeys.ObjectPattern = ["properties"];
extendedVisitorKeys.ArrayPattern = ["elements"];
// Execute default Referencer logic
super.visitFunction(node);
// set them back to normal...
extendedVisitorKeys.ObjectPattern = t.VISITOR_KEYS.ObjectPattern;
extendedVisitorKeys.ArrayPattern = t.VISITOR_KEYS.ArrayPattern;
if (typeParamScope) {
this.close(node);
}
}

// visit flow type in VariableDeclaration
VariableDeclaration(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);
}
}
}
// Execute default Referencer logic
super.VariableDeclaration(node);
}

InterfaceDeclaration(node) {
createScopeVariable.call(this, node, node.id);
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
}
// TODO: Handle mixins
for (var i = 0; i < node.extends.length; i++) {
visitTypeAnnotation.call(this, node.extends[i]);
}
visitTypeAnnotation.call(this, node.body);
if (typeParamScope) {
this.close(node);
}
}

TypeAlias(node) {
createScopeVariable.call(this, node, node.id);
var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
}
if (node.right) {
visitTypeAnnotation.call(this, node.right);
}
if (typeParamScope) {
this.close(node);
}
}

DeclareModule(node) {
if (node.id) {
createScopeVariable.call(this, node, node.id);
}

var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
}
if (typeParamScope) {
this.close(node);
}
}

DeclareFunction(node) {
if (node.id) {
createScopeVariable.call(this, node, node.id);
}

var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
}
if (typeParamScope) {
this.close(node);
}
}

DeclareVariable(node) {
if (node.id) {
createScopeVariable.call(this, node, node.id);
}

var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
}
if (typeParamScope) {
this.close(node);
}
}

DeclareClass(node) {
if (node.id) {
createScopeVariable.call(this, node, node.id);
}

var typeParamScope;
if (node.typeParameters) {
typeParamScope = nestTypeParamScope.call(this, this.scopeManager, node);
}
if (typeParamScope) {
this.close(node);
}
}
};
22 changes: 22 additions & 0 deletions lib/extended-visitor-keys.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
"use strict";

var BABEL_VISITOR_KEYS = require("babel-types").VISITOR_KEYS;
var eslintScope = require("eslint-scope");

/**
* Extended visitorKeys, based on the default keys from eslint-scope and the
* VISITOR_KEYS from babel-types
*/
module.exports = Object.assign(
{},
eslintScope.Traverser.DEFAULT_VISITOR_KEYS,
BABEL_VISITOR_KEYS,
{
MethodDefinition: eslintScope.Traverser.DEFAULT_VISITOR_KEYS.MethodDefinition.concat(
["decorators"]
),
Property: eslintScope.Traverser.DEFAULT_VISITOR_KEYS.Property.concat([
"decorators",
]),
}
);
91 changes: 91 additions & 0 deletions lib/parse-with-babylon.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
"use strict";

var parse = require("babylon").parse;
var tt = require("babylon").tokTypes;
var traverse = require("babel-traverse").default;
var codeFrameColumns = require("babel-code-frame").codeFrameColumns;

var babylonToEspree = require("./babylon-to-espree");

/**
* Method to parse the given source code using Babylon, based on the given parser
* options, and return an Espree-formatted (ESTree) AST.
*
* @param {String} code Source code
* @param {Object} options Parser options
* @returns {Object} AST
*/
module.exports = function parseWithBabylon(code, options) {
var opts = {
codeFrame: options.hasOwnProperty("codeFrame") ? options.codeFrame : true,
sourceType: options.sourceType,
allowImportExportEverywhere: options.allowImportExportEverywhere, // consistent with espree
allowReturnOutsideFunction: true,
allowSuperOutsideMethod: true,
ranges: true,
tokens: true,
plugins: [
"flow",
"jsx",
"estree",
"asyncFunctions",
"asyncGenerators",
"classConstructorCall",
"classProperties",
"decorators",
"doExpressions",
"exponentiationOperator",
"exportExtensions",
"functionBind",
"functionSent",
"objectRestSpread",
"trailingFunctionCommas",
"dynamicImport",
"numericSeparator",
"optionalChaining",
"importMeta",
"classPrivateProperties",
"bigInt",
],
};

var ast;
try {
ast = parse(code, opts);
} catch (err) {
if (err instanceof SyntaxError) {
err.lineNumber = err.loc.line;
err.column = err.loc.column;

if (opts.codeFrame) {
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+)\)$/, "") +
// add codeframe
"\n\n" +
codeFrameColumns(
code,
{
start: {
line: err.lineNumber,
column: err.column,
},
},
{ highlightCode: true }
);
}
}

throw err;
}

babylonToEspree(ast, traverse, tt, code);

return ast;
};
Loading