Skip to content

Commit

Permalink
feat: #603 - Make emit async and add new emitSync
Browse files Browse the repository at this point in the history
Also fix `emitBOM`.
  • Loading branch information
dsherret committed Apr 5, 2019
1 parent 667e5a5 commit 8a8c1c7
Show file tree
Hide file tree
Showing 15 changed files with 310 additions and 98 deletions.
6 changes: 6 additions & 0 deletions breaking-changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ when using the specific methods:
sourceFile.addClass({ name: "MyClass" }); // ok
```

### `.emit()` methods are now async

Methods like `project.emit()` are now async. Corresponding `emitSync` methods have also been added.

The new async emit methods should be much faster than the previous synchronous methods.

### FileSystemHost changes

The `FileSystemHost` interface now requires implementing `realpathSync()` and `isCaseSensitive()`.
Expand Down
15 changes: 12 additions & 3 deletions docs/emitting.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,10 @@ Here's an example:
```ts
const project = new Project({ compilerOptions: { outDir: "dist", declaration: true } });
project.createSourceFile("MyFile.ts", "const num = 1;");
project.emit();
project.emit(); // async

// or
project.emitSync(); // slow
```

This outputs two files in the `dist` folder:
Expand All @@ -30,7 +33,10 @@ Call `.emit()` on the source file:

```ts
const sourceFile = project.getSourceFileOrThrow("MyFile.ts");
sourceFile.emit();
sourceFile.emit(); // async

// or
sourceFile.emitSync(); // slow
```

Or get its emit output:
Expand Down Expand Up @@ -58,7 +64,7 @@ project.emit({ emitOnlyDtsFiles: true });
Diagnostics about the emit can be found on the result:

```ts
const emitResult = project.emit();
const emitResult = await project.emit();
for (const diagnostic of emitResult.getDiagnostics())
console.log(diagnostic.getMessageText());
```
Expand Down Expand Up @@ -115,6 +121,9 @@ for (const file of result.getFiles()) {
console.log(file.text);
console.log("\n");
}

// or finally save this result to the underlying file system (or use `saveFilesSync()`)
result.saveFiles().then(() => console.log("written"));
```

To manipulate after emitting, you may load the result into a new project and manipulate that:
Expand Down
94 changes: 59 additions & 35 deletions lib/ts-morph.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -539,10 +539,15 @@ export declare class Project {
*/
getFileSystem(): FileSystemHost;
/**
* Emits all the source files.
* Asynchronously emits all the source files to the file system as JavaScript files.
* @param emitOptions - Optional emit options.
*/
emit(emitOptions?: EmitOptions): EmitResult;
/**
* Synchronously emits all the source files to the file system as JavaScript files.
* @param emitOptions - Optional emit options.
*/
emitSync(emitOptions?: EmitOptions): EmitResult;
/**
* Emits all the source files to memory.
* @param emitOptions - Optional emit options.
Expand Down Expand Up @@ -7607,9 +7612,13 @@ export declare class SourceFile extends SourceFileBase<ts.SourceFile> {
*/
indent(positionRange: [number, number], times?: number): this;
/**
* Emits the source file.
* Asynchronously emits the source file as a JavaScript file.
*/
emit(options?: SourceFileEmitOptions): Promise<EmitResult>;
/**
* Synchronously emits the source file as a JavaScript file.
*/
emit(options?: SourceFileEmitOptions): EmitResult;
emitSync(options?: SourceFileEmitOptions): EmitResult;
/**
* Gets the emit output of this source file.
* @param options - Emit options.
Expand Down Expand Up @@ -9125,10 +9134,16 @@ export declare class Program {
*/
getTypeChecker(): TypeChecker;
/**
* Emits the TypeScript files to JavaScript files.
* Asynchronously emits the TypeScript files as JavaScript files.
* @param options - Options for emitting.
*/
emit(options?: ProgramEmitOptions): EmitResult;
emit(options?: ProgramEmitOptions): Promise<EmitResult>;
/**
* Synchronously emits the TypeScript files as JavaScript files.
* @param options - Options for emitting.
* @remarks Use `emit()` as the asynchronous version will be much faster.
*/
emitSync(options?: ProgramEmitOptions): EmitResult;
/**
* Emits the TypeScript files to JavaScript files to memory.
* @param options - Options for emitting.
Expand Down Expand Up @@ -9406,36 +9421,6 @@ 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;
private constructor();
/**
* Gets the files that were emitted to memory.
*/
getFiles(): MemoryEmitResultFile[];
}

export interface ApplyFileTextChangesOptions {
/** If a file should be overwritten when the file text change is for a new file, but the file currently exists. */
overwrite?: boolean;
Expand Down Expand Up @@ -9480,6 +9465,45 @@ export declare class ImplementationLocation extends DocumentSpan<ts.Implementati
getDisplayParts(): SymbolDisplayPart[];
}

/**
* 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;
private constructor();
/**
* Gets the files that were emitted to memory.
*/
getFiles(): MemoryEmitResultFile[];
/**
* Asynchronously writes the files to the file system.
*/
saveFiles(): Promise<void[]>;
/**
* Synchronously writes the files to the file system.
* @remarks Use `saveFiles()` as the asynchronous version will be much faster.
*/
saveFilesSync(): void;
}

/**
* Output file of an emit.
*/
Expand Down
12 changes: 10 additions & 2 deletions src/Project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -510,13 +510,21 @@ export class Project {
}

/**
* Emits all the source files.
* Asynchronously emits all the source files to the file system as JavaScript files.
* @param emitOptions - Optional emit options.
*/
emit(emitOptions: EmitOptions = {}): EmitResult {
emit(emitOptions: EmitOptions = {}): Promise<EmitResult> {
return this._context.program.emit(emitOptions);
}

/**
* Synchronously emits all the source files to the file system as JavaScript files.
* @param emitOptions - Optional emit options.
*/
emitSync(emitOptions: EmitOptions = {}): EmitResult {
return this._context.program.emitSync(emitOptions);
}

/**
* Emits all the source files to memory.
* @param emitOptions - Optional emit options.
Expand Down
11 changes: 9 additions & 2 deletions src/compiler/ast/module/SourceFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -628,12 +628,19 @@ export class SourceFile extends SourceFileBase<ts.SourceFile> {
}

/**
* Emits the source file.
* Asynchronously emits the source file as a JavaScript file.
*/
emit(options?: SourceFileEmitOptions): EmitResult {
emit(options?: SourceFileEmitOptions): Promise<EmitResult> {
return this._context.program.emit({ targetSourceFile: this, ...options });
}

/**
* Synchronously emits the source file as a JavaScript file.
*/
emitSync(options?: SourceFileEmitOptions): EmitResult {
return this._context.program.emitSync({ targetSourceFile: this, ...options });
}

/**
* Gets the emit output of this source file.
* @param options - Emit options.
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/tools/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ export class LanguageService {
// getDefaultLibLocation: (...) => {},
getDefaultLibFileName: (options: CompilerOptions) => languageServiceHost.getDefaultLibFileName(options),
writeFile: (filePath, data, writeByteOrderMark, onError, sourceFiles) => {
this._context.fileSystemWrapper.writeFileSync(filePath, data);
this._context.fileSystemWrapper.writeFileSync(filePath, writeByteOrderMark ? "\uFEFF" + data : data);
},
getCurrentDirectory: () => languageServiceHost.getCurrentDirectory(),
getDirectories: (path: string) => this._context.fileSystemWrapper.getDirectories(path),
Expand Down
28 changes: 26 additions & 2 deletions src/compiler/tools/Program.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import * as errors from "../../errors";
import { ProjectContext } from "../../ProjectContext";
import { ModuleResolutionKind, ts } from "../../typescript";
import * as tsInternal from "../../typescript/tsInternal";
Expand Down Expand Up @@ -103,10 +104,33 @@ export class Program {
}

/**
* Emits the TypeScript files to JavaScript files.
* Asynchronously emits the TypeScript files as JavaScript files.
* @param options - Options for emitting.
*/
emit(options: ProgramEmitOptions = {}) {
async emit(options: ProgramEmitOptions = {}) {
if (options.writeFile) {
const message = `Cannot specify a ${nameof(options.writeFile)} option when emitting asynchrously. `
+ `Use ${nameof(this.emitSync)}() instead.`;
throw new errors.InvalidOperationError(message);
}

const { fileSystemWrapper } = this._context;
const promises: Promise<void>[] = [];
const emitResult = this._emit({
writeFile: (filePath, text, writeByteOrderMark) => {
promises.push(fileSystemWrapper.writeFile(filePath, writeByteOrderMark ? "\uFEFF" + text : text));
}, ...options
});
await Promise.all(promises);
return new EmitResult(this._context, emitResult);
}

/**
* Synchronously emits the TypeScript files as JavaScript files.
* @param options - Options for emitting.
* @remarks Use `emit()` as the asynchronous version will be much faster.
*/
emitSync(options: ProgramEmitOptions = {}) {
return new EmitResult(this._context, this._emit(options));
}

Expand Down
41 changes: 2 additions & 39 deletions src/compiler/tools/results/EmitResult.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ import { Memoize } from "../../../utils";
*/
export class EmitResult {
/** @internal */
private readonly _context: ProjectContext;
protected readonly _context: ProjectContext;
/** @internal */
private readonly _compilerObject: ts.EmitResult;
protected readonly _compilerObject: ts.EmitResult;

/**
* @private
Expand Down Expand Up @@ -55,40 +55,3 @@ 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 {
/**
* @private
*/
constructor(context: ProjectContext, compilerObject: ts.EmitResult, private readonly _files: ReadonlyArray<MemoryEmitResultFile>) {
super(context, compilerObject);
}

/**
* Gets the files that were emitted to memory.
*/
getFiles() {
return this._files as MemoryEmitResultFile[]; // assert mutable array
}
}
59 changes: 59 additions & 0 deletions src/compiler/tools/results/MemoryEmitResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { ProjectContext } from "../../../ProjectContext";
import { ts } from "../../../typescript";
import { EmitResult } from "./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 {
/**
* @private
*/
constructor(context: ProjectContext, compilerObject: ts.EmitResult, private readonly _files: ReadonlyArray<MemoryEmitResultFile>) {
super(context, compilerObject);
}

/**
* Gets the files that were emitted to memory.
*/
getFiles() {
return this._files as MemoryEmitResultFile[]; // assert mutable array
}

/**
* Asynchronously writes the files to the file system.
*/
saveFiles() {
const fileSystem = this._context.fileSystemWrapper;
const promises = this._files.map(f => fileSystem.writeFile(f.filePath, f.writeByteOrderMark ? "\uFEFF" + f.text : f.text));
return Promise.all(promises);
}

/**
* Synchronously writes the files to the file system.
* @remarks Use `saveFiles()` as the asynchronous version will be much faster.
*/
saveFilesSync() {
const fileSystem = this._context.fileSystemWrapper;
for (const file of this._files)
fileSystem.writeFileSync(file.filePath, file.writeByteOrderMark ? "\uFEFF" + file.text : file.text);
}
}

0 comments on commit 8a8c1c7

Please sign in to comment.