From 46f65bcf494eddc0b9f967eb021b1f242047f380 Mon Sep 17 00:00:00 2001 From: Sebastian McKenzie Date: Wed, 6 May 2015 17:19:30 +0100 Subject: [PATCH] Merge pull request babel/babel-eslint#76 from Cellule/attach_comments Attach comments to ast using estraverse --- eslint/babel-eslint-parser/index.js | 49 ++++++++++- .../babel-eslint-parser/test/babel-eslint.js | 85 ++++++++++++------- 2 files changed, 103 insertions(+), 31 deletions(-) diff --git a/eslint/babel-eslint-parser/index.js b/eslint/babel-eslint-parser/index.js index dcef0c5b1c..f8aef5b7f5 100644 --- a/eslint/babel-eslint-parser/index.js +++ b/eslint/babel-eslint-parser/index.js @@ -6,6 +6,7 @@ var parse = require("babel-core").parse; var path = require("path"); var t = require("babel-core").types; +var estraverse; var hasPatched = false; function createModule(filename) { @@ -32,7 +33,7 @@ function monkeypatch() { var escopeMod = createModule(escopeLoc); // monkeypatch estraverse - var estraverse = escopeMod.require("estraverse"); + estraverse = escopeMod.require("estraverse"); assign(estraverse.VisitorKeys, t.VISITOR_KEYS); // monkeypatch escope @@ -51,6 +52,51 @@ function monkeypatch() { }; } +exports.attachComments = function(ast, comments, tokens) { + estraverse.attachComments(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.range[0] = lastComment.range[1]; + ast.loc.start.line = lastComment.loc.end.line; + ast.loc.start.column = lastComment.loc.end.column; + } else if (firstComment.start < tokens[0].range[0]) { + // if there are comments before the first token, the program starts at the first token + var token = tokens[0]; + ast.range[0] = token.range[0]; + 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.range[0]; + 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.range[1]) { + // If there is a comment after the last token, the program ends at the + // last token and not the comment + ast.range[1] = lastToken.range[1]; + ast.loc.end.line = lastToken.loc.end.line; + ast.loc.end.column = lastToken.loc.end.column; + } + } + } +}; + exports.parse = function (code) { try { monkeypatch(); @@ -92,6 +138,7 @@ exports.parse = function (code) { // add comments ast.comments = comments; + exports.attachComments(ast, comments, tokens); // transform esprima and acorn divergent nodes acornToEsprima.toAST(ast); diff --git a/eslint/babel-eslint-parser/test/babel-eslint.js b/eslint/babel-eslint-parser/test/babel-eslint.js index 1dc2008fb7..7c7c45c5b2 100644 --- a/eslint/babel-eslint-parser/test/babel-eslint.js +++ b/eslint/babel-eslint-parser/test/babel-eslint.js @@ -2,45 +2,32 @@ var babelEslint = require(".."); var espree = require("espree"); var util = require("util"); -function assertSameAST(a, b, path) { +// Checks if the source ast implements the target ast. Ignores extra keys on source ast +function assertImplementsAST(target, source, path) { if (!path) { path = []; } function error(text) { - throw new Error("At " + path.join(".") + ": " + text + ":\n" + util.inspect(a) + "\n" + util.inspect(b)); + var err = new Error("At " + path.join(".") + ": " + text + ":"); + err.depth = path.length + 1; + throw err; } - var typeA = a === null ? "null" : typeof a; - var typeB = b === null ? "null" : typeof b; + var typeA = target === null ? "null" : typeof target; + var typeB = source === null ? "null" : typeof source; if (typeA !== typeB) { - error("have not the same type (" + typeA + " !== " + typeB + ")"); + error("have different types (" + typeA + " !== " + typeB + ")"); } else if (typeA === "object") { - var keysA = Object.keys(a); - var keysB = Object.keys(b); - keysA.sort(); - keysB.sort(); - while (true) { - var keyA = keysA.shift(); - if (keyA && keyA[0] === "_") continue; - - // Exception: ignore "end" and "start" outside "loc" properties - if ((keyA === "end" || keyA === "start") && path[path.length - 1] !== "loc") continue; - - // Exception: ignore root "comments" property - if (keyA === "comments" && path.length === 0) continue; - - var keyB = keysB.shift(); - - if (keyA === undefined && keyB === undefined) break; - if (keyA === undefined || keyA > keyB) error('first does not have key "' + keyB + '"'); - if (keyB === undefined || keyA < keyB) error('second does not have key "' + keyA + '"'); - path.push(keyA); - assertSameAST(a[keyA], b[keyB], path); + var keysTarget = Object.keys(target); + for(var i in keysTarget) { + var key = keysTarget[i]; + path.push(key); + assertImplementsAST(target[key], source[key], path); path.pop(); } - } else if (a !== b) { - error("are different (" + JSON.stringify(a) + " !== " + JSON.stringify(b) + ")"); + } else if (target !== source) { + error("are different (" + JSON.stringify(target) + " !== " + JSON.stringify(source) + ")"); } } @@ -53,10 +40,21 @@ function parseAndAssertSame(code) { }, tokens: true, loc: true, - range: true + range: true, + comment: true, + attachComment: true }); var acornAST = babelEslint.parse(code); - assertSameAST(acornAST, esAST); + try { + assertImplementsAST(esAST, acornAST); + } catch(err) { + err.message += + "\nespree:\n" + + util.inspect(esAST, {depth: err.depth}) + + "\nbabel-eslint:\n" + + util.inspect(acornAST, {depth: err.depth}); + throw err; + } } describe("acorn-to-esprima", function () { @@ -131,4 +129,31 @@ describe("acorn-to-esprima", function () { it("export named alias", function () { parseAndAssertSame("export { foo as bar };"); }); + + it("empty program with line comment", function () { + parseAndAssertSame("// single comment"); + }); + + it("empty program with block comment", function () { + parseAndAssertSame(" /* multiline\n * comment\n*/"); + }); + + it("line comments", function () { + parseAndAssertSame([ + " // single comment", + "var foo = 15; // comment next to statement", + "// second comment after statement" + ].join("\n")); + }); + + it("block comments", function () { + parseAndAssertSame([ + " /* single comment */ ", + "var foo = 15; /* comment next to statement */", + "/*", + " * multiline", + " * comment", + " */" + ].join("\n")); + }); });