Merge pull request babel/babel-eslint#76 from Cellule/attach_comments
Attach comments to ast using estraverse
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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"));
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user