UNPKG

ts-simple-ast

Version:

TypeScript compiler wrapper for AST navigation and code generation.

284 lines (282 loc) 16.8 kB
"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