Skip to content

Commit

Permalink
feat: #126 - Ability to emit to memory.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Aug 26, 2018
1 parent 644eba5 commit 4f6fb5a
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 23 deletions.
40 changes: 40 additions & 0 deletions docs/emitting.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,43 @@ function numericLiteralToStringLiteral(node: ts.Node) {
return node;
}
```

## Emitting to Memory

If you don't want to emit to the file system, you can call `emitToMemory()`:

```ts
const project = new Project({ compilerOptions: { outDir: "dist" } });
project.createSourceFile("MyFile.ts", "const num = 1;");
const result = project.emitToMemory();

// output the emitted files to the console
for (const file of result.getFiles()) {
console.log("----");
console.log(file.filePath);
console.log("----");
console.log(file.text);
console.log("\n");
}
```

To manipulate after emitting, you may load the result into a new project and manipulate that:

```ts
const project = new Project({ compilerOptions: { outDir: "dist" } });
project.createSourceFile("MyFile.ts", "const num = 1;");
const result = project.emitToMemory();

// load the javascript files into a new project
const newProject = new Project();
for (const file of result.getFiles()) {
newProject.createSourceFile(file.filePath, file.text, { overwrite: true });
}

// ...manipulate the javascript files here...

// save the new files to the file system
newProject.save();
```

...but consider using the custom transformers discussed above if you want it to be faster.
53 changes: 50 additions & 3 deletions lib/ts-simple-ast.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -529,6 +529,11 @@ export declare class Project {
* @param emitOptions - Optional emit options.
*/
emit(emitOptions?: EmitOptions): EmitResult;
/**
* Emits all the source files to memory.
* @param emitOptions - Optional emit options.
*/
emitToMemory(emitOptions?: EmitOptions): MemoryEmitResult;
/**
* Gets the compiler options.
*/
Expand Down Expand Up @@ -8149,6 +8154,13 @@ export declare class LanguageService {
private _getFilledUserPreferences;
}

/**
* Options for emitting from a Program.
*/
export interface ProgramEmitOptions extends EmitOptions {
writeFile?: ts.WriteFileCallback;
}

/**
* Options for emitting.
*/
Expand Down Expand Up @@ -8183,9 +8195,15 @@ export declare class Program {
*/
getTypeChecker(): TypeChecker;
/**
* Emits the TypeScript files to the specified target.
* Emits the TypeScript files to JavaScript files.
* @param options - Options for emitting.
*/
emit(options?: ProgramEmitOptions): EmitResult;
/**
* Emits the TypeScript files to JavaScript files to memory.
* @param options - Options for emitting.
*/
emit(options?: EmitOptions): EmitResult;
emitToMemory(options?: EmitOptions): MemoryEmitResult;
/**
* Gets the syntactic diagnostics.
* @param sourceFile - Optional source file to filter by.
Expand Down Expand Up @@ -8396,6 +8414,35 @@ export declare class EmitResult {
getDiagnostics(): Diagnostic<ts.Diagnostic>[];
}

/**
* The emitted file in memory.
*/
export interface MemoryEmitResultFile {
/**
* File path that was emitted to.
*/
filePath: string;
/**
* The text that was emitted.
*/
text: string;
/**
* Whether the byte order mark should be written.
*/
writeByteOrderMark: boolean;
}

/**
* Result of an emit to memory.
*/
export declare class MemoryEmitResult extends EmitResult {
private readonly files;
/**
* Gets the files that were emitted to memory.
*/
getFiles(): MemoryEmitResultFile[];
}

export declare class FileTextChanges {
/**
* Gets the file path.
Expand Down Expand Up @@ -8423,7 +8470,7 @@ export declare class ImplementationLocation extends DocumentSpan<ts.Implementati
*/
export declare class OutputFile {
/**
* TypeScript compiler emit result.
* TypeScript compiler output file.
*/
readonly compilerObject: ts.OutputFile;
/**
Expand Down
8 changes: 8 additions & 0 deletions src/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,14 @@ export class Project {
return this.context.program.emit(emitOptions);
}

/**
* Emits all the source files to memory.
* @param emitOptions - Optional emit options.
*/
emitToMemory(emitOptions: EmitOptions = {}) {
return this.context.program.emitToMemory(emitOptions);
}

/**
* Gets the compiler options.
*/
Expand Down
43 changes: 37 additions & 6 deletions src/compiler/tools/Program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,16 @@ import { ProjectContext } from "../../ProjectContext";
import { ModuleResolutionKind, ts } from "../../typescript";
import * as tsInternal from "../../typescript/tsInternal";
import { SourceFile } from "../file";
import { Diagnostic, DiagnosticWithLocation, EmitResult } from "./results";
import { Diagnostic, DiagnosticWithLocation, EmitResult, MemoryEmitResult, MemoryEmitResultFile } from "./results";
import { TypeChecker } from "./TypeChecker";

/**
* Options for emitting from a Program.
*/
export interface ProgramEmitOptions extends EmitOptions {
writeFile?: ts.WriteFileCallback;
}

/**
* Options for emitting.
*/
Expand Down Expand Up @@ -80,14 +87,38 @@ export class Program {
}

/**
* Emits the TypeScript files to the specified target.
* Emits the TypeScript files to JavaScript files.
* @param options - Options for emitting.
*/
emit(options: EmitOptions = {}) {
emit(options: ProgramEmitOptions = {}) {
return new EmitResult(this.context, this._emit(options));
}

/**
* Emits the TypeScript files to JavaScript files to memory.
* @param options - Options for emitting.
*/
emitToMemory(options: EmitOptions = {}) {
const sourceFiles: MemoryEmitResultFile[] = [];
const { fileSystemWrapper } = this.context;
const emitResult = this._emit({
writeFile: (filePath, text, writeByteOrderMark) => {
sourceFiles.push({
filePath: fileSystemWrapper.getStandardizedAbsolutePath(filePath),
text,
writeByteOrderMark: writeByteOrderMark || false
});
}, ...options
});
return new MemoryEmitResult(this.context, emitResult, sourceFiles);
}

/** @internal */
private _emit(options: ProgramEmitOptions = {}) {
const targetSourceFile = options.targetSourceFile != null ? options.targetSourceFile.compilerNode : undefined;
const { emitOnlyDtsFiles, customTransformers } = options;
const { emitOnlyDtsFiles, customTransformers, writeFile } = options;
const cancellationToken = undefined; // todo: expose this
const emitResult = this.compilerObject.emit(targetSourceFile, undefined, cancellationToken, emitOnlyDtsFiles, customTransformers);
return new EmitResult(this.context, emitResult);
return this.compilerObject.emit(targetSourceFile, writeFile, cancellationToken, emitOnlyDtsFiles, customTransformers);
}

/**
Expand Down
38 changes: 38 additions & 0 deletions src/compiler/tools/results/EmitResult.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { ProjectContext } from "../../../ProjectContext";
import { ts } from "../../../typescript";
import { Memoize } from "../../../utils";
import { OutputFile } from "./OutputFile";

/**
* Result of an emit.
Expand Down Expand Up @@ -49,3 +50,40 @@ export class EmitResult {
}
*/
}

/**
* The emitted file in memory.
*/
export interface MemoryEmitResultFile {
/**
* File path that was emitted to.
*/
filePath: string;
/**
* The text that was emitted.
*/
text: string;
/**
* Whether the byte order mark should be written.
*/
writeByteOrderMark: boolean;
}

/**
* Result of an emit to memory.
*/
export class MemoryEmitResult extends EmitResult {
/**
* @internal
*/
constructor(context: ProjectContext, compilerObject: ts.EmitResult, private readonly files: MemoryEmitResultFile[]) {
super(context, compilerObject);
}

/**
* Gets the files that were emitted to memory.
*/
getFiles() {
return this.files;
}
}
2 changes: 1 addition & 1 deletion src/compiler/tools/results/OutputFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export class OutputFile {
}

/**
* TypeScript compiler emit result.
* TypeScript compiler output file.
*/
get compilerObject() {
return this._compilerObject;
Expand Down
44 changes: 31 additions & 13 deletions src/tests/projectTests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect } from "chai";
import * as path from "path";
import { ClassDeclaration, EmitResult, InterfaceDeclaration, NamespaceDeclaration, Node, SourceFile } from "../compiler";
import { ClassDeclaration, EmitResult, MemoryEmitResult, InterfaceDeclaration, NamespaceDeclaration, Node, SourceFile } from "../compiler";
import * as errors from "../errors";
import { VirtualFileSystemHost } from "../fileSystem";
import { IndentationText } from "../options";
Expand Down Expand Up @@ -570,17 +570,17 @@ describe(nameof(Project), () => {
});
});

describe(nameof<Project>(project => project.emit), () => {
function setup(compilerOptions: CompilerOptions) {
const fileSystem = testHelpers.getFileSystemHostWithFiles([]);
const project = new Project({ compilerOptions }, fileSystem);
project.createSourceFile("file1.ts", "const num1 = 1;");
project.createSourceFile("file2.ts", "const num2 = 2;");
return {fileSystem, project};
}
function emitSetup(compilerOptions: CompilerOptions) {
const fileSystem = testHelpers.getFileSystemHostWithFiles([]);
const project = new Project({ compilerOptions }, fileSystem);
project.createSourceFile("file1.ts", "const num1 = 1;");
project.createSourceFile("file2.ts", "const num2 = 2;");
return {fileSystem, project};
}

describe(nameof<Project>(project => project.emit), () => {
it("should emit multiple files when not specifying any options", () => {
const {project, fileSystem} = setup({ noLib: true, outDir: "dist" });
const {project, fileSystem} = emitSetup({ noLib: true, outDir: "dist" });
const result = project.emit();
expect(result).to.be.instanceof(EmitResult);

Expand All @@ -593,7 +593,7 @@ describe(nameof(Project), () => {
});

it("should emit the source file when specified", () => {
const {project, fileSystem} = setup({ noLib: true, outDir: "dist" });
const {project, fileSystem} = emitSetup({ noLib: true, outDir: "dist" });
project.emit({ targetSourceFile: project.getSourceFile("file1.ts") });

const writeLog = fileSystem.getWriteLog();
Expand All @@ -603,7 +603,7 @@ describe(nameof(Project), () => {
});

it("should only emit the declaration file when specified", () => {
const {project, fileSystem} = setup({ noLib: true, outDir: "dist", declaration: true });
const {project, fileSystem} = emitSetup({ noLib: true, outDir: "dist", declaration: true });
project.emit({ emitOnlyDtsFiles: true });

const writeLog = fileSystem.getWriteLog();
Expand All @@ -615,7 +615,7 @@ describe(nameof(Project), () => {
});

it("should emit with custom transformations", () => {
const { project, fileSystem } = setup({ noLib: true, outDir: "dist" });
const { project, fileSystem } = emitSetup({ noLib: true, outDir: "dist" });

function visitSourceFile(sourceFile: ts.SourceFile, context: ts.TransformationContext, visitNode: (node: ts.Node) => ts.Node) {
return visitNodeAndChildren(sourceFile) as ts.SourceFile;
Expand Down Expand Up @@ -646,6 +646,24 @@ describe(nameof(Project), () => {
});
});

describe(nameof<Project>(project => project.emitToMemory), () => {
it("should emit multiple files to memory", () => {
const { project, fileSystem } = emitSetup({ noLib: true, outDir: "dist" });
const result = project.emitToMemory();
expect(result).to.be.instanceof(MemoryEmitResult);

const writeLog = fileSystem.getWriteLog();
expect(writeLog.length).to.equal(0);

const files = result.getFiles();
expect(files[0].filePath).to.equal("/dist/file1.js");
expect(files[0].text).to.equal("var num1 = 1;\n");
expect(files[1].filePath).to.equal("/dist/file2.js");
expect(files[1].text).to.equal("var num2 = 2;\n");
expect(files.length).to.equal(2);
});
});

describe(nameof<Project>(project => project.getSourceFile), () => {
it("should get the first match based on the directory structure", () => {
const project = new Project({ useVirtualFileSystem: true });
Expand Down

0 comments on commit 4f6fb5a

Please sign in to comment.