Merge pull request babel/babel-eslint#76 from Cellule/attach_comments

Attach comments to ast using estraverse
This commit is contained in:
Sebastian McKenzie
2015-05-06 17:19:30 +01:00
parent 47c1673092
commit 46f65bcf49
2 changed files with 103 additions and 31 deletions

View File

@@ -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);

View File

@@ -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"));
});
});