Skip to content

Commit

Permalink
feat: Wrap LanguageService.getEmitOutput(...).
Browse files Browse the repository at this point in the history
  • Loading branch information
dsherret committed Dec 22, 2017
1 parent 334f20b commit 40ecc32
Show file tree
Hide file tree
Showing 6 changed files with 187 additions and 3 deletions.
14 changes: 13 additions & 1 deletion src/compiler/tools/LanguageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {SourceFile} from "./../file";
import {Node} from "./../common";
import {Program} from "./Program";
import {FormatCodeSettings} from "./inputs";
import {ReferencedSymbol, DefinitionInfo, RenameLocation, ImplementationLocation, TextChange} from "./results";
import {ReferencedSymbol, DefinitionInfo, RenameLocation, ImplementationLocation, TextChange, EmitOutput} from "./results";

export class LanguageService {
private readonly _compilerObject: ts.LanguageService;
Expand Down Expand Up @@ -241,4 +241,16 @@ export class LanguageService {
return fullText;
}
}

/**
* Gets the emit output of a 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);
if (!this.global.compilerFactory.containsSourceFileAtPath(filePath))
throw new errors.FileNotFoundError(filePath);
return new EmitOutput(this.global, filePath, this.compilerObject.getEmitOutput(filePath, emitOnlyDtsFiles));
}
}
61 changes: 61 additions & 0 deletions src/compiler/tools/results/EmitOutput.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import * as ts from "typescript";
import {GlobalContainer} from "./../../../GlobalContainer";
import {Memoize} from "./../../../utils";
import {OutputFile} from "./OutputFile";
import {Diagnostic} from "./Diagnostic";

/**
* Output of an emit on a single file.
*/
export class EmitOutput {
/** @internal */
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() : [];
}

/**
* TypeScript compiler emit result.
*/
get compilerObject() {
return this._compilerObject;
}

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

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

/**
* Gets the output files.
*/
@Memoize
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));
}
}
44 changes: 44 additions & 0 deletions src/compiler/tools/results/OutputFile.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import * as ts from "typescript";

/**
* Output file of an emit.
*/
export class OutputFile {
/** @internal */
private readonly _compilerObject: ts.OutputFile;

/**
* @internal
*/
constructor(compilerObject: ts.OutputFile) {
this._compilerObject = compilerObject;
}

/**
* TypeScript compiler emit result.
*/
get compilerObject() {
return this._compilerObject;
}

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

/**
* Gets whether the byte order mark should be written.
*/
getWriteByteOrderMark() {
return this.compilerObject.writeByteOrderMark || false;
}

/**
* Gets the file text.
*/
getText() {
return this.compilerObject.text;
}
}
2 changes: 2 additions & 0 deletions src/compiler/tools/results/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ export * from "./Diagnostic";
export * from "./DiagnosticMessageChain";
export * from "./DocumentSpan";
export * from "./EmitResult";
export * from "./EmitOutput";
export * from "./ImplementationLocation";
export * from "./OutputFile";
export * from "./ReferencedSymbol";
export * from "./ReferencedSymbolDefinitionInfo";
export * from "./ReferenceEntry";
Expand Down
5 changes: 3 additions & 2 deletions src/tests/compiler/testHelpers/getInfoFromText.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,13 +21,14 @@ export interface GetInfoFromTextOptions {
filePath?: string;
host?: FileSystemHost;
disableErrorCheck?: boolean;
compilerOptions?: ts.CompilerOptions;
}

/** @internal */
export function getInfoFromText<TFirstChild extends Node>(text: string, opts?: GetInfoFromTextOptions) {
// tslint:disable-next-line:no-unnecessary-initializer -- tslint not realizing undefined is required
const {isDefinitionFile = false, filePath = undefined, host = defaultHost, disableErrorCheck = false} = opts || {};
const tsSimpleAst = new TsSimpleAst({ compilerOptions: { target: ts.ScriptTarget.ES2017 }}, host);
const {isDefinitionFile = false, filePath = undefined, host = defaultHost, disableErrorCheck = false, compilerOptions = { target: ts.ScriptTarget.ES2017 }} = opts || {};
const tsSimpleAst = new TsSimpleAst({ compilerOptions }, host);
const sourceFile = tsSimpleAst.createSourceFile(filePath || (isDefinitionFile ? "testFile.d.ts" : "testFile.ts"), text);
const firstChild = sourceFile.getChildSyntaxListOrThrow().getChildren()[0] as TFirstChild;

Expand Down
64 changes: 64 additions & 0 deletions src/tests/compiler/tools/languageServiceTests.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
import * as ts from "typescript";
import {expect} from "chai";
import {LanguageService, EmitOutput} from "./../../../compiler";
import {FileNotFoundError} from "./../../../errors";
import {FileUtils} from "./../../../utils";
import {getInfoFromText} from "./../testHelpers";

describe(nameof(LanguageService), () => {
describe(nameof<LanguageService>(l => l.getEmitOutput), () => {
function checkOutput(output: EmitOutput, expected: { emitSkipped: boolean; outputFiles: { fileName: string; text: string; writeByteOrderMark: boolean; }[]; }) {
expect(output.getEmitSkipped()).to.equal(expected.emitSkipped);
expect(output.getOutputFiles().length).to.equal(expected.outputFiles.length);
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.getText()).to.equal(expectedFile.text);
expect(actualFile.getWriteByteOrderMark()).to.equal(expectedFile.writeByteOrderMark);
}
}

it("should get the emit output", () => {
const {sourceFile, tsSimpleAst} = getInfoFromText("const t = 5;", { compilerOptions: { target: ts.ScriptTarget.ES5 } });
const output = sourceFile.global.languageService.getEmitOutput(sourceFile.getFilePath());
checkOutput(output, {
emitSkipped: false,
outputFiles: [{
fileName: sourceFile.getBaseName().replace(".ts", ".js"),
text: "var t = 5;\n",
writeByteOrderMark: false
}]
});
});

it("should only emit the declaration file when specified", () => {
const {sourceFile, tsSimpleAst} = getInfoFromText("const t = 5;", { compilerOptions: { declaration: true } });
const output = sourceFile.global.languageService.getEmitOutput(sourceFile.getFilePath(), true);
checkOutput(output, {
emitSkipped: false,
outputFiles: [{
fileName: sourceFile.getBaseName().replace(".ts", ".d.ts"),
text: "declare const t = 5;\n",
writeByteOrderMark: false
}]
});
});

it("should not emit if there is a declaraton file error", () => {
const {sourceFile, tsSimpleAst} = getInfoFromText("class MyClass {}\n export class Test extends MyClass {}\n", { compilerOptions: { declaration: true } });
const output = sourceFile.global.languageService.getEmitOutput(sourceFile.getFilePath(), true);
checkOutput(output, {
emitSkipped: true,
outputFiles: []
});

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

it("should throw when the specified file does not exist", () => {
const {tsSimpleAst} = getInfoFromText("");
expect(() => tsSimpleAst.getLanguageService().getEmitOutput("nonExistentFile.ts")).to.throw(FileNotFoundError);
});
});
});

0 comments on commit 40ecc32

Please sign in to comment.