recast/test/parser.ts

237 lines
6.4 KiB
TypeScript

import assert from "assert";
import { parse } from "../lib/parser";
import { getReprinter } from "../lib/patcher";
import { Printer } from "../lib/printer";
import { fromString } from "../lib/lines";
import * as types from "ast-types";
const namedTypes = types.namedTypes;
import FastPath from "../lib/fast-path";
import { EOL as eol } from "os";
const nodeMajorVersion = parseInt(process.versions.node, 10);
// Esprima seems unable to handle unnamed top-level functions, so declare
// test functions with names and then export them later.
describe("parser", function () {
[
"../parsers/acorn",
"../parsers/babel",
"../parsers/esprima",
"../parsers/flow",
"../parsers/typescript",
].forEach(runTestsForParser);
it("AlternateParser", function () {
const b = types.builders;
const parser = {
parse: function () {
const program = b.program([
b.expressionStatement(b.identifier("surprise")),
]);
program.comments = [];
return program;
},
};
function check(options?: any) {
const ast = parse("ignored", options);
const printer = new Printer();
types.namedTypes.File.assert(ast, true);
assert.strictEqual(printer.printGenerically(ast).code, "surprise;");
}
check({ esprima: parser });
check({ parser: parser });
});
});
function runTestsForParser(parserId: string) {
const parserName = parserId.split("/").pop();
if (
nodeMajorVersion < 6 &&
(parserName === "babel" ||
parserName === "flow" ||
parserName === "typescript")
) {
// Babel 7 no longer supports Node 4 or 5.
return;
}
if (!parserName) {
return;
}
const parser = require(parserId);
it("[" + parserName + "] empty source", function () {
const printer = new Printer();
function check(code: string) {
const ast = parse(code, { parser });
assert.strictEqual(printer.print(ast).code, code);
}
check("");
check("/* block comment */");
check("// line comment");
check("\t\t\t");
check(eol);
check(eol + eol);
check(" ");
});
const lineCommentTypes: { [name: string]: string } = {
acorn: "Line",
babel: "CommentLine",
esprima: "Line",
flow: "CommentLine",
typescript: "CommentLine",
};
it("[" + parserName + "] parser basics", function testParser(done) {
const code = testParser + "";
const ast = parse(code, { parser });
namedTypes.File.assert(ast);
assert.ok(getReprinter(FastPath.from(ast)));
const funDecl = ast.program.body[0];
const funBody = funDecl.body;
namedTypes.FunctionDeclaration.assert(funDecl);
namedTypes.BlockStatement.assert(funBody);
assert.ok(getReprinter(FastPath.from(funBody)));
const lastStatement = funBody.body.pop();
const doneCall = lastStatement.expression;
assert.ok(!getReprinter(FastPath.from(funBody)));
assert.ok(getReprinter(FastPath.from(ast)));
funBody.body.push(lastStatement);
assert.ok(getReprinter(FastPath.from(funBody)));
assert.strictEqual(doneCall.callee.name, "done");
assert.strictEqual(lastStatement.comments.length, 2);
const firstComment = lastStatement.comments[0];
assert.strictEqual(firstComment.type, lineCommentTypes[parserName]);
assert.strictEqual(firstComment.leading, true);
assert.strictEqual(firstComment.trailing, false);
assert.strictEqual(
firstComment.value,
" Make sure done() remains the final statement in this function,",
);
const secondComment = lastStatement.comments[1];
assert.strictEqual(secondComment.type, lineCommentTypes[parserName]);
assert.strictEqual(secondComment.leading, true);
assert.strictEqual(secondComment.trailing, false);
assert.strictEqual(
secondComment.value,
" or the above assertions will probably fail.",
);
// Make sure done() remains the final statement in this function,
// or the above assertions will probably fail.
done();
});
it("[" + parserName + "] LocationFixer", function () {
const code = ["function foo() {", " a()", " b()", "}"].join(eol);
const ast = parse(code, { parser });
const printer = new Printer();
types.visit(ast, {
visitFunctionDeclaration: function (path) {
if (namedTypes.BlockStatement.check(path.node.body)) {
path.node.body.body.reverse();
}
this.traverse(path);
},
});
const altered = code
.replace("a()", "xxx")
.replace("b()", "a()")
.replace("xxx", "b()");
assert.strictEqual(altered, printer.print(ast).code);
});
it("[" + parserName + "] TabHandling", function () {
function check(code: string, tabWidth: number) {
const lines = fromString(code, { tabWidth: tabWidth });
assert.strictEqual(lines.length, 1);
function checkId(s: any, loc: types.namedTypes.SourceLocation) {
const sliced = lines.slice(loc.start, loc.end);
assert.strictEqual(s + "", sliced.toString());
}
types.visit(
parse(code, {
tabWidth: tabWidth,
parser,
}),
{
visitIdentifier(path) {
const ident = path.node;
checkId(ident.name, ident.loc!);
this.traverse(path);
},
visitLiteral(path) {
const lit = path.node;
checkId(lit.value, lit.loc!);
this.traverse(path);
},
},
);
}
for (let tabWidth = 1; tabWidth <= 8; ++tabWidth) {
check("\t\ti = 10;", tabWidth);
check("\t\ti \t= 10;", tabWidth);
check("\t\ti \t=\t 10;", tabWidth);
check("\t \ti \t=\t 10;", tabWidth);
check("\t \ti \t=\t 10;\t", tabWidth);
check("\t \ti \t=\t 10;\t ", tabWidth);
}
});
it("[" + parserName + "] Only comment followed by space", function () {
const printer = new Printer();
function check(code: string) {
const ast = parse(code, { parser });
assert.strictEqual(printer.print(ast).code, code);
}
check("// comment");
check("// comment ");
check("// comment\n");
check("// comment\n\n");
check(" // comment\n");
check(" // comment\n ");
check(" // comment \n ");
check("/* comment */");
check("/* comment */ ");
check(" /* comment */");
check("\n/* comment */");
check("\n/* comment */\n");
check("\n /* comment */\n ");
check("/* comment */\n ");
check("/* com\n\nment */");
check("/* com\n\nment */ ");
check(" /* com\n\nment */ ");
});
}