UNPKG

ts-simple-ast

Version:

TypeScript compiler wrapper for AST navigation and code generation.

435 lines (433 loc) 24.7 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 chai_1 = require("chai"); const ts = require("typescript"); const compiler_1 = require("./../../../compiler"); const testHelpers_1 = require("./../testHelpers"); const testHelpers_2 = require("./../../testHelpers"); const TsSimpleAst_1 = require("./../../../TsSimpleAst"); const utils_1 = require("./../../../utils"); describe("SourceFile", () => { describe("copy", () => { const fileText = " interface Identifier {} "; const { sourceFile, tsSimpleAst } = testHelpers_1.getInfoFromText(fileText, { filePath: "Folder/File.ts" }); const relativeSourceFile = sourceFile.copy("../NewFolder/NewFile.ts"); const absoluteSourceFile = sourceFile.copy(utils_1.FileUtils.getStandardizedAbsolutePath("NewFile.ts")); describe("tsSimpleAst", () => { it("should include the copied source files", () => { chai_1.expect(tsSimpleAst.getSourceFiles().length).to.equal(3); }); }); describe("relative source file", () => { it("should not be saved", () => { chai_1.expect(relativeSourceFile.isSaved()).to.be.false; }); it("should have have the same text", () => { chai_1.expect(relativeSourceFile.getFullText()).to.equal(fileText); }); it("should have the expected path", () => { chai_1.expect(relativeSourceFile.getFilePath()).to.equal(utils_1.FileUtils.getStandardizedAbsolutePath("NewFolder/NewFile.ts")); }); }); describe("absolute source file", () => { it("should not be saved", () => { chai_1.expect(absoluteSourceFile.isSaved()).to.be.false; }); it("should have have the same text", () => { chai_1.expect(absoluteSourceFile.getFullText()).to.equal(fileText); }); it("should have the expected path", () => { chai_1.expect(absoluteSourceFile.getFilePath()).to.equal(utils_1.FileUtils.getStandardizedAbsolutePath("NewFile.ts")); }); }); }); describe("save", () => { const fileText = " interface Identifier {} "; const filePath = utils_1.FileUtils.getStandardizedAbsolutePath("/Folder/File.ts"); const host = testHelpers_2.getFileSystemHostWithFiles([]); const { sourceFile } = testHelpers_1.getInfoFromText(fileText, { filePath, host }); it("should save the file", () => __awaiter(this, void 0, void 0, function* () { chai_1.expect(sourceFile.isSaved()).to.be.false; yield sourceFile.save(); chai_1.expect(sourceFile.isSaved()).to.be.true; const writeLog = host.getWriteLog(); const entry = writeLog[0]; chai_1.expect(entry.filePath).to.equal(filePath); chai_1.expect(entry.fileText).to.equal(fileText); chai_1.expect(writeLog.length).to.equal(1); chai_1.expect(host.getCreatedDirectories()).to.deep.equal([utils_1.FileUtils.getDirPath(filePath)]); })); }); describe("isSaved", () => { const filePath = utils_1.FileUtils.getStandardizedAbsolutePath("/Folder/File.ts"); it("should not be saved after doing an action that will replace the tree", () => { const host = testHelpers_2.getFileSystemHostWithFiles([]); const { sourceFile } = testHelpers_1.getInfoFromText("class MyClass {}", { filePath, host }); chai_1.expect(sourceFile.isSaved()).to.be.false; sourceFile.saveSync(); chai_1.expect(sourceFile.isSaved()).to.be.true; sourceFile.addClass({ name: "NewClass" }); chai_1.expect(sourceFile.isSaved()).to.be.false; }); it("should not be saved after doing an action that changes only the text", () => { const host = testHelpers_2.getFileSystemHostWithFiles([]); const { sourceFile } = testHelpers_1.getInfoFromText("class MyClass {}", { filePath, host }); chai_1.expect(sourceFile.isSaved()).to.be.false; sourceFile.saveSync(); chai_1.expect(sourceFile.isSaved()).to.be.true; sourceFile.getClasses()[0].rename("NewClassName"); chai_1.expect(sourceFile.isSaved()).to.be.false; }); }); describe("saveSync", () => { const fileText = " interface Identifier {} "; const filePath = utils_1.FileUtils.getStandardizedAbsolutePath("/Folder/File.ts"); const host = testHelpers_2.getFileSystemHostWithFiles([]); const { sourceFile } = testHelpers_1.getInfoFromText(fileText, { filePath, host }); it("should save the file", () => { chai_1.expect(sourceFile.isSaved()).to.be.false; sourceFile.saveSync(); chai_1.expect(sourceFile.isSaved()).to.be.true; const writeLog = host.getSyncWriteLog(); const entry = writeLog[0]; chai_1.expect(entry.filePath).to.equal(filePath); chai_1.expect(entry.fileText).to.equal(fileText); chai_1.expect(writeLog.length).to.equal(1); chai_1.expect(host.getCreatedDirectories()).to.deep.equal([utils_1.FileUtils.getDirPath(filePath)]); }); }); describe("isDeclarationFile", () => { it("should be a source file when the file name ends with .d.ts", () => { const ast = new TsSimpleAst_1.TsSimpleAst(); const sourceFile = ast.addSourceFileFromText("MyFile.d.ts", ""); chai_1.expect(sourceFile.isDeclarationFile()).to.be.true; }); it("should not be a source file when the file name ends with .ts", () => { const ast = new TsSimpleAst_1.TsSimpleAst(); const sourceFile = ast.addSourceFileFromText("MyFile.ts", ""); chai_1.expect(sourceFile.isDeclarationFile()).to.be.false; }); }); describe("insertImports", () => { function doTest(startCode, index, structures, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.insertImports(index, structures); chai_1.expect(result.length).to.equal(structures.length); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should insert the different kinds of imports", () => { doTest("", 0, [ { moduleSpecifier: "./test" }, { defaultImport: "identifier", moduleSpecifier: "./test" }, { defaultImport: "identifier", namespaceImport: "name", moduleSpecifier: "./test" }, { defaultImport: "identifier", namedImports: [{ name: "name" }, { name: "name", alias: "alias" }], moduleSpecifier: "./test" }, { namedImports: [{ name: "name" }], moduleSpecifier: "./test" }, { namespaceImport: "name", moduleSpecifier: "./test" } ], [ `import "./test";`, `import identifier from "./test";`, `import identifier, * as name from "./test";`, `import identifier, {name, name as alias} from "./test";`, `import {name} from "./test";`, `import * as name from "./test";` ].join("\n") + "\n"); }); it("should throw when specifying a namespace import and named imports", () => { const { sourceFile } = testHelpers_1.getInfoFromText(""); chai_1.expect(() => { sourceFile.insertImports(0, [{ namespaceImport: "name", namedImports: [{ name: "name" }], moduleSpecifier: "file" }]); }).to.throw(); }); it("should insert at the beginning", () => { doTest(`export class Class {}\n`, 0, [{ moduleSpecifier: "./test" }], `import "./test";\n\nexport class Class {}\n`); }); it("should insert in the middle", () => { doTest(`import "./file1";\nimport "./file3";\n`, 1, [{ moduleSpecifier: "./file2" }], `import "./file1";\nimport "./file2";\nimport "./file3";\n`); }); it("should insert at the end", () => { doTest(`export class Class {}\n`, 1, [{ moduleSpecifier: "./test" }], `export class Class {}\n\nimport "./test";\n`); }); }); describe("insertImport", () => { function doTest(startCode, index, structure, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.insertImport(index, structure); chai_1.expect(result).to.be.instanceOf(compiler_1.ImportDeclaration); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should insert at the specified position", () => { doTest(`import "./file1";\nimport "./file3";\n`, 1, { moduleSpecifier: "./file2" }, `import "./file1";\nimport "./file2";\nimport "./file3";\n`); }); }); describe("addImport", () => { function doTest(startCode, structure, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.addImport(structure); chai_1.expect(result).to.be.instanceOf(compiler_1.ImportDeclaration); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should add at the last import if one exists", () => { doTest(`import "./file1";\nimport "./file2";\n\nexport class MyClass {}\n`, { moduleSpecifier: "./file3" }, `import "./file1";\nimport "./file2";\nimport "./file3";\n\nexport class MyClass {}\n`); }); it("should add at the start if no imports exists", () => { doTest(`export class MyClass {}\n`, { moduleSpecifier: "./file" }, `import "./file";\n\nexport class MyClass {}\n`); }); }); describe("addImports", () => { function doTest(startCode, structures, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.addImports(structures); chai_1.expect(result.length).to.equal(structures.length); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should add at the last import if one exists", () => { doTest(`import "./file1";\n\nexport class MyClass {}\n`, [{ moduleSpecifier: "./file2" }, { moduleSpecifier: "./file3" }], `import "./file1";\nimport "./file2";\nimport "./file3";\n\nexport class MyClass {}\n`); }); it("should add at the start if no imports exists", () => { doTest(`export class MyClass {}\n`, [{ moduleSpecifier: "./file1" }, { moduleSpecifier: "./file2" }], `import "./file1";\nimport "./file2";\n\nexport class MyClass {}\n`); }); }); describe("getImports", () => { it("should get the import declarations", () => { const { sourceFile } = testHelpers_1.getInfoFromText("import myImport from 'test'; import {next} from './test';"); chai_1.expect(sourceFile.getImports().length).to.equal(2); chai_1.expect(sourceFile.getImports()[0]).to.be.instanceOf(compiler_1.ImportDeclaration); }); }); describe("getImport", () => { it("should get the import declaration", () => { const { sourceFile } = testHelpers_1.getInfoFromText("import myImport from 'test'; import {next} from './test';"); chai_1.expect(sourceFile.getImport(i => i.getDefaultImport() != null).getText()).to.equal("import myImport from 'test';"); }); }); describe("getImportOrThrow", () => { it("should get the import declaration", () => { const { sourceFile } = testHelpers_1.getInfoFromText("import myImport from 'test'; import {next} from './test';"); chai_1.expect(sourceFile.getImportOrThrow(i => i.getDefaultImport() != null).getText()).to.equal("import myImport from 'test';"); }); it("should throw when not exists", () => { const { sourceFile } = testHelpers_1.getInfoFromText(""); chai_1.expect(() => sourceFile.getImportOrThrow(e => false)).to.throw(); }); }); describe("insertExports", () => { function doTest(startCode, index, structures, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.insertExports(index, structures); chai_1.expect(result.length).to.equal(structures.length); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should insert the different kinds of exports", () => { doTest("", 0, [ { moduleSpecifier: "./test" }, { namedExports: [{ name: "name" }, { name: "name", alias: "alias" }], moduleSpecifier: "./test" }, { namedExports: [{ name: "name" }] }, {} ], [ `export * from "./test";`, `export {name, name as alias} from "./test";`, `export {name};`, `export {};` ].join("\n") + "\n"); }); it("should insert at the beginning", () => { doTest(`export class Class {}\n`, 0, [{ moduleSpecifier: "./test" }], `export * from "./test";\n\nexport class Class {}\n`); }); it("should insert in the middle", () => { doTest(`export * from "./file1";\nexport * from "./file3";\n`, 1, [{ moduleSpecifier: "./file2" }], `export * from "./file1";\nexport * from "./file2";\nexport * from "./file3";\n`); }); it("should insert at the end", () => { doTest(`export class Class {}\n`, 1, [{ moduleSpecifier: "./test" }], `export class Class {}\n\nexport * from "./test";\n`); }); }); describe("insertExport", () => { function doTest(startCode, index, structure, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.insertExport(index, structure); chai_1.expect(result).to.be.instanceOf(compiler_1.ExportDeclaration); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should insert at the specified position", () => { doTest(`export * from "./file1";\nexport * from "./file3";\n`, 1, { moduleSpecifier: "./file2" }, `export * from "./file1";\nexport * from "./file2";\nexport * from "./file3";\n`); }); }); describe("addExport", () => { function doTest(startCode, structure, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.addExport(structure); chai_1.expect(result).to.be.instanceOf(compiler_1.ExportDeclaration); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should always add at the end of the file", () => { doTest(`export class MyClass {}\n`, { moduleSpecifier: "./file" }, `export class MyClass {}\n\nexport * from "./file";\n`); }); }); describe("addExports", () => { function doTest(startCode, structures, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startCode); const result = sourceFile.addExports(structures); chai_1.expect(result.length).to.equal(structures.length); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should add multiple", () => { doTest(`export class MyClass {}\n`, [{ moduleSpecifier: "./file1" }, { moduleSpecifier: "./file2" }], `export class MyClass {}\n\nexport * from "./file1";\nexport * from "./file2";\n`); }); }); describe("getExports", () => { it("should get the export declarations", () => { const { sourceFile } = testHelpers_1.getInfoFromText("export * from 'test'; export {next} from './test';"); chai_1.expect(sourceFile.getExports().length).to.equal(2); chai_1.expect(sourceFile.getExports()[0]).to.be.instanceOf(compiler_1.ExportDeclaration); }); }); describe("getExport", () => { it("should get the export declaration", () => { const { sourceFile } = testHelpers_1.getInfoFromText("export * from 'test'; export {next} from './test';"); chai_1.expect(sourceFile.getExport(e => e.isNamespaceExport()).getText()).to.equal("export * from 'test';"); }); }); describe("getExportOrThrow", () => { it("should get the export declaration", () => { const { sourceFile } = testHelpers_1.getInfoFromText("export * from 'test'; export {next} from './test';"); chai_1.expect(sourceFile.getExportOrThrow(e => e.isNamespaceExport()).getText()).to.equal("export * from 'test';"); }); it("should throw when not exists", () => { const { sourceFile } = testHelpers_1.getInfoFromText(""); chai_1.expect(() => sourceFile.getExportOrThrow(e => false)).to.throw(); }); }); describe("getDefaultExportSymbol", () => { it("should return undefined when there's no default export", () => { const { sourceFile } = testHelpers_1.getInfoFromText(""); chai_1.expect(sourceFile.getDefaultExportSymbol()).to.be.undefined; }); it("should return the default export symbol when one exists", () => { const { sourceFile } = testHelpers_1.getInfoFromText("export default class Identifier {}"); const defaultExportSymbol = sourceFile.getDefaultExportSymbol(); chai_1.expect(defaultExportSymbol.getName()).to.equal("default"); }); it("should return the default export symbol when default exported on a separate statement", () => { const { sourceFile } = testHelpers_1.getInfoFromText("class Identifier {}\nexport default Identifier;"); const defaultExportSymbol = sourceFile.getDefaultExportSymbol(); chai_1.expect(defaultExportSymbol.getName()).to.equal("default"); }); }); describe("getDefaultExportSymbolOrThrow", () => { it("should throw when there's no default export", () => { const { sourceFile } = testHelpers_1.getInfoFromText(""); chai_1.expect(() => sourceFile.getDefaultExportSymbolOrThrow()).to.throw(); }); it("should return the default export symbol when one exists", () => { const { sourceFile } = testHelpers_1.getInfoFromText("export default class Identifier {}"); chai_1.expect(sourceFile.getDefaultExportSymbolOrThrow().getName()).to.equal("default"); }); }); describe("removeDefaultExport", () => { it("should do nothing when there's no default export", () => { const { sourceFile } = testHelpers_1.getInfoFromText(""); sourceFile.removeDefaultExport(); chai_1.expect(sourceFile.getFullText()).to.equal(""); }); it("should return the default export symbol when one exists", () => { const { sourceFile } = testHelpers_1.getInfoFromText("export default class Identifier {}"); sourceFile.removeDefaultExport(); chai_1.expect(sourceFile.getFullText()).to.equal("class Identifier {}"); }); it("should return the default export symbol when default exported on a separate statement", () => { const { sourceFile } = testHelpers_1.getInfoFromText("namespace Identifier {}\nclass Identifier {}\nexport default Identifier;\n"); sourceFile.removeDefaultExport(); chai_1.expect(sourceFile.getFullText()).to.equal("namespace Identifier {}\nclass Identifier {}\n"); }); }); describe("getLanguageVariant", () => { it("should return standard when in a ts file", () => { const { sourceFile } = testHelpers_1.getInfoFromText(""); chai_1.expect(sourceFile.getLanguageVariant()).to.equal(ts.LanguageVariant.Standard); }); it("should return jsx when in a tsx file", () => { const { sourceFile } = testHelpers_1.getInfoFromText("", { filePath: "file.tsx" }); chai_1.expect(sourceFile.getLanguageVariant()).to.equal(ts.LanguageVariant.JSX); }); }); describe("emit", () => { it("should emit the source file", () => { const fileSystem = testHelpers_2.getFileSystemHostWithFiles([]); const ast = new TsSimpleAst_1.TsSimpleAst({ compilerOptions: { noLib: true, outDir: "dist" } }, fileSystem); const sourceFile = ast.addSourceFileFromText("file1.ts", "const num1 = 1;"); ast.addSourceFileFromText("file2.ts", "const num2 = 2;"); const result = sourceFile.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.length).to.equal(1); }); it("should create the directories when emitting", () => { const fileSystem = testHelpers_2.getFileSystemHostWithFiles([]); const ast = new TsSimpleAst_1.TsSimpleAst({ compilerOptions: { noLib: true, outDir: "dist/subdir" } }, fileSystem); const sourceFile = ast.addSourceFileFromText("/test.ts", "const num1 = 1;"); ast.emit(); const directories = fileSystem.getCreatedDirectories(); chai_1.expect(directories).to.deep.equal(["dist", "dist/subdir"]); }); }); describe("fill", () => { function doTest(startingCode, structure, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startingCode); sourceFile.fill(structure); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should not modify anything if the structure doesn't change anything", () => { doTest("", {}, ""); }); it("should modify when changed", () => { const structure = { imports: [{ moduleSpecifier: "module" }], exports: [{ moduleSpecifier: "export-module" }] }; doTest("", structure, `import "module";\n\nexport * from "export-module";\n`); }); }); describe("formatText", () => { function doTest(startingCode, expectedCode) { const { sourceFile } = testHelpers_1.getInfoFromText(startingCode); sourceFile.formatText(); chai_1.expect(sourceFile.getText()).to.equal(expectedCode); } it("should format the text when it contains different spacing", () => { doTest("class MyClass{}", "class MyClass {\n}\n"); }); it("should format the text when it contains multiple semi colons", () => { doTest("var myTest: string;;;;", "var myTest: string;\n;\n;\n;\n"); }); it("should format the text when it contains syntax errors", () => { // wow, it's impressive it does this doTest("function myTest(}{{{{}}) {}", "function myTest({}, {}, {}, {}) { }\n"); }); it("should format the text in the documentation as described", () => { doTest(`var myVariable : string | number; function myFunction(param : MyClass){ return ""; } `, `var myVariable: string | number; function myFunction(param: MyClass) { return ""; } `); }); it("should dispose of any previous descendants", () => { const { sourceFile, firstChild } = testHelpers_1.getInfoFromText("function test {}"); sourceFile.formatText(); chai_1.expect(() => firstChild.compilerNode).to.throw(); }); }); }); //# sourceMappingURL=sourceFileTests.js.map