Skip to content

Commit

Permalink
feat: Emit a directory.
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Dec 23, 2017
1 parent a561994 commit 3cb455c
Show file tree
Hide file tree
Showing 11 changed files with 299 additions and 37 deletions.
15 changes: 12 additions & 3 deletions src/compiler/tools/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -243,12 +243,21 @@ export class LanguageService {
}

/**
* Gets the emit output of a file.
* Gets the emit output of a source file.
* @param sourceFile - Source file.
* @param emitOnlyDtsFiles - Whether to only emit the d.ts files.
*/
getEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles?: boolean): EmitOutput;
/**
* Gets the emit output of a source file.
* @param filePath - File path.
* @param emitOnlyDtsFiles - Whether to only emit the d.ts files.
*/
getEmitOutput(filePath: string, emitOnlyDtsFiles?: boolean): EmitOutput {
filePath = FileUtils.getStandardizedAbsolutePath(filePath);
getEmitOutput(filePath: string, emitOnlyDtsFiles?: boolean): EmitOutput;
/** @internal */
getEmitOutput(filePathOrSourceFile: SourceFile | string, emitOnlyDtsFiles?: boolean): EmitOutput;
getEmitOutput(filePathOrSourceFile: SourceFile | string, emitOnlyDtsFiles?: boolean): EmitOutput {
const filePath = typeof filePathOrSourceFile === "string" ? FileUtils.getStandardizedAbsolutePath(filePathOrSourceFile) : filePathOrSourceFile.getFilePath();
if (!this.global.compilerFactory.containsSourceFileAtPath(filePath))
throw new errors.FileNotFoundError(filePath);
return new EmitOutput(this.global, filePath, this.compilerObject.getEmitOutput(filePath, emitOnlyDtsFiles));
Expand Down
17 changes: 0 additions & 17 deletions src/compiler/tools/results/EmitOutput.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import {GlobalContainer} from "./../../../GlobalContainer";
import {Memoize} from "./../../../utils";
import {OutputFile} from "./OutputFile";
import {Diagnostic} from "./Diagnostic";

/**
* Output of an emit on a single file.
Expand All @@ -12,16 +11,13 @@ export class EmitOutput {
private readonly global: GlobalContainer;
/** @internal */
private readonly _compilerObject: ts.EmitOutput;
/** @internal */
private readonly _diagnostics: Diagnostic[];

/**
* @internal
*/
constructor(global: GlobalContainer, private readonly filePath: string, compilerObject: ts.EmitOutput) {
this.global = global;
this._compilerObject = compilerObject;
this._diagnostics = this.compilerObject.emitSkipped ? this._getPreEmitDiagnostics() : [];
}

/**
Expand All @@ -31,13 +27,6 @@ export class EmitOutput {
return this._compilerObject;
}

/**
* Gets the diagnostics when the emit is skipped.
*/
getDiagnostics() {
return this._diagnostics;
}

/**
* Gets if the emit was skipped.
*/
Expand All @@ -52,10 +41,4 @@ export class EmitOutput {
getOutputFiles() {
return this.compilerObject.outputFiles.map(f => new OutputFile(f));
}

private _getPreEmitDiagnostics() {
const sourceFile = this.global.compilerFactory.getSourceFileFromFilePath(this.filePath)!;
const compilerDiagnostics = ts.getPreEmitDiagnostics(this.global.program.compilerObject, sourceFile.compilerNode);
return compilerDiagnostics.map(d => new Diagnostic(this.global, d));
}
}
4 changes: 2 additions & 2 deletions src/compiler/tools/results/OutputFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ export class OutputFile {
}

/**
* Gets the file name.
* Gets the file path.
*/
getName() {
getFilePath() {
return this.compilerObject.name;
}

Expand Down
86 changes: 85 additions & 1 deletion src/fileSystem/Directory.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import {SourceFile} from "./../compiler";
import {SourceFile, OutputFile} from "./../compiler";
import * as errors from "./../errors";
import {ArrayUtils, FileUtils} from "./../utils";
import {SourceFileStructure} from "./../structures";
import {GlobalContainer} from "./../GlobalContainer";
import {DirectoryEmitResult} from "./DirectoryEmitResult";

export class Directory {
private _global: GlobalContainer | undefined;
Expand Down Expand Up @@ -258,6 +259,89 @@ export class Directory {
return this.global.compilerFactory.getSourceFileFromFilePath(filePath)!;
}

/**
* Emits the files in the directory.
* @param options - Options for emitting.
*/
async emit(options: { emitOnlyDtsFiles?: boolean; outDir?: string; declarationDir?: string; } = {}) {
const {fileSystem} = this.global;
const writeTasks: Promise<void>[] = [];
const outputFilePaths: string[] = [];

for (const emitResult of this._emitInternal(options)) {
if (emitResult === false) {
await writeTasks;
return new DirectoryEmitResult(true, outputFilePaths);
}

writeTasks.push(fileSystem.writeFile(emitResult.filePath, emitResult.fileText));
outputFilePaths.push(emitResult.filePath);
}

await writeTasks;
return new DirectoryEmitResult(false, outputFilePaths);
}

/**
* Emits the files in the directory synchronously.
*
* Remarks: This might be very slow compared to the asynchronous version if there are a lot of files.
* @param options - Options for emitting.
*/
emitSync(options: { emitOnlyDtsFiles?: boolean; outDir?: string; declarationDir?: string; } = {}) {
const {fileSystem} = this.global;
const outputFilePaths: string[] = [];

for (const emitResult of this._emitInternal(options)) {
if (emitResult === false)
return new DirectoryEmitResult(true, outputFilePaths);

fileSystem.writeFileSync(emitResult.filePath, emitResult.fileText);
outputFilePaths.push(emitResult.filePath);
}

return new DirectoryEmitResult(false, outputFilePaths);
}

private _emitInternal(options: { emitOnlyDtsFiles?: boolean; outDir?: string; declarationDir?: string; } = {})
{
const {languageService} = this.global;
const {emitOnlyDtsFiles = false} = options;
const isJsFile = options.outDir == null ? undefined : /\.js$/i;
const isMapFile = options.outDir == null ? undefined : /\.js\.map$/i;
const isDtsFile = options.declarationDir == null && options.outDir == null ? undefined : /\.d\.ts$/i;
const getStandardizedPath = (path: string | undefined) => path == null ? undefined : FileUtils.getStandardizedAbsolutePath(path, this.getPath());
const getSubDirPath = (path: string | undefined, dir: Directory) => path == null ? undefined : FileUtils.pathJoin(path, dir.getBaseName());
const hasDeclarationDir = this.global.compilerOptions.declarationDir != null || options.declarationDir != null;

return emitDirectory(this, getStandardizedPath(options.outDir), getStandardizedPath(options.declarationDir));

function *emitDirectory(directory: Directory, outDir?: string, declarationDir?: string): IterableIterator<false | { filePath: string; fileText: string; }> {
for (const sourceFile of directory.getSourceFiles()) {
const output = languageService.getEmitOutput(sourceFile, emitOnlyDtsFiles);
if (output.getEmitSkipped()) {
yield false;
return;
}

for (const outputFile of output.getOutputFiles()) {
let filePath = outputFile.getFilePath();
const fileText = outputFile.getWriteByteOrderMark() ? FileUtils.getTextWithByteOrderMark(outputFile.getText()) : outputFile.getText();

if (outDir != null && (isJsFile!.test(filePath) || isMapFile!.test(filePath) || (!hasDeclarationDir && isDtsFile!.test(filePath))))
filePath = FileUtils.pathJoin(outDir, FileUtils.getBaseName(filePath));
else if (declarationDir != null && isDtsFile!.test(filePath))
filePath = FileUtils.pathJoin(declarationDir, FileUtils.getBaseName(filePath));

yield { filePath, fileText };
}
}

for (const dir of directory.getDirectories())
yield* emitDirectory(dir, getSubDirPath(outDir, dir), getSubDirPath(declarationDir, dir));
}
}

/**
* Copies a directory to a new directory.
* @param relativeOrAbsolutePath - The relative or absolute path to the new directory. Path is relative to the parent directory.
Expand Down
21 changes: 21 additions & 0 deletions src/fileSystem/DirectoryEmitResult.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import {Diagnostic} from "./../compiler";

export class DirectoryEmitResult {
/** @internal */
constructor(private readonly _emitSkipped: boolean, private readonly _outputFilePaths: string[]) {
}

/**
* Gets if the emit was skipped.
*/
getEmitSkipped() {
return this._emitSkipped;
}

/**
* Gets the output file paths.
*/
getOutputFilePaths() {
return this._outputFilePaths;
}
}
1 change: 1 addition & 0 deletions src/fileSystem/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export * from "./DefaultFileSystemHost";
export * from "./Directory";
export * from "./DirectoryEmitResult";
export * from "./FileSystemHost";
export * from "./VirtualFileSystemHost";
3 changes: 1 addition & 2 deletions src/main.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
export * from "./compiler";
export * from "./structures";
export {TsSimpleAst as default} from "./TsSimpleAst";
export * from "./fileSystem/FileSystemHost";
export * from "./fileSystem/Directory";
export {FileSystemHost, Directory, DirectoryEmitResult} from "./fileSystem";
export * from "./ManipulationSettings";
export {createWrappedNode} from "./createWrappedNode";
export {getCompilerOptionsFromTsConfig} from "./utils/getCompilerOptionsFromTsConfig";
Expand Down
27 changes: 22 additions & 5 deletions src/tests/compiler/tools/languageServiceTests.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import * as ts from "typescript";
import {expect} from "chai";
import {LanguageService, EmitOutput} from "./../../../compiler";
import {LanguageService, EmitOutput, SourceFile} from "./../../../compiler";
import {FileNotFoundError} from "./../../../errors";
import {FileUtils} from "./../../../utils";
import {getInfoFromText} from "./../testHelpers";
Expand All @@ -13,15 +13,34 @@ describe(nameof(LanguageService), () => {
for (let i = 0; i < expected.outputFiles.length; i++) {
const actualFile = output.getOutputFiles()[i];
const expectedFile = expected.outputFiles[i];
expect(actualFile.getName()).to.equal(FileUtils.getStandardizedAbsolutePath(expectedFile.fileName));
expect(actualFile.getFilePath()).to.equal(FileUtils.getStandardizedAbsolutePath(expectedFile.fileName));
expect(actualFile.getText()).to.equal(expectedFile.text);
expect(actualFile.getWriteByteOrderMark()).to.equal(expectedFile.writeByteOrderMark);
}
}

it("should get the emit output", () => {
function doTest(sourceFileOrFilePath: string | SourceFile) {
const output = sourceFile.global.languageService.getEmitOutput(sourceFileOrFilePath);
checkOutput(output, {
emitSkipped: false,
outputFiles: [{
fileName: sourceFile.getBaseName().replace(".ts", ".js"),
text: "var t = 5;\n",
writeByteOrderMark: false
}]
});
}

const {sourceFile} = getInfoFromText("const t = 5;", { compilerOptions: { target: ts.ScriptTarget.ES5 } });

doTest(sourceFile);
doTest(sourceFile.getFilePath());
});

it("should get the emit output when specifying a source file", () => {
const {sourceFile, tsSimpleAst} = getInfoFromText("const t = 5;", { compilerOptions: { target: ts.ScriptTarget.ES5 } });
const output = sourceFile.global.languageService.getEmitOutput(sourceFile.getFilePath());
const output = sourceFile.global.languageService.getEmitOutput(sourceFile);
checkOutput(output, {
emitSkipped: false,
outputFiles: [{
Expand Down Expand Up @@ -52,8 +71,6 @@ describe(nameof(LanguageService), () => {
emitSkipped: true,
outputFiles: []
});

expect(output.getDiagnostics().length).to.equal(1);
});

it("should throw when the specified file does not exist", () => {
Expand Down

1 comment on commit 3cb455c

@dsherret
Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was for #183.

Please sign in to comment.