From a539ce29446a8d546e4a1edbcc16a21c44c35b19 Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Sat, 27 Feb 2016 20:23:32 -0500 Subject: [PATCH 1/7] ESLint 2 --- package.json | 6 ++--- test/babel-eslint.js | 46 +++++++++++--------------------- test/non-regression.js | 60 +++++++++++++++--------------------------- 3 files changed, 40 insertions(+), 72 deletions(-) diff --git a/package.json b/package.json index 5433fbfe..4dc700ba 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "babel-eslint", "version": "5.0.0", - "description": "", + "description": "Custom parser for ESLint", "main": "index.js", "files": [ "index.js" @@ -32,8 +32,8 @@ }, "homepage": "https://github.com/babel/babel-eslint", "devDependencies": { - "eslint": "^1.10.2", - "espree": "^2.2.5", + "eslint": "^2.0.0", + "espree": "^3.0.0", "mocha": "^2.3.3" } } diff --git a/test/babel-eslint.js b/test/babel-eslint.js index dea774eb..34efda6d 100644 --- a/test/babel-eslint.js +++ b/test/babel-eslint.js @@ -35,46 +35,32 @@ function assertImplementsAST(target, source, path) { function parseAndAssertSame(code) { var esAST = espree.parse(code, { ecmaFeatures: { - arrowFunctions: true, - binaryLiterals: true, - blockBindings: true, - classes: true, - defaultParams: true, - destructuring: true, - forOf: true, - generators: true, - modules: true, - objectLiteralComputedProperties: true, - objectLiteralDuplicateProperties: true, - objectLiteralShorthandMethods: true, - objectLiteralShorthandProperties: true, - octalLiterals: true, - regexUFlag: true, - regexYFlag: true, - restParams: true, - spread: true, - superInFunctions: true, - templateStrings: true, - unicodeCodePointEscapes: true, - globalReturn: true, - jsx: true, - experimentalObjectRestSpread: true, + // enable JSX parsing + jsx: true, + // enable return in global scope + globalReturn: true, + // enable implied strict mode (if ecmaVersion >= 5) + impliedStrict: true, + // allow experimental object rest/spread + experimentalObjectRestSpread: true }, tokens: true, loc: true, range: true, comment: true, - attachComment: true + attachComment: true, + ecmaVersion: 6, + sourceType: "module" }); var babylonAST = babelEslint.parse(code); try { assertImplementsAST(esAST, babylonAST); } catch(err) { - err.message += - "\nespree:\n" + - util.inspect(esAST, {depth: err.depth, colors: true}) + - "\nbabel-eslint:\n" + - util.inspect(babylonAST, {depth: err.depth, colors: true}); + // err.message += + // "\nespree:\n" + + // util.inspect(esAST, {depth: err.depth, colors: true}) + + // "\nbabel-eslint:\n" + + // util.inspect(babylonAST, {depth: err.depth, colors: true}); throw err; } // assert.equal(esAST, babylonAST); diff --git a/test/non-regression.js b/test/non-regression.js index 4e2d1128..0e979df4 100644 --- a/test/non-regression.js +++ b/test/non-regression.js @@ -2,43 +2,25 @@ "use strict"; var eslint = require("eslint"); -function verifyAndAssertMessages(code, rules, expectedMessages, features) { - var defaultEcmaFeatures = { - arrowFunctions: true, - binaryLiterals: true, - blockBindings: true, - classes: true, - defaultParams: true, - destructuring: true, - forOf: true, - generators: true, - modules: true, - objectLiteralComputedProperties: true, - objectLiteralDuplicateProperties: true, - objectLiteralShorthandMethods: true, - objectLiteralShorthandProperties: true, - octalLiterals: true, - regexUFlag: true, - regexYFlag: true, - restParams: true, - spread: true, - superInFunctions: true, - templateStrings: true, - unicodeCodePointEscapes: true, - globalReturn: true, - jsx: true, - experimentalObjectRestSpread: true - }; - +function verifyAndAssertMessages(code, rules, expectedMessages, sourceType) { var messages = eslint.linter.verify( code, { parser: require.resolve(".."), rules: rules, env: { - node: true + node: true, + es6: true }, - ecmaFeatures: features || defaultEcmaFeatures + parserOptions: { + ecmaVersion: 6, + ecmaFeatures: { + jsx: true, + experimentalObjectRestSpread: true, + globalReturn: true + }, + sourceType: sourceType || "module" + } } ); @@ -143,7 +125,7 @@ describe("verify", function () { "\"use strict\"; () => 1", { "strict": [1, "global"] }, [], - { modules: false } + "script" ); }); @@ -415,8 +397,8 @@ describe("verify", function () { "var b: T = 1; b;" ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, - [ "1:21 \"T\" is defined but never used no-unused-vars", - '2:8 "T" is not defined. no-undef' ] + [ "1:21 'T' is defined but never used no-unused-vars", + "2:8 'T' is not defined. no-undef" ] ); }); @@ -426,7 +408,7 @@ describe("verify", function () { "export class Foo extends Bar {}", ].join("\n"), { "no-unused-vars": 1, "no-undef": 1 }, - [ '2:30 "T" is not defined. no-undef' ] + [ "2:30 'T' is not defined. no-undef" ] ); }); @@ -1151,7 +1133,7 @@ describe("verify", function () { verifyAndAssertMessages( "var unused;", { "no-unused-vars": 1 }, - [ "1:5 \"unused\" is defined but never used no-unused-vars" ] + [ "1:5 'unused' is defined but never used no-unused-vars" ] ); }); @@ -1175,7 +1157,7 @@ describe("verify", function () { verifyAndAssertMessages( "const {Bacona} = require('baconjs')", { "no-undef": 1, "no-unused-vars": 1 }, - [ "1:8 \"Bacona\" is defined but never used no-unused-vars" ] + [ "1:8 'Bacona' is defined but never used no-unused-vars" ] ); }); @@ -1301,7 +1283,7 @@ describe("verify", function () { "var x = 1;" ].join("\n"), { "no-use-before-define": 1 }, - [ "1:13 \"x\" was used before it was defined no-use-before-define" ] + [ "1:13 'x' was used before it was defined no-use-before-define" ] ) }); @@ -1316,10 +1298,10 @@ describe("verify", function () { it("getter/setter #218", function () { verifyAndAssertMessages([ "class Person {", - "set a (v) { }", + " set a (v) { }", "}" ].join("\n"), - { "space-before-function-paren": 1, "space-before-keywords": 1, "indent": 1 }, + { "space-before-function-paren": 1, "keyword-spacing": [1, {"before": true}], "indent": 1 }, [] ) }); From 314a13f4af8a82603caab297951825f250ecc92e Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Sat, 27 Feb 2016 22:54:53 -0500 Subject: [PATCH 2/7] add back acorn-to-esprima as babylon-to-espree and fix tests --- babylon-to-espree/attachComments.js | 57 +++++ babylon-to-espree/convertTemplateType.js | 93 ++++++++ babylon-to-espree/index.js | 20 ++ babylon-to-espree/toAST.js | 274 +++++++++++++++++++++++ babylon-to-espree/toToken.js | 60 +++++ babylon-to-espree/toTokens.js | 16 ++ index.js | 10 +- package.json | 1 - test/babel-eslint.js | 30 ++- 9 files changed, 547 insertions(+), 14 deletions(-) create mode 100644 babylon-to-espree/attachComments.js create mode 100644 babylon-to-espree/convertTemplateType.js create mode 100644 babylon-to-espree/index.js create mode 100644 babylon-to-espree/toAST.js create mode 100644 babylon-to-espree/toToken.js create mode 100644 babylon-to-espree/toTokens.js diff --git a/babylon-to-espree/attachComments.js b/babylon-to-espree/attachComments.js new file mode 100644 index 00000000..4040ce7e --- /dev/null +++ b/babylon-to-espree/attachComments.js @@ -0,0 +1,57 @@ +// comment fixes +module.exports = function (ast, comments, tokens) { + if (comments.length) { + var firstComment = comments[0]; + var lastComment = comments[comments.length - 1]; + // fixup program start + if (!tokens.length) { + // if no tokens, the program starts at the end of the last comment + ast.start = lastComment.end; + ast.loc.start.line = lastComment.loc.end.line; + ast.loc.start.column = lastComment.loc.end.column; + + if (ast.leadingComments === null && ast.innerComments.length) { + ast.leadingComments = ast.innerComments; + } + } else if (firstComment.start < tokens[0].start) { + // if there are comments before the first token, the program starts at the first token + var token = tokens[0]; + // ast.start = token.start; + // ast.loc.start.line = token.loc.start.line; + // ast.loc.start.column = token.loc.start.column; + + // estraverse do not put leading comments on first node when the comment + // appear before the first token + if (ast.body.length) { + var node = ast.body[0]; + node.leadingComments = []; + var firstTokenStart = token.start; + var len = comments.length; + for (var i = 0; i < len && comments[i].start < firstTokenStart; i++) { + node.leadingComments.push(comments[i]); + } + } + } + // fixup program end + if (tokens.length) { + var lastToken = tokens[tokens.length - 1]; + if (lastComment.end > lastToken.end) { + // If there is a comment after the last token, the program ends at the + // last token and not the comment + // ast.end = lastToken.end; + ast.range[1] = lastToken.end; + ast.loc.end.line = lastToken.loc.end.line; + ast.loc.end.column = lastToken.loc.end.column; + } + } + } else { + if (!tokens.length) { + ast.loc.start.line = 1; + ast.loc.end.line = 1; + } + } + if (ast.body && ast.body.length > 0) { + ast.loc.start.line = ast.body[0].loc.start.line; + ast.range[0] = ast.body[0].start; + } +}; diff --git a/babylon-to-espree/convertTemplateType.js b/babylon-to-espree/convertTemplateType.js new file mode 100644 index 00000000..0c3293dc --- /dev/null +++ b/babylon-to-espree/convertTemplateType.js @@ -0,0 +1,93 @@ +module.exports = function (tokens, tt) { + var startingToken = 0; + var currentToken = 0; + var numBraces = 0; // track use of {} + var numBackQuotes = 0; // track number of nested templates + + function isBackQuote(token) { + return tokens[token].type === tt.backQuote; + } + + function isTemplateStarter(token) { + return isBackQuote(token) || + // only can be a template starter when in a template already + tokens[token].type === tt.braceR && numBackQuotes > 0; + } + + function isTemplateEnder(token) { + return isBackQuote(token) || + tokens[token].type === tt.dollarBraceL; + } + + // append the values between start and end + function createTemplateValue(start, end) { + var value = ""; + while (start <= end) { + if (tokens[start].value) { + value += tokens[start].value; + } else if (tokens[start].type !== tt.template) { + value += tokens[start].type.label; + } + start++; + } + return value; + } + + // create Template token + function replaceWithTemplateType(start, end) { + var templateToken = { + type: "Template", + value: createTemplateValue(start, end), + start: tokens[start].start, + end: tokens[end].end, + loc: { + start: tokens[start].loc.start, + end: tokens[end].loc.end + } + }; + + // put new token in place of old tokens + tokens.splice(start, end - start + 1, templateToken); + } + + function trackNumBraces(token) { + if (tokens[token].type === tt.braceL) { + numBraces++; + } else if (tokens[token].type === tt.braceR) { + numBraces--; + } + } + + while (startingToken < tokens.length) { + // template start: check if ` or } + if (isTemplateStarter(startingToken) && numBraces === 0) { + if (isBackQuote(startingToken)) { + numBackQuotes++; + } + + currentToken = startingToken + 1; + + // check if token after template start is "template" + if (currentToken >= tokens.length - 1 || tokens[currentToken].type !== tt.template) { + break; + } + + // template end: find ` or ${ + while (!isTemplateEnder(currentToken)) { + if (currentToken >= tokens.length - 1) { + break; + } + currentToken++; + } + + if (isBackQuote(currentToken)) { + numBackQuotes--; + } + // template start and end found: create new token + replaceWithTemplateType(startingToken, currentToken); + } else if (numBackQuotes > 0) { + trackNumBraces(startingToken); + } + startingToken++; + } +} diff --git a/babylon-to-espree/index.js b/babylon-to-espree/index.js new file mode 100644 index 00000000..70accfdf --- /dev/null +++ b/babylon-to-espree/index.js @@ -0,0 +1,20 @@ +exports.attachComments = require("./attachComments"); + +exports.toTokens = require("./toTokens"); +exports.toAST = require("./toAST"); + +exports.convertComments = function (comments) { + for (var i = 0; i < comments.length; i++) { + var comment = comments[i]; + if (comment.type === "CommentBlock") { + comment.type = "Block"; + } else if (comment.type === "CommentLine") { + comment.type = "Line"; + } + // sometimes comments don't get ranges computed, + // even with options.ranges === true + if (!comment.range) { + comment.range = [comment.start, comment.end]; + } + } +} diff --git a/babylon-to-espree/toAST.js b/babylon-to-espree/toAST.js new file mode 100644 index 00000000..45535add --- /dev/null +++ b/babylon-to-espree/toAST.js @@ -0,0 +1,274 @@ +var source; + +module.exports = function (ast, traverse, code) { + source = code; + ast.sourceType = "module"; + ast.range = [ast.start, ast.end]; + traverse(ast, astTransformVisitor); +}; + +function changeToLiteral(node) { + node.type = 'Literal'; + if (!node.raw) { + if (node.extra && node.extra.raw) { + node.raw = node.extra.raw; + } else { + node.raw = source.slice(node.start, node.end); + } + } +} + +var astTransformVisitor = { + noScope: true, + enter: function (path) { + var node = path.node; + + node.range = [node.start, node.end]; + + // private var to track original node type + node._babelType = node.type; + + if (node.innerComments) { + node.trailingComments = node.innerComments; + delete node.innerComments; + } + + if (node.trailingComments) { + for (var i = 0; i < node.trailingComments.length; i++) { + var comment = node.trailingComments[i]; + if (comment.type === 'CommentLine') { + comment.type = 'Line'; + } else if (comment.type === 'CommentBlock') { + comment.type = 'Block'; + } + comment.range = [comment.start, comment.end]; + } + } + + if (node.leadingComments) { + for (var i = 0; i < node.leadingComments.length; i++) { + var comment = node.leadingComments[i]; + if (comment.type === 'CommentLine') { + comment.type = 'Line'; + } else if (comment.type === 'CommentBlock') { + comment.type = 'Block'; + } + comment.range = [comment.start, comment.end]; + } + } + + // make '_paths' non-enumerable (babel-eslint #200) + Object.defineProperty(node, "_paths", { value: node._paths, writable: true }); + }, + exit: function (path) { + var node = path.node; + + [ + fixDirectives, + ].forEach(function (fixer) { + fixer(path); + }); + + if (path.isJSXText()) { + node.type = 'Literal'; + node.raw = node.value; + } + + if (path.isNumericLiteral() || + path.isStringLiteral()) { + changeToLiteral(node); + } + + if (path.isBooleanLiteral()) { + node.type = 'Literal'; + node.raw = String(node.value); + } + + if (path.isNullLiteral()) { + node.type = 'Literal'; + node.raw = 'null'; + node.value = null; + } + + if (path.isRegExpLiteral()) { + node.type = 'Literal'; + node.raw = node.extra.raw; + node.value = {}; + node.regex = { + pattern: node.pattern, + flags: node.flags + }; + delete node.extra; + delete node.pattern; + delete node.flags; + } + + if (path.isObjectProperty()) { + node.type = 'Property'; + node.kind = 'init'; + } + + if (path.isClassMethod() || path.isObjectMethod()) { + var code = source.slice(node.key.end, node.body.start); + var offset = code.indexOf("("); + + node.value = { + type: 'FunctionExpression', + id: node.id, + params: node.params, + body: node.body, + async: node.async, + generator: node.generator, + expression: node.expression, + defaults: [], // basic support - TODO: remove (old esprima) + loc: { + start: { + line: node.key.loc.start.line, + column: node.key.loc.end.column + offset // a[() {] + }, + end: node.body.loc.end + } + }; + // [asdf]() { + node.value.range = [node.key.end + offset, node.body.end]; + + node.value.start = node.value.range && node.value.range[0] || node.value.loc.start.column; + node.value.end = node.value.range && node.value.range[1] || node.value.loc.end.column; + + if (node.returnType) { + node.value.returnType = node.returnType; + } + + if (node.typeParameters) { + node.value.typeParameters = node.typeParameters; + } + + if (path.isClassMethod()) { + node.type = 'MethodDefinition'; + } + + if (path.isObjectMethod()) { + node.type = 'Property'; + if (node.kind === 'method') { + node.kind = 'init'; + } + } + + delete node.body; + delete node.id; + delete node.async; + delete node.generator; + delete node.expression; + delete node.params; + delete node.returnType; + delete node.typeParameters; + } + + if (path.isRestProperty() || path.isSpreadProperty()) { + node.type = "SpreadProperty"; + node.key = node.value = node.argument; + } + + // flow: prevent "no-undef" + // for "Component" in: "let x: React.Component" + if (path.isQualifiedTypeIdentifier()) { + delete node.id; + } + // for "b" in: "var a: { b: Foo }" + if (path.isObjectTypeProperty()) { + delete node.key; + } + // for "indexer" in: "var a: {[indexer: string]: number}" + if (path.isObjectTypeIndexer()) { + delete node.id; + } + // for "param" in: "var a: { func(param: Foo): Bar };" + if (path.isFunctionTypeParam()) { + delete node.name; + } + + // modules + + if (path.isImportDeclaration()) { + delete node.isType; + } + + if (path.isExportDeclaration()) { + var declar = path.get("declaration"); + if (declar.isClassExpression()) { + node.declaration.type = "ClassDeclaration"; + } else if (declar.isFunctionExpression()) { + node.declaration.type = "FunctionDeclaration"; + } + } + + // remove class property keys (or patch in escope) + if (path.isClassProperty()) { + delete node.key; + } + + // async function as generator + if (path.isFunction()) { + if (node.async) node.generator = true; + } + + // TODO: remove (old esprima) + if (path.isFunction()) { + if (!node.defaults) { + node.defaults = []; + } + } + + // await transform to yield + if (path.isAwaitExpression()) { + node.type = "YieldExpression"; + node.delegate = node.all; + delete node.all; + } + + // template string range fixes + if (path.isTemplateLiteral()) { + node.quasis.forEach(function (q) { + q.range[0] -= 1; + if (q.tail) { + q.range[1] += 1; + } else { + q.range[1] += 2; + } + q.loc.start.column -= 1; + if (q.tail) { + q.loc.end.column += 1; + } else { + q.loc.end.column += 2; + } + }); + } + } +}; + + +function fixDirectives (path) { + if (!(path.isProgram() || path.isFunction())) return; + + var node = path.node; + var directivesContainer = node; + var body = node.body; + + if (node.type !== "Program") { + directivesContainer = body; + body = body.body; + } + + if (!directivesContainer.directives) return; + + directivesContainer.directives.reverse().forEach(function (directive) { + directive.type = "ExpressionStatement"; + directive.expression = directive.value; + delete directive.value; + directive.expression.type = "Literal"; + changeToLiteral(directive.expression); + body.unshift(directive); + }); + delete directivesContainer.directives; +} +// fixDirectives diff --git a/babylon-to-espree/toToken.js b/babylon-to-espree/toToken.js new file mode 100644 index 00000000..7e13f28e --- /dev/null +++ b/babylon-to-espree/toToken.js @@ -0,0 +1,60 @@ +module.exports = function (token, tt, source) { + var type = token.type; + token.range = [token.start, token.end]; + + if (type === tt.name) { + token.type = "Identifier"; + } else if (type === tt.semi || type === tt.comma || + type === tt.parenL || type === tt.parenR || + type === tt.braceL || type === tt.braceR || + type === tt.slash || type === tt.dot || + type === tt.bracketL || type === tt.bracketR || + type === tt.ellipsis || type === tt.arrow || + type === tt.star || type === tt.incDec || + type === tt.colon || type === tt.question || + type === tt.template || type === tt.backQuote || + type === tt.dollarBraceL || type === tt.at || + type === tt.logicalOR || type === tt.logicalAND || + type === tt.bitwiseOR || type === tt.bitwiseXOR || + type === tt.bitwiseAND || type === tt.equality || + type === tt.relational || type === tt.bitShift || + type === tt.plusMin || type === tt.modulo || + type === tt.exponent || type === tt.prefix || + type === tt.doubleColon || + type.isAssign) { + token.type = "Punctuator"; + if (!token.value) token.value = type.label; + } else if (type === tt.jsxTagStart) { + token.type = "Punctuator"; + token.value = "<"; + } else if (type === tt.jsxTagEnd) { + token.type = "Punctuator"; + token.value = ">"; + } else if (type === tt.jsxName) { + token.type = "JSXIdentifier"; + } else if (type === tt.jsxText) { + token.type = "JSXText"; + } else if (type.keyword === "null") { + token.type = "Null"; + } else if (type.keyword === "false" || type.keyword === "true") { + token.type = "Boolean"; + } else if (type.keyword) { + token.type = "Keyword"; + } else if (type === tt.num) { + token.type = "Numeric"; + token.value = source.slice(token.start, token.end); + } else if (type === tt.string) { + token.type = "String"; + token.value = source.slice(token.start, token.end); + } else if (type === tt.regexp) { + token.type = "RegularExpression"; + var value = token.value; + token.regex = { + pattern: value.pattern, + flags: value.flags + }; + token.value = "/" + value.pattern + "/" + value.flags; + } + + return token; +}; diff --git a/babylon-to-espree/toTokens.js b/babylon-to-espree/toTokens.js new file mode 100644 index 00000000..dab4b21b --- /dev/null +++ b/babylon-to-espree/toTokens.js @@ -0,0 +1,16 @@ +var convertTemplateType = require("./convertTemplateType"); +var toToken = require("./toToken"); + +module.exports = function (tokens, tt, code) { + // transform tokens to type "Template" + convertTemplateType(tokens, tt); + var transformedTokens = tokens.filter(function (token) { + return token.type !== "CommentLine" && token.type !== "CommentBlock"; + }); + + for (var i = 0, l = transformedTokens.length; i < l; i++) { + transformedTokens[i] = toToken(transformedTokens[i], tt, code); + } + + return transformedTokens; +}; diff --git a/index.js b/index.js index 47980470..e188aa1d 100644 --- a/index.js +++ b/index.js @@ -1,4 +1,4 @@ -var acornToEsprima = require("acorn-to-esprima"); +var babylonToEspree = require("./babylon-to-espree"); var assign = require("lodash.assign"); var pick = require("lodash.pick"); var Module = require("module"); @@ -437,13 +437,13 @@ exports.parseNoPatch = function (code) { ast.tokens.pop(); // convert tokens - ast.tokens = acornToEsprima.toTokens(ast.tokens, tt, code); + ast.tokens = babylonToEspree.toTokens(ast.tokens, tt, code); // add comments - acornToEsprima.convertComments(ast.comments); + babylonToEspree.convertComments(ast.comments); // transform esprima and acorn divergent nodes - acornToEsprima.toAST(ast, traverse, code); + babylonToEspree.toAST(ast, traverse, code); // ast.program.tokens = ast.tokens; // ast.program.comments = ast.comments; @@ -457,7 +457,7 @@ exports.parseNoPatch = function (code) { delete ast.program; delete ast._paths; - acornToEsprima.attachComments(ast, ast.comments, ast.tokens); + babylonToEspree.attachComments(ast, ast.comments, ast.tokens); return ast; } diff --git a/package.json b/package.json index 4dc700ba..c5f7e19d 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "url": "https://github.com/babel/babel-eslint.git" }, "dependencies": { - "acorn-to-esprima": "^2.0.4", "babel-traverse": "^6.0.20", "babel-types": "^6.0.19", "babylon": "^6.0.18", diff --git a/test/babel-eslint.js b/test/babel-eslint.js index 34efda6d..c24cbe82 100644 --- a/test/babel-eslint.js +++ b/test/babel-eslint.js @@ -32,6 +32,13 @@ function assertImplementsAST(target, source, path) { } } +function lookup(obj, keypath, backwardsDepth) { + if (!keypath) { return obj; } + + return keypath.split('.').slice(0, -1 * backwardsDepth) + .reduce((base, segment) => base && base[segment], obj); +} + function parseAndAssertSame(code) { var esAST = espree.parse(code, { ecmaFeatures: { @@ -56,17 +63,24 @@ function parseAndAssertSame(code) { try { assertImplementsAST(esAST, babylonAST); } catch(err) { - // err.message += - // "\nespree:\n" + - // util.inspect(esAST, {depth: err.depth, colors: true}) + - // "\nbabel-eslint:\n" + - // util.inspect(babylonAST, {depth: err.depth, colors: true}); + var traversal = err.message.slice(3, err.message.indexOf(':')); + if (esAST.tokens) { + delete esAST.tokens; + } + if (babylonAST.tokens) { + delete babylonAST.tokens; + } + err.message += + "\nespree:\n" + + util.inspect(lookup(esAST, traversal, 2), {depth: err.depth, colors: true}) + + "\nbabel-eslint:\n" + + util.inspect(lookup(babylonAST, traversal, 2), {depth: err.depth, colors: true}); throw err; } // assert.equal(esAST, babylonAST); } -describe("acorn-to-esprima", function () { +describe.only("acorn-to-esprima", function () { describe("templates", function () { it("empty template string", function () { parseAndAssertSame("``"); @@ -380,7 +394,7 @@ describe("acorn-to-esprima", function () { it("do not allow import export everywhere", function() { assert.throws(function () { parseAndAssertSame("function F() { import a from \"a\"; }"); - }, /Illegal import declaration/) + }, /SyntaxError: 'import' and 'export' may only appear at the top level/) }); it("return outside function", function () { @@ -391,7 +405,7 @@ describe("acorn-to-esprima", function () { parseAndAssertSame("function F() { super(); }"); }); - it("StringLiteral", function () { + it.only("StringLiteral", function () { parseAndAssertSame(''); parseAndAssertSame(""); parseAndAssertSame("a"); From 35fe006e8255ddd8ddb26cba0d83ac1bb8fc5e36 Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Sat, 27 Feb 2016 23:02:25 -0500 Subject: [PATCH 3/7] fix lint --- babylon-to-espree/toAST.js | 42 +++++++++++++++++++------------------- index.js | 2 +- package.json | 3 ++- test/babel-eslint.js | 8 ++++---- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/babylon-to-espree/toAST.js b/babylon-to-espree/toAST.js index 45535add..39845e54 100644 --- a/babylon-to-espree/toAST.js +++ b/babylon-to-espree/toAST.js @@ -8,7 +8,7 @@ module.exports = function (ast, traverse, code) { }; function changeToLiteral(node) { - node.type = 'Literal'; + node.type = "Literal"; if (!node.raw) { if (node.extra && node.extra.raw) { node.raw = node.extra.raw; @@ -36,10 +36,10 @@ var astTransformVisitor = { if (node.trailingComments) { for (var i = 0; i < node.trailingComments.length; i++) { var comment = node.trailingComments[i]; - if (comment.type === 'CommentLine') { - comment.type = 'Line'; - } else if (comment.type === 'CommentBlock') { - comment.type = 'Block'; + if (comment.type === "CommentLine") { + comment.type = "Line"; + } else if (comment.type === "CommentBlock") { + comment.type = "Block"; } comment.range = [comment.start, comment.end]; } @@ -48,10 +48,10 @@ var astTransformVisitor = { if (node.leadingComments) { for (var i = 0; i < node.leadingComments.length; i++) { var comment = node.leadingComments[i]; - if (comment.type === 'CommentLine') { - comment.type = 'Line'; - } else if (comment.type === 'CommentBlock') { - comment.type = 'Block'; + if (comment.type === "CommentLine") { + comment.type = "Line"; + } else if (comment.type === "CommentBlock") { + comment.type = "Block"; } comment.range = [comment.start, comment.end]; } @@ -70,7 +70,7 @@ var astTransformVisitor = { }); if (path.isJSXText()) { - node.type = 'Literal'; + node.type = "Literal"; node.raw = node.value; } @@ -80,18 +80,18 @@ var astTransformVisitor = { } if (path.isBooleanLiteral()) { - node.type = 'Literal'; + node.type = "Literal"; node.raw = String(node.value); } if (path.isNullLiteral()) { - node.type = 'Literal'; - node.raw = 'null'; + node.type = "Literal"; + node.raw = "null"; node.value = null; } if (path.isRegExpLiteral()) { - node.type = 'Literal'; + node.type = "Literal"; node.raw = node.extra.raw; node.value = {}; node.regex = { @@ -104,8 +104,8 @@ var astTransformVisitor = { } if (path.isObjectProperty()) { - node.type = 'Property'; - node.kind = 'init'; + node.type = "Property"; + node.kind = "init"; } if (path.isClassMethod() || path.isObjectMethod()) { @@ -113,7 +113,7 @@ var astTransformVisitor = { var offset = code.indexOf("("); node.value = { - type: 'FunctionExpression', + type: "FunctionExpression", id: node.id, params: node.params, body: node.body, @@ -144,13 +144,13 @@ var astTransformVisitor = { } if (path.isClassMethod()) { - node.type = 'MethodDefinition'; + node.type = "MethodDefinition"; } if (path.isObjectMethod()) { - node.type = 'Property'; - if (node.kind === 'method') { - node.kind = 'init'; + node.type = "Property"; + if (node.kind === "method") { + node.kind = "init"; } } diff --git a/index.js b/index.js index e188aa1d..111209b2 100644 --- a/index.js +++ b/index.js @@ -450,7 +450,7 @@ exports.parseNoPatch = function (code) { // ast = ast.program; // remove File - ast.type = 'Program'; + ast.type = "Program"; ast.sourceType = ast.program.sourceType; ast.directives = ast.program.directives; ast.body = ast.program.body; diff --git a/package.json b/package.json index c5f7e19d..dfe4858b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,8 @@ "bootstrap": "git submodule update --init && cd eslint && npm install", "eslint": "cd eslint && mocha -c tests/lib/rules/*.js -r ../eslint-tester.js", "test": "mocha", - "lint": "./node_modules/eslint/bin/eslint.js ./test index.js acorn-to-esprima.js", + "lint": "./node_modules/eslint/bin/eslint.js index.js babylon-to-espree test", + "fix": "./node_modules/eslint/bin/eslint.js index.js babylon-to-espree test --fix", "preversion": "npm test" }, "author": "Sebastian McKenzie ", diff --git a/test/babel-eslint.js b/test/babel-eslint.js index c24cbe82..9a0fab07 100644 --- a/test/babel-eslint.js +++ b/test/babel-eslint.js @@ -35,8 +35,8 @@ function assertImplementsAST(target, source, path) { function lookup(obj, keypath, backwardsDepth) { if (!keypath) { return obj; } - return keypath.split('.').slice(0, -1 * backwardsDepth) - .reduce((base, segment) => base && base[segment], obj); + return keypath.split(".").slice(0, -1 * backwardsDepth) + .reduce(function (base, segment) { base && base[segment], obj }); } function parseAndAssertSame(code) { @@ -63,7 +63,7 @@ function parseAndAssertSame(code) { try { assertImplementsAST(esAST, babylonAST); } catch(err) { - var traversal = err.message.slice(3, err.message.indexOf(':')); + var traversal = err.message.slice(3, err.message.indexOf(":")); if (esAST.tokens) { delete esAST.tokens; } @@ -406,7 +406,7 @@ describe.only("acorn-to-esprima", function () { }); it.only("StringLiteral", function () { - parseAndAssertSame(''); + parseAndAssertSame(""); parseAndAssertSame(""); parseAndAssertSame("a"); }); From 27bec527f5f97dfdc0d5e0221b87535864b0ea1b Mon Sep 17 00:00:00 2001 From: Henry Zhu Date: Sat, 27 Feb 2016 23:11:06 -0500 Subject: [PATCH 4/7] add folder to npm files --- package.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index dfe4858b..28a969a4 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Custom parser for ESLint", "main": "index.js", "files": [ - "index.js" + "index.js", + "babylon-to-espree" ], "repository": { "type": "git", From 31dd8116f09ad08a6970d7e2bcbaf5d4fe0373f0 Mon Sep 17 00:00:00 2001 From: Daniel Tschinder Date: Mon, 29 Feb 2016 16:00:56 +0100 Subject: [PATCH 5/7] fix(SpreadOperator): Correctly transform SpreadOperator to ExperimentalRestProperty --- babylon-to-espree/toAST.js | 3 +-- test/babel-eslint.js | 18 +++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/babylon-to-espree/toAST.js b/babylon-to-espree/toAST.js index 39845e54..13aacf34 100644 --- a/babylon-to-espree/toAST.js +++ b/babylon-to-espree/toAST.js @@ -165,8 +165,7 @@ var astTransformVisitor = { } if (path.isRestProperty() || path.isSpreadProperty()) { - node.type = "SpreadProperty"; - node.key = node.value = node.argument; + node.type = "Experimental" + node.type; } // flow: prevent "no-undef" diff --git a/test/babel-eslint.js b/test/babel-eslint.js index 9a0fab07..a66f1c40 100644 --- a/test/babel-eslint.js +++ b/test/babel-eslint.js @@ -36,7 +36,7 @@ function lookup(obj, keypath, backwardsDepth) { if (!keypath) { return obj; } return keypath.split(".").slice(0, -1 * backwardsDepth) - .reduce(function (base, segment) { base && base[segment], obj }); + .reduce(function (base, segment) { return base && base[segment], obj }); } function parseAndAssertSame(code) { @@ -80,7 +80,7 @@ function parseAndAssertSame(code) { // assert.equal(esAST, babylonAST); } -describe.only("acorn-to-esprima", function () { +describe("acorn-to-esprima", function () { describe("templates", function () { it("empty template string", function () { parseAndAssertSame("``"); @@ -405,7 +405,7 @@ describe.only("acorn-to-esprima", function () { parseAndAssertSame("function F() { super(); }"); }); - it.only("StringLiteral", function () { + it("StringLiteral", function () { parseAndAssertSame(""); parseAndAssertSame(""); parseAndAssertSame("a"); @@ -443,5 +443,17 @@ describe.only("acorn-to-esprima", function () { "};" ].join("\n")); }); + + it("RestOperator", function () { + parseAndAssertSame("var { a, ...b } = c"); + parseAndAssertSame("var [ a, ...b ] = c"); + parseAndAssertSame("var a = function (...b) {}"); + }); + + it("SpreadOperator", function () { + parseAndAssertSame("var a = { b, ...c }"); + parseAndAssertSame("var a = [ a, ...b ]"); + parseAndAssertSame("var a = sum(...b)"); + }); }); }); From f91d2c73522e66151b1db9cba191f5775c5223ff Mon Sep 17 00:00:00 2001 From: Daniel Tschinder Date: Tue, 1 Mar 2016 11:53:04 +0100 Subject: [PATCH 6/7] fix(parse-errors): align parse errors from babel with eslint. babel/babylon use zero based index, espree use one based index. --- index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.js b/index.js index 111209b2..1cbf8ed1 100644 --- a/index.js +++ b/index.js @@ -422,7 +422,7 @@ exports.parseNoPatch = function (code) { } catch (err) { if (err instanceof SyntaxError) { err.lineNumber = err.loc.line; - err.column = err.loc.column; + 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+)\)$/, ""); From 285110eab197c315591ce0b427ffbabd1763665e Mon Sep 17 00:00:00 2001 From: Daniel Tschinder Date: Tue, 1 Mar 2016 12:00:34 +0100 Subject: [PATCH 7/7] fix(sourceType): Use sourceType provided by eslint The sourceType was hardcoded to "module", but at least the "strict" rule behaves different depending on the sourceType. This patch reads the sourceType from the eslint-parserOptions and uses it if available, otherwise falls back to old behavior and uses "module". --- babylon-to-espree/toAST.js | 1 - index.js | 10 ++++++---- test/integration.js | 3 +++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/babylon-to-espree/toAST.js b/babylon-to-espree/toAST.js index 13aacf34..f82c4e59 100644 --- a/babylon-to-espree/toAST.js +++ b/babylon-to-espree/toAST.js @@ -2,7 +2,6 @@ var source; module.exports = function (ast, traverse, code) { source = code; - ast.sourceType = "module"; ast.range = [ast.start, ast.end]; traverse(ast, astTransformVisitor); }; diff --git a/index.js b/index.js index 1cbf8ed1..87180e67 100644 --- a/index.js +++ b/index.js @@ -380,7 +380,9 @@ function monkeypatch() { }; } -exports.parse = function (code) { +exports.parse = function (code, options) { + options = options || {}; + try { monkeypatch(); } catch (err) { @@ -388,12 +390,12 @@ exports.parse = function (code) { process.exit(1); } - return exports.parseNoPatch(code); + return exports.parseNoPatch(code, options); } -exports.parseNoPatch = function (code) { +exports.parseNoPatch = function (code, options) { var opts = { - sourceType: "module", + sourceType: options.sourceType || "module", strictMode: true, allowImportExportEverywhere: false, // consistent with espree allowReturnOutsideFunction: true, diff --git a/test/integration.js b/test/integration.js index 5ce7a443..f3f09522 100644 --- a/test/integration.js +++ b/test/integration.js @@ -13,6 +13,9 @@ var errorLevel = 2; var baseEslintOpts = { parser: require.resolve(".."), + parserOptions: { + sourceType: "script" + } }; /**