ts-simple-ast
Version:
TypeScript compiler wrapper for AST navigation and code generation.
284 lines (282 loc) • 16.8 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const path = require("path");
const chai_1 = require("chai");
const compiler_1 = require("./../compiler");
const TsSimpleAst_1 = require("./../TsSimpleAst");
const ManipulationSettings_1 = require("./../ManipulationSettings");
const utils_1 = require("./../utils");
const errors = require("./../errors");
const testHelpers = require("./testHelpers");
describe("TsSimpleAst", () => {
describe("constructor", () => {
it("should set the manipulation settings if provided", () => {
const ast = new TsSimpleAst_1.TsSimpleAst({
manipulationSettings: {
indentationText: ManipulationSettings_1.IndentationText.EightSpaces
}
});
chai_1.expect(ast.manipulationSettings.getIndentationText()).to.equal(ManipulationSettings_1.IndentationText.EightSpaces);
});
});
describe("getCompilerOptions", () => {
it(`should get the default compiler options when not providing anything and no tsconfig exists`, () => {
const host = testHelpers.getFileSystemHostWithFiles([]);
const ast = new TsSimpleAst_1.TsSimpleAst({}, host);
chai_1.expect(ast.getCompilerOptions()).to.deep.equal({});
});
it(`should not get the compiler options from tsconfig.json when not providing anything and a tsconfig exists`, () => {
const host = testHelpers.getFileSystemHostWithFiles([{ filePath: "tsconfig.json", text: `{ "compilerOptions": { "rootDir": "test", "target": "ES5" } }` }]);
const ast = new TsSimpleAst_1.TsSimpleAst({}, host);
chai_1.expect(ast.getCompilerOptions()).to.deep.equal({});
});
it(`should get empty compiler options when providing an empty compiler options object`, () => {
const host = testHelpers.getFileSystemHostWithFiles([]);
const ast = new TsSimpleAst_1.TsSimpleAst({ compilerOptions: {} }, host);
chai_1.expect(ast.getCompilerOptions()).to.deep.equal({});
});
it(`should override the tsconfig options`, () => {
const host = testHelpers.getFileSystemHostWithFiles([{ filePath: "tsconfig.json", text: `{ "compilerOptions": { "rootDir": "test", "target": "ES5" } }` }]);
const ast = new TsSimpleAst_1.TsSimpleAst({
tsConfigFilePath: "tsconfig.json",
compilerOptions: {
target: 2,
allowJs: true
}
}, host);
chai_1.expect(ast.getCompilerOptions()).to.deep.equal({ rootDir: "test", target: 2, allowJs: true });
});
});
describe("getOrAddSourceFile", () => {
it("should throw an exception if creating a source file at an existing path", () => {
const fileSystem = testHelpers.getFileSystemHostWithFiles([]);
const ast = new TsSimpleAst_1.TsSimpleAst(undefined, fileSystem);
chai_1.expect(() => {
ast.getOrAddSourceFile("non-existent-file.ts");
}).to.throw(errors.FileNotFoundError, `File not found: ${utils_1.FileUtils.getStandardizedAbsolutePath("non-existent-file.ts")}`);
});
});
describe("addSourceFiles", () => {
// todo: would be more ideal to use a mocking framework here
const fileSystem = testHelpers.getFileSystemHostWithFiles([{ filePath: "file1.ts", text: "" }, { filePath: "file2.ts", text: "" }]);
fileSystem.glob = patterns => {
if (patterns.length !== 1 || patterns[0] !== "some-pattern")
throw new Error("Unexpected input!");
return ["file1.ts", "file2.ts", "file3.ts"].map(p => utils_1.FileUtils.getStandardizedAbsolutePath(p));
};
const ast = new TsSimpleAst_1.TsSimpleAst(undefined, fileSystem);
ast.addSourceFiles("some-pattern");
it("should have 2 source files", () => {
const sourceFiles = ast.getSourceFiles();
chai_1.expect(sourceFiles.length).to.equal(2);
chai_1.expect(sourceFiles[0].getFilePath()).to.equal(utils_1.FileUtils.getStandardizedAbsolutePath("file1.ts"));
chai_1.expect(sourceFiles[0].isSaved()).to.be.true; // should be saved because it was read from the disk
chai_1.expect(sourceFiles[1].getFilePath()).to.equal(utils_1.FileUtils.getStandardizedAbsolutePath("file2.ts"));
});
});
describe("addSourceFileFromText", () => {
it("should throw an exception if creating a source file at an existing path", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
ast.addSourceFileFromText("file.ts", "");
chai_1.expect(() => {
ast.addSourceFileFromText("file.ts", "");
}).to.throw(errors.InvalidOperationError, `A source file already exists at the provided file path: ${utils_1.FileUtils.getStandardizedAbsolutePath("file.ts")}`);
});
it("should mark the source file as having not been saved", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
const sourceFile = ast.addSourceFileFromText("file.ts", "");
chai_1.expect(sourceFile.isSaved()).to.be.false;
});
it("", () => {
// todo: remove
const ast = new TsSimpleAst_1.TsSimpleAst();
const sourceFile = ast.addSourceFileFromText("MyFile.ts", "enum MyEnum {\n myMember\n}\nlet myEnum: MyEnum;\nlet myOtherEnum: MyNewEnum;");
const enumDef = sourceFile.getEnums()[0];
enumDef.rename("NewName");
const addedEnum = sourceFile.addEnum({
name: "MyNewEnum"
});
addedEnum.rename("MyOtherNewName");
const enumMember = enumDef.getMembers()[0];
enumMember.rename("myNewMemberName");
chai_1.expect(enumMember.getValue()).to.equal(0);
chai_1.expect(sourceFile.getFullText()).to.equal("enum NewName {\n myNewMemberName\n}\nlet myEnum: NewName;\nlet myOtherEnum: MyOtherNewName;\n\nenum MyOtherNewName {\n}\n");
});
});
describe("addSourceFileFromStructure", () => {
it("should throw an exception if creating a source file at an existing path", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
ast.addSourceFileFromText("file.ts", "");
chai_1.expect(() => {
ast.addSourceFileFromStructure("file.ts", {});
}).to.throw(errors.InvalidOperationError, `A source file already exists at the provided file path: ${utils_1.FileUtils.getStandardizedAbsolutePath("file.ts")}`);
});
it("should mark the source file as having not been saved", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
const sourceFile = ast.addSourceFileFromStructure("file.ts", {});
chai_1.expect(sourceFile.isSaved()).to.be.false;
});
it("should add a source file based on a structure", () => {
// basic test
const ast = new TsSimpleAst_1.TsSimpleAst();
const sourceFile = ast.addSourceFileFromStructure("MyFile.ts", {
enums: [{
name: "MyEnum"
}],
imports: [{ moduleSpecifier: "./test" }],
exports: [{ moduleSpecifier: "./test" }]
});
chai_1.expect(sourceFile.getFullText()).to.equal(`import "./test";\n\nenum MyEnum {\n}\n\nexport * from "./test";\n`);
});
});
describe("mixing real files with virtual files", () => {
const testFilesDirPath = path.join(__dirname, "../../src/tests/testFiles");
const ast = new TsSimpleAst_1.TsSimpleAst();
ast.addSourceFiles(`${testFilesDirPath}/**/*.ts`);
ast.addSourceFileFromText(path.join(testFilesDirPath, "variableTestFile.ts"), `import * as testClasses from "./testClasses";\n\nlet var = new testClasses.TestClass().name;\n`);
it("should have 3 source files", () => {
chai_1.expect(ast.getSourceFiles().length).to.equal(3);
});
it("should rename an identifier appropriately", () => {
const interfaceFile = ast.getSourceFileOrThrow("testInterfaces.ts");
interfaceFile.getInterfaces()[0].getProperties()[0].getNameIdentifier().rename("newName");
const variableFile = ast.getSourceFileOrThrow("variableTestFile.ts");
chai_1.expect(variableFile.getFullText()).to.equal(`import * as testClasses from "./testClasses";\n\nlet var = new testClasses.TestClass().newName;\n`);
});
});
describe("removeSourceFile", () => {
it("should remove the source file", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
const sourceFile = ast.addSourceFileFromText("myFile.ts", ``);
chai_1.expect(ast.removeSourceFile(sourceFile)).to.equal(true);
chai_1.expect(ast.removeSourceFile(sourceFile)).to.equal(false);
chai_1.expect(ast.getSourceFiles().length).to.equal(0);
chai_1.expect(() => sourceFile.getChildCount()).to.throw(); // should be disposed
});
});
describe("saveUnsavedSourceFiles", () => {
it("should save all the unsaved source files asynchronously", () => __awaiter(this, void 0, void 0, function* () {
const fileSystem = testHelpers.getFileSystemHostWithFiles([]);
const ast = new TsSimpleAst_1.TsSimpleAst(undefined, fileSystem);
ast.addSourceFileFromText("file1.ts", "").saveSync();
ast.addSourceFileFromText("file2.ts", "");
ast.addSourceFileFromText("file3.ts", "");
yield ast.saveUnsavedSourceFiles();
chai_1.expect(ast.getSourceFiles().map(f => f.isSaved())).to.deep.equal([true, true, true]);
chai_1.expect(fileSystem.getWriteLog().length).to.equal(2); // 2 writes
chai_1.expect(fileSystem.getSyncWriteLog().length).to.equal(1); // 1 write
}));
});
describe("saveUnsavedSourceFilesSync", () => {
it("should save all the unsaved source files synchronously", () => {
const fileSystem = testHelpers.getFileSystemHostWithFiles([]);
const ast = new TsSimpleAst_1.TsSimpleAst(undefined, fileSystem);
ast.addSourceFileFromText("file1.ts", "").saveSync();
ast.addSourceFileFromText("file2.ts", "");
ast.addSourceFileFromText("file3.ts", "");
ast.saveUnsavedSourceFilesSync();
chai_1.expect(ast.getSourceFiles().map(f => f.isSaved())).to.deep.equal([true, true, true]);
chai_1.expect(fileSystem.getWriteLog().length).to.equal(0);
chai_1.expect(fileSystem.getSyncWriteLog().length).to.equal(3); // 3 writes
});
});
describe("emit", () => {
function setup(compilerOptions) {
const fileSystem = testHelpers.getFileSystemHostWithFiles([]);
const ast = new TsSimpleAst_1.TsSimpleAst({ compilerOptions }, fileSystem);
ast.addSourceFileFromText("file1.ts", "const num1 = 1;");
ast.addSourceFileFromText("file2.ts", "const num2 = 2;");
return { fileSystem, ast };
}
it("should emit multiple files when not specifying any options", () => {
const { ast, fileSystem } = setup({ noLib: true, outDir: "dist" });
const result = ast.emit();
chai_1.expect(result).to.be.instanceof(compiler_1.EmitResult);
const writeLog = fileSystem.getSyncWriteLog();
chai_1.expect(writeLog[0].filePath).to.equal("dist/file1.js");
chai_1.expect(writeLog[0].fileText).to.equal("var num1 = 1;\n");
chai_1.expect(writeLog[1].filePath).to.equal("dist/file2.js");
chai_1.expect(writeLog[1].fileText).to.equal("var num2 = 2;\n");
chai_1.expect(writeLog.length).to.equal(2);
});
it("should emit the source file when specified", () => {
const { ast, fileSystem } = setup({ noLib: true, outDir: "dist" });
ast.emit({ targetSourceFile: ast.getSourceFile("file1.ts") });
const writeLog = fileSystem.getSyncWriteLog();
chai_1.expect(writeLog[0].filePath).to.equal("dist/file1.js");
chai_1.expect(writeLog[0].fileText).to.equal("var num1 = 1;\n");
chai_1.expect(writeLog.length).to.equal(1);
});
it("should only emit the declaration file when specified", () => {
const { ast, fileSystem } = setup({ noLib: true, outDir: "dist", declaration: true });
ast.emit({ emitOnlyDtsFiles: true });
const writeLog = fileSystem.getSyncWriteLog();
chai_1.expect(writeLog[0].filePath).to.equal("dist/file1.d.ts");
chai_1.expect(writeLog[0].fileText).to.equal("declare const num1 = 1;\n");
chai_1.expect(writeLog[1].filePath).to.equal("dist/file2.d.ts");
chai_1.expect(writeLog[1].fileText).to.equal("declare const num2 = 2;\n");
chai_1.expect(writeLog.length).to.equal(2);
});
});
describe("getSourceFileOrThrow", () => {
it("should throw when it can't find the source file based on a provided path", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
chai_1.expect(() => ast.getSourceFileOrThrow("some path")).to.throw();
});
it("should throw when it can't find the source file based on a provided condition", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
chai_1.expect(() => ast.getSourceFileOrThrow(s => false)).to.throw();
});
it("should not throw when it finds the file", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
ast.addSourceFileFromText("myFile.ts", "");
chai_1.expect(ast.getSourceFileOrThrow("myFile.ts").getFilePath()).to.contain("myFile.ts");
});
});
describe("getSourceFiles", () => {
it("should get all the source files added to the ast", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
ast.addSourceFileFromText("file1.ts", "");
ast.addSourceFileFromText("file2.ts", "");
chai_1.expect(ast.getSourceFiles().map(s => s.getFilePath())).to.deep.equal([
utils_1.FileUtils.getStandardizedAbsolutePath("file1.ts"),
utils_1.FileUtils.getStandardizedAbsolutePath("file2.ts")
]);
});
it("should be able to do a file glob", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
ast.addSourceFileFromText("file.ts", "");
ast.addSourceFileFromText("src/file.ts", "");
ast.addSourceFileFromText("src/test/file1.ts", "");
ast.addSourceFileFromText("src/test/file2.ts", "");
ast.addSourceFileFromText("src/test/file3.ts", "");
ast.addSourceFileFromText("src/test/file3.js", "");
ast.addSourceFileFromText("src/test/folder/file.ts", "");
chai_1.expect(ast.getSourceFiles("**/src/test/**/*.ts").map(s => s.getFilePath())).to.deep.equal([
utils_1.FileUtils.getStandardizedAbsolutePath("src/test/file1.ts"),
utils_1.FileUtils.getStandardizedAbsolutePath("src/test/file2.ts"),
utils_1.FileUtils.getStandardizedAbsolutePath("src/test/file3.ts"),
utils_1.FileUtils.getStandardizedAbsolutePath("src/test/folder/file.ts")
]);
});
});
describe("manipulating then getting something from the type checker", () => {
it("should not error after manipulation", () => {
const ast = new TsSimpleAst_1.TsSimpleAst();
const sourceFile = ast.addSourceFileFromText("myFile.ts", `function myFunction(param: string) {}`);
const param = sourceFile.getFunctions()[0].getParameters()[0];
chai_1.expect(param.getType().getText()).to.equal("string");
param.setType("number");
chai_1.expect(param.getType().getText()).to.equal("number");
});
});
});
//# sourceMappingURL=tsSimpleAstTests.js.map