mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-02-15 11:35:42 -06:00
Modify the api in builder so that it tracks changed files
This commit is contained in:
parent
2dd6aed654
commit
89c61e797c
@ -18,13 +18,14 @@ namespace ts {
|
||||
text: string;
|
||||
}
|
||||
|
||||
export interface Builder<T extends EmitOutput> {
|
||||
export interface Builder {
|
||||
/**
|
||||
* This is the callback when file infos in the builder are updated
|
||||
*/
|
||||
onProgramUpdateGraph(program: Program): void;
|
||||
getFilesAffectedBy(program: Program, path: Path): string[];
|
||||
emitFile(program: Program, path: Path): T | EmitOutput;
|
||||
emitFile(program: Program, path: Path): EmitOutput;
|
||||
emitChangedFiles(program: Program): EmitOutputDetailed[];
|
||||
clear(): void;
|
||||
}
|
||||
|
||||
@ -38,19 +39,7 @@ namespace ts {
|
||||
getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[];
|
||||
}
|
||||
|
||||
export function getDetailedEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
|
||||
cancellationToken ?: CancellationToken, customTransformers ?: CustomTransformers): EmitOutputDetailed {
|
||||
return getEmitOutput(/*detailed*/ true, program, sourceFile, emitOnlyDtsFiles,
|
||||
cancellationToken, customTransformers) as EmitOutputDetailed;
|
||||
}
|
||||
|
||||
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
|
||||
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput {
|
||||
return getEmitOutput(/*detailed*/ false, program, sourceFile, emitOnlyDtsFiles,
|
||||
cancellationToken, customTransformers);
|
||||
}
|
||||
|
||||
function getEmitOutput(isDetailed: boolean, program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean,
|
||||
export function getFileEmitOutput(program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean,
|
||||
cancellationToken?: CancellationToken, customTransformers?: CustomTransformers): EmitOutput | EmitOutputDetailed {
|
||||
const outputFiles: OutputFile[] = [];
|
||||
let emittedSourceFiles: SourceFile[];
|
||||
@ -75,20 +64,23 @@ namespace ts {
|
||||
}
|
||||
}
|
||||
|
||||
export function createBuilder<T extends EmitOutput>(
|
||||
export function createBuilder(
|
||||
getCanonicalFileName: (fileName: string) => string,
|
||||
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles?: boolean) => T,
|
||||
getEmitOutput: (program: Program, sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) => EmitOutput | EmitOutputDetailed,
|
||||
computeHash: (data: string) => string,
|
||||
shouldEmitFile: (sourceFile: SourceFile) => boolean
|
||||
): Builder<T> {
|
||||
): Builder {
|
||||
let isModuleEmit: boolean | undefined;
|
||||
// Last checked shape signature for the file info
|
||||
let fileInfos: Map<string>;
|
||||
type FileInfo = { version: string; signature: string; };
|
||||
let fileInfos: Map<FileInfo>;
|
||||
let changedFilesSinceLastEmit: Map<true>;
|
||||
let emitHandler: EmitHandler;
|
||||
return {
|
||||
onProgramUpdateGraph,
|
||||
getFilesAffectedBy,
|
||||
emitFile,
|
||||
emitChangedFiles,
|
||||
clear
|
||||
};
|
||||
|
||||
@ -100,19 +92,37 @@ namespace ts {
|
||||
fileInfos = undefined;
|
||||
}
|
||||
|
||||
fileInfos = mutateExistingMap(
|
||||
changedFilesSinceLastEmit = changedFilesSinceLastEmit || createMap<true>();
|
||||
fileInfos = mutateExistingMapWithSameExistingValues(
|
||||
fileInfos, arrayToMap(program.getSourceFiles(), sourceFile => sourceFile.path),
|
||||
(_path, sourceFile) => {
|
||||
emitHandler.addScriptInfo(program, sourceFile);
|
||||
return "";
|
||||
},
|
||||
(path: Path, _value) => emitHandler.removeScriptInfo(path),
|
||||
/*isSameValue*/ undefined,
|
||||
/*OnDeleteExistingMismatchValue*/ undefined,
|
||||
(_prevValue, sourceFile) => emitHandler.updateScriptInfo(program, sourceFile)
|
||||
// Add new file info
|
||||
(_path, sourceFile) => addNewFileInfo(program, sourceFile),
|
||||
// Remove existing file info
|
||||
removeExistingFileInfo,
|
||||
// We will update in place instead of deleting existing value and adding new one
|
||||
(existingInfo, sourceFile) => updateExistingFileInfo(program, existingInfo, sourceFile)
|
||||
);
|
||||
}
|
||||
|
||||
function addNewFileInfo(program: Program, sourceFile: SourceFile): FileInfo {
|
||||
changedFilesSinceLastEmit.set(sourceFile.path, true);
|
||||
emitHandler.addScriptInfo(program, sourceFile);
|
||||
return { version: sourceFile.version, signature: undefined };
|
||||
}
|
||||
|
||||
function removeExistingFileInfo(path: Path, _existingFileInfo: FileInfo) {
|
||||
changedFilesSinceLastEmit.set(path, true);
|
||||
emitHandler.removeScriptInfo(path);
|
||||
}
|
||||
|
||||
function updateExistingFileInfo(program: Program, existingInfo: FileInfo, sourceFile: SourceFile) {
|
||||
if (existingInfo.version !== sourceFile.version) {
|
||||
changedFilesSinceLastEmit.set(sourceFile.path, true);
|
||||
existingInfo.version = sourceFile.version;
|
||||
emitHandler.updateScriptInfo(program, sourceFile);
|
||||
}
|
||||
}
|
||||
|
||||
function ensureProgramGraph(program: Program) {
|
||||
if (!emitHandler) {
|
||||
createProgramGraph(program);
|
||||
@ -130,20 +140,53 @@ namespace ts {
|
||||
|
||||
const sourceFile = program.getSourceFile(path);
|
||||
const singleFileResult = sourceFile && shouldEmitFile(sourceFile) ? [sourceFile.fileName] : [];
|
||||
if (!fileInfos || !fileInfos.has(path) || !updateShapeSignature(program, sourceFile)) {
|
||||
const info = fileInfos && fileInfos.get(path);
|
||||
if (!info || !updateShapeSignature(program, sourceFile, info)) {
|
||||
return singleFileResult;
|
||||
}
|
||||
|
||||
Debug.assert(!!sourceFile);
|
||||
return emitHandler.getFilesAffectedByUpdatedShape(program, sourceFile, singleFileResult);
|
||||
}
|
||||
|
||||
function emitFile(program: Program, path: Path): T | EmitOutput {
|
||||
function emitFile(program: Program, path: Path) {
|
||||
ensureProgramGraph(program);
|
||||
if (!fileInfos || !fileInfos.has(path)) {
|
||||
return { outputFiles: [], emitSkipped: true };
|
||||
}
|
||||
|
||||
return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false);
|
||||
return getEmitOutput(program, program.getSourceFileByPath(path), /*emitOnlyDtsFiles*/ false, /*isDetailed*/ false);
|
||||
}
|
||||
|
||||
function emitChangedFiles(program: Program): EmitOutputDetailed[] {
|
||||
ensureProgramGraph(program);
|
||||
const result: EmitOutputDetailed[] = [];
|
||||
if (changedFilesSinceLastEmit) {
|
||||
const seenFiles = createMap<SourceFile>();
|
||||
changedFilesSinceLastEmit.forEach((__value, path: Path) => {
|
||||
const affectedFiles = getFilesAffectedBy(program, path);
|
||||
for (const file of affectedFiles) {
|
||||
if (!seenFiles.has(file)) {
|
||||
const sourceFile = program.getSourceFile(file);
|
||||
seenFiles.set(file, sourceFile);
|
||||
if (sourceFile) {
|
||||
const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ false, /*isDetailed*/ true) as EmitOutputDetailed;
|
||||
result.push(emitOutput);
|
||||
|
||||
// mark all the emitted source files as seen
|
||||
if (emitOutput.emittedSourceFiles) {
|
||||
for (const file of emitOutput.emittedSourceFiles) {
|
||||
seenFiles.set(file.fileName, file);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
changedFilesSinceLastEmit = undefined;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function clear() {
|
||||
@ -152,10 +195,6 @@ namespace ts {
|
||||
fileInfos = undefined;
|
||||
}
|
||||
|
||||
function isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile: SourceFile) {
|
||||
return sourceFile && (isExternalModule(sourceFile) || containsOnlyAmbientModules(sourceFile));
|
||||
}
|
||||
|
||||
/**
|
||||
* For script files that contains only ambient external modules, although they are not actually external module files,
|
||||
* they can only be consumed via importing elements from them. Regular script files cannot consume them. Therefore,
|
||||
@ -174,20 +213,21 @@ namespace ts {
|
||||
/**
|
||||
* @return {boolean} indicates if the shape signature has changed since last update.
|
||||
*/
|
||||
function updateShapeSignature(program: Program, sourceFile: SourceFile) {
|
||||
const path = sourceFile.path;
|
||||
|
||||
const prevSignature = fileInfos.get(path);
|
||||
let latestSignature = prevSignature;
|
||||
function updateShapeSignature(program: Program, sourceFile: SourceFile, info: FileInfo) {
|
||||
const prevSignature = info.signature;
|
||||
let latestSignature: string;
|
||||
if (sourceFile.isDeclarationFile) {
|
||||
latestSignature = computeHash(sourceFile.text);
|
||||
fileInfos.set(path, latestSignature);
|
||||
info.signature = latestSignature;
|
||||
}
|
||||
else {
|
||||
const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true);
|
||||
const emitOutput = getEmitOutput(program, sourceFile, /*emitOnlyDtsFiles*/ true, /*isDetailed*/ false);
|
||||
if (emitOutput.outputFiles && emitOutput.outputFiles.length > 0) {
|
||||
latestSignature = computeHash(emitOutput.outputFiles[0].text);
|
||||
fileInfos.set(path, latestSignature);
|
||||
info.signature = latestSignature;
|
||||
}
|
||||
else {
|
||||
latestSignature = prevSignature;
|
||||
}
|
||||
}
|
||||
|
||||
@ -201,6 +241,7 @@ namespace ts {
|
||||
*/
|
||||
function getReferencedFiles(program: Program, sourceFile: SourceFile): Map<true> {
|
||||
const referencedFiles = createMap<true>();
|
||||
|
||||
// We need to use a set here since the code can contain the same import twice,
|
||||
// but that will only be one dependency.
|
||||
// To avoid invernal conversion, the key of the referencedFiles map must be of type Path
|
||||
@ -281,44 +322,45 @@ namespace ts {
|
||||
function getModuleEmitHandler(): EmitHandler {
|
||||
const references = createMap<Map<true>>();
|
||||
const referencedBy = createMultiMap<Path>();
|
||||
const scriptVersionForReferences = createMap<string>();
|
||||
return {
|
||||
addScriptInfo,
|
||||
addScriptInfo: setReferences,
|
||||
removeScriptInfo,
|
||||
updateScriptInfo,
|
||||
updateScriptInfo: setReferences,
|
||||
getFilesAffectedByUpdatedShape
|
||||
};
|
||||
|
||||
function setReferences(program: Program, sourceFile: SourceFile, existingMap: Map<true>) {
|
||||
function setReferences(program: Program, sourceFile: SourceFile) {
|
||||
const path = sourceFile.path;
|
||||
existingMap = mutateExistingMapWithNewSet<true>(
|
||||
existingMap,
|
||||
getReferencedFiles(program, sourceFile),
|
||||
// Creating new Reference: as sourceFile references file with path 'key'
|
||||
// in other words source file (path) is referenced by 'key'
|
||||
key => { referencedBy.add(key, path); return true; },
|
||||
// Remove existing reference by entry: source file doesnt reference file 'key' any more
|
||||
// in other words source file (path) is not referenced by 'key'
|
||||
(key, _existingValue) => { referencedBy.remove(key, path); }
|
||||
references.set(path,
|
||||
mutateExistingMapWithNewSet<true>(
|
||||
// Existing references
|
||||
references.get(path),
|
||||
// Updated references
|
||||
getReferencedFiles(program, sourceFile),
|
||||
// Creating new Reference: as sourceFile references file with path 'key'
|
||||
// in other words source file (path) is referenced by 'key'
|
||||
key => { referencedBy.add(key, path); return true; },
|
||||
// Remove existing reference by entry: source file doesnt reference file 'key' any more
|
||||
// in other words source file (path) is not referenced by 'key'
|
||||
(key, _existingValue) => { referencedBy.remove(key, path); }
|
||||
)
|
||||
);
|
||||
references.set(path, existingMap);
|
||||
scriptVersionForReferences.set(path, sourceFile.version);
|
||||
}
|
||||
|
||||
function addScriptInfo(program: Program, sourceFile: SourceFile) {
|
||||
setReferences(program, sourceFile, undefined);
|
||||
}
|
||||
|
||||
function removeScriptInfo(path: Path) {
|
||||
// Remove existing references
|
||||
references.forEach((_value, key) => {
|
||||
referencedBy.remove(key, path);
|
||||
});
|
||||
references.delete(path);
|
||||
scriptVersionForReferences.delete(path);
|
||||
}
|
||||
|
||||
function updateScriptInfo(program: Program, sourceFile: SourceFile) {
|
||||
const path = sourceFile.path;
|
||||
const lastUpdatedVersion = scriptVersionForReferences.get(path);
|
||||
if (lastUpdatedVersion !== sourceFile.version) {
|
||||
setReferences(program, sourceFile, references.get(path));
|
||||
// Delete the entry and add files referencing this file, as chagned files too
|
||||
const referencedByPaths = referencedBy.get(path);
|
||||
if (referencedByPaths) {
|
||||
for (const path of referencedByPaths) {
|
||||
changedFilesSinceLastEmit.set(path, true);
|
||||
}
|
||||
referencedBy.delete(path);
|
||||
}
|
||||
}
|
||||
|
||||
@ -327,7 +369,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
function getFilesAffectedByUpdatedShape(program: Program, sourceFile: SourceFile, singleFileResult: string[]): string[] {
|
||||
if (!isExternalModuleOrHasOnlyAmbientExternalModules(sourceFile)) {
|
||||
if (!isExternalModule(sourceFile) && !containsOnlyAmbientModules(sourceFile)) {
|
||||
return getAllEmittableFiles(program);
|
||||
}
|
||||
|
||||
@ -353,7 +395,7 @@ namespace ts {
|
||||
const currentPath = queue.pop();
|
||||
if (!seenFileNamesMap.has(currentPath)) {
|
||||
const currentSourceFile = program.getSourceFileByPath(currentPath);
|
||||
if (currentSourceFile && updateShapeSignature(program, currentSourceFile)) {
|
||||
if (currentSourceFile && updateShapeSignature(program, currentSourceFile, fileInfos.get(currentPath))) {
|
||||
queue.push(...getReferencedByPaths(currentPath));
|
||||
}
|
||||
setSeenFileName(currentPath, currentSourceFile);
|
||||
@ -361,7 +403,7 @@ namespace ts {
|
||||
}
|
||||
|
||||
// Return array of values that needs emit
|
||||
return flatMapIter(seenFileNamesMap.values(), value => value);
|
||||
return flatMapIter(seenFileNamesMap.values());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -473,12 +473,14 @@ namespace ts {
|
||||
return result;
|
||||
}
|
||||
|
||||
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U | U[] | undefined): U[] {
|
||||
const result: U[] = [];
|
||||
export function flatMapIter<T>(iter: Iterator<T>): T[];
|
||||
export function flatMapIter<T, U>(iter: Iterator<T>, mapfn: (x: T) => U | U[] | undefined): U[];
|
||||
export function flatMapIter<T>(iter: Iterator<T>, mapfn?: (x: any) => any): any[] {
|
||||
const result = [];
|
||||
while (true) {
|
||||
const { value, done } = iter.next();
|
||||
if (done) break;
|
||||
const res = mapfn(value);
|
||||
const res = mapfn ? mapfn(value) : value;
|
||||
if (res) {
|
||||
if (isArray(res)) {
|
||||
result.push(...res);
|
||||
|
||||
@ -248,7 +248,6 @@ namespace ts {
|
||||
let timerToUpdateProgram: any; // timer callback to recompile the program
|
||||
|
||||
const sourceFilesCache = createMap<HostFileInfo | string>(); // Cache that stores the source file and version info
|
||||
let changedFilePaths: Path[] = [];
|
||||
|
||||
let host: System;
|
||||
if (configFileName) {
|
||||
@ -262,7 +261,7 @@ namespace ts {
|
||||
const getCanonicalFileName = createGetCanonicalFileName(host.useCaseSensitiveFileNames);
|
||||
|
||||
// There is no extra check needed since we can just rely on the program to decide emit
|
||||
const builder = createBuilder(getCanonicalFileName, getDetailedEmitOutput, computeHash, _sourceFile => true);
|
||||
const builder = createBuilder(getCanonicalFileName, getFileEmitOutput, computeHash, _sourceFile => true);
|
||||
|
||||
if (compilerOptions.pretty) {
|
||||
reportDiagnosticWorker = reportDiagnosticWithColorAndContext;
|
||||
@ -352,51 +351,44 @@ namespace ts {
|
||||
|
||||
function emitWithBuilder(program: Program): EmitResult {
|
||||
builder.onProgramUpdateGraph(program);
|
||||
const filesPendingToEmit = changedFilePaths;
|
||||
changedFilePaths = [];
|
||||
|
||||
const seenFiles = createMap<true>();
|
||||
|
||||
let emitSkipped: boolean;
|
||||
const diagnostics: Diagnostic[] = [];
|
||||
const emittedFiles: string[] = program.getCompilerOptions().listEmittedFiles ? [] : undefined;
|
||||
let sourceMaps: SourceMapData[];
|
||||
while (filesPendingToEmit.length) {
|
||||
const filePath = filesPendingToEmit.pop();
|
||||
const affectedFiles = builder.getFilesAffectedBy(program, filePath);
|
||||
for (const file of affectedFiles) {
|
||||
if (!seenFiles.has(file)) {
|
||||
seenFiles.set(file, true);
|
||||
const sourceFile = program.getSourceFile(file);
|
||||
if (sourceFile) {
|
||||
writeFiles(<EmitOutputDetailed>builder.emitFile(program, sourceFile.path));
|
||||
let emitSkipped: boolean;
|
||||
let diagnostics: Diagnostic[];
|
||||
|
||||
const result = builder.emitChangedFiles(program);
|
||||
switch (result.length) {
|
||||
case 0:
|
||||
emitSkipped = true;
|
||||
break;
|
||||
case 1:
|
||||
const emitOutput = result[0];
|
||||
({ diagnostics, sourceMaps, emitSkipped } = emitOutput);
|
||||
writeOutputFiles(emitOutput.outputFiles);
|
||||
break;
|
||||
default:
|
||||
for (const emitOutput of result) {
|
||||
if (emitOutput.emitSkipped) {
|
||||
emitSkipped = true;
|
||||
}
|
||||
diagnostics = concatenate(diagnostics, emitOutput.diagnostics);
|
||||
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
|
||||
writeOutputFiles(emitOutput.outputFiles);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { emitSkipped, diagnostics, emittedFiles, sourceMaps };
|
||||
return { emitSkipped, diagnostics: diagnostics || [], emittedFiles, sourceMaps };
|
||||
|
||||
function writeFiles(emitOutput: EmitOutputDetailed) {
|
||||
if (emitOutput.emitSkipped) {
|
||||
emitSkipped = true;
|
||||
}
|
||||
|
||||
diagnostics.push(...emitOutput.diagnostics);
|
||||
sourceMaps = concatenate(sourceMaps, emitOutput.sourceMaps);
|
||||
// If it emitted more than one source files, just mark all those source files as seen
|
||||
if (emitOutput.emittedSourceFiles && emitOutput.emittedSourceFiles.length > 1) {
|
||||
for (const file of emitOutput.emittedSourceFiles) {
|
||||
seenFiles.set(file.fileName, true);
|
||||
}
|
||||
}
|
||||
for (const outputFile of emitOutput.outputFiles) {
|
||||
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
|
||||
if (error) {
|
||||
diagnostics.push(error);
|
||||
}
|
||||
if (emittedFiles) {
|
||||
emittedFiles.push(outputFile.name);
|
||||
function writeOutputFiles(outputFiles: OutputFile[]) {
|
||||
if (outputFiles) {
|
||||
for (const outputFile of outputFiles) {
|
||||
const error = writeFile(outputFile.name, outputFile.text, outputFile.writeByteOrderMark);
|
||||
if (error) {
|
||||
diagnostics.push(error);
|
||||
}
|
||||
if (emittedFiles) {
|
||||
emittedFiles.push(outputFile.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -430,8 +422,6 @@ namespace ts {
|
||||
|
||||
// Create new source file if requested or the versions dont match
|
||||
if (!hostSourceFile || shouldCreateNewSourceFile || hostSourceFile.version.toString() !== hostSourceFile.sourceFile.version) {
|
||||
changedFilePaths.push(path);
|
||||
|
||||
const sourceFile = getNewSourceFile();
|
||||
if (hostSourceFile) {
|
||||
if (shouldCreateNewSourceFile) {
|
||||
|
||||
@ -3656,13 +3656,27 @@ namespace ts {
|
||||
);
|
||||
}
|
||||
|
||||
export function mutateExistingMapWithSameExistingValues<T, U>(
|
||||
existingMap: Map<T>, newMap: Map<U>,
|
||||
createNewValue: (key: string, valueInNewMap: U) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void,
|
||||
onExistingValue?: (existingValue: T, valueInNewMap: U) => void
|
||||
): Map<T> {
|
||||
return mutateExistingMap(
|
||||
existingMap, newMap,
|
||||
createNewValue, onDeleteExistingValue,
|
||||
/*isSameValue*/ undefined, /*onDeleteExistingMismatchValue*/ undefined,
|
||||
onExistingValue
|
||||
);
|
||||
}
|
||||
|
||||
export function mutateExistingMap<T, U>(
|
||||
existingMap: Map<T>, newMap: Map<U>,
|
||||
createNewValue: (key: string, valueInNewMap: U) => T,
|
||||
onDeleteExistingValue: (key: string, existingValue: T) => void,
|
||||
isSameValue?: (existingValue: T, valueInNewMap: U) => boolean,
|
||||
OnDeleteExistingMismatchValue?: (key: string, existingValue: T) => void,
|
||||
onSameExistingValue?: (existingValue: T, valueInNewMap: U) => void
|
||||
onExistingValue?: (existingValue: T, valueInNewMap: U) => void
|
||||
): Map<T> {
|
||||
// If there are new values update them
|
||||
if (newMap) {
|
||||
@ -3680,8 +3694,8 @@ namespace ts {
|
||||
existingMap.delete(key);
|
||||
OnDeleteExistingMismatchValue(key, existingValue);
|
||||
}
|
||||
else if (onSameExistingValue) {
|
||||
onSameExistingValue(existingValue, valueInNewMap);
|
||||
else if (onExistingValue) {
|
||||
onExistingValue(existingValue, valueInNewMap);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ -1015,9 +1015,24 @@ namespace ts.tscWatch {
|
||||
describe("tsc-watch emit for configured projects", () => {
|
||||
const file1Consumer1Path = "/a/b/file1Consumer1.ts";
|
||||
const moduleFile1Path = "/a/b/moduleFile1.ts";
|
||||
function getInitialState(configObj: any = {}, fileNames?: string[]) {
|
||||
const configFilePath = "/a/b/tsconfig.json";
|
||||
type InitialStateParams = {
|
||||
/** custom config file options */
|
||||
configObj?: any;
|
||||
/** list of the files that will be emitted for first compilation */
|
||||
firstCompilationEmitFiles?: string[];
|
||||
/** get the emit file for file - default is multi file emit line */
|
||||
getEmitLine?(file: FileOrFolder, host: WatchedSystem): string;
|
||||
/** Additional files and folders to add */
|
||||
getAdditionalFileOrFolder?(): FileOrFolder[];
|
||||
/** initial list of files to emit if not the default list */
|
||||
firstReloadFileList?: string[];
|
||||
};
|
||||
function getInitialState({ configObj = {}, firstCompilationEmitFiles, getEmitLine, getAdditionalFileOrFolder, firstReloadFileList }: InitialStateParams = {}) {
|
||||
const host = createWatchedSystem([]);
|
||||
const getOutputName = (file: FileOrFolder) => getEmittedLineForMultiFileOutput(file, host);
|
||||
const getOutputName = getEmitLine ? (file: FileOrFolder) => getEmitLine(file, host) :
|
||||
(file: FileOrFolder) => getEmittedLineForMultiFileOutput(file, host);
|
||||
|
||||
const moduleFile1 = getFileOrFolderEmit({
|
||||
path: moduleFile1Path,
|
||||
content: "export function Foo() { };",
|
||||
@ -1043,20 +1058,24 @@ namespace ts.tscWatch {
|
||||
content: `interface GlobalFoo { age: number }`
|
||||
});
|
||||
|
||||
const additionalFiles = getAdditionalFileOrFolder ?
|
||||
map(getAdditionalFileOrFolder(), file => getFileOrFolderEmit(file, getOutputName)) :
|
||||
[];
|
||||
|
||||
(configObj.compilerOptions || (configObj.compilerOptions = {})).listEmittedFiles = true;
|
||||
const configFile = getFileOrFolderEmit({
|
||||
path: "/a/b/tsconfig.json",
|
||||
path: configFilePath,
|
||||
content: JSON.stringify(configObj)
|
||||
});
|
||||
|
||||
const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile];
|
||||
const files = [moduleFile1, file1Consumer1, file1Consumer2, globalFile3, moduleFile2, configFile, libFile, ...additionalFiles];
|
||||
let allEmittedFiles = getEmittedLines(files);
|
||||
host.reloadFS(files);
|
||||
host.reloadFS(firstReloadFileList ? getFiles(firstReloadFileList) : files);
|
||||
|
||||
// Initial compile
|
||||
createWatchWithConfig(configFile.path, host);
|
||||
if (fileNames) {
|
||||
checkAffectedLines(host, map(fileNames, name => find(files, file => file.path === name)), allEmittedFiles);
|
||||
if (firstCompilationEmitFiles) {
|
||||
checkAffectedLines(host, getFiles(firstCompilationEmitFiles), allEmittedFiles);
|
||||
}
|
||||
else {
|
||||
checkOutputContains(host, allEmittedFiles);
|
||||
@ -1066,11 +1085,20 @@ namespace ts.tscWatch {
|
||||
return {
|
||||
moduleFile1, file1Consumer1, file1Consumer2, moduleFile2, globalFile3, configFile,
|
||||
files,
|
||||
getFile,
|
||||
verifyAffectedFiles,
|
||||
verifyAffectedAllFiles,
|
||||
getOutputName
|
||||
};
|
||||
|
||||
function getFiles(filelist: string[]) {
|
||||
return map(filelist, getFile);
|
||||
}
|
||||
|
||||
function getFile(fileName: string) {
|
||||
return find(files, file => file.path === fileName);
|
||||
}
|
||||
|
||||
function verifyAffectedAllFiles() {
|
||||
host.reloadFS(files);
|
||||
host.checkTimeoutQueueLengthAndRun(1);
|
||||
@ -1172,8 +1200,8 @@ namespace ts.tscWatch {
|
||||
it("should detect changes in non-root files", () => {
|
||||
const {
|
||||
moduleFile1, file1Consumer1,
|
||||
verifyAffectedFiles,
|
||||
} = getInitialState({ files: [file1Consumer1Path] }, [file1Consumer1Path, moduleFile1Path]);
|
||||
verifyAffectedFiles
|
||||
} = getInitialState({ configObj: { files: [file1Consumer1Path] }, firstCompilationEmitFiles: [file1Consumer1Path, moduleFile1Path] });
|
||||
|
||||
moduleFile1.content = `export var T: number;export function Foo() { };`;
|
||||
verifyAffectedFiles([moduleFile1, file1Consumer1]);
|
||||
@ -1192,189 +1220,129 @@ namespace ts.tscWatch {
|
||||
verifyAffectedAllFiles();
|
||||
});
|
||||
|
||||
//it("should save with base tsconfig.json", () => {
|
||||
// configFile = {
|
||||
// path: "/a/b/tsconfig.json",
|
||||
// content: `{
|
||||
// "extends": "/a/tsconfig.json"
|
||||
// }`
|
||||
// };
|
||||
it("should always return the file itself if '--isolatedModules' is specified", () => {
|
||||
const {
|
||||
moduleFile1, verifyAffectedFiles
|
||||
} = getInitialState({ configObj: { compilerOptions: { isolatedModules: true } } });
|
||||
|
||||
// const configFile2: FileOrFolder = {
|
||||
// path: "/a/tsconfig.json",
|
||||
// content: `{
|
||||
// "compileOnSave": true
|
||||
// }`
|
||||
// };
|
||||
moduleFile1.content = `export var T: number;export function Foo() { };`;
|
||||
verifyAffectedFiles([moduleFile1]);
|
||||
});
|
||||
|
||||
// const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer2, configFile2, configFile, libFile]);
|
||||
// const typingsInstaller = createTestTypingsInstaller(host);
|
||||
// const session = createSession(host, typingsInstaller);
|
||||
it("should always return the file itself if '--out' or '--outFile' is specified", () => {
|
||||
const outFilePath = "/a/b/out.js";
|
||||
const {
|
||||
moduleFile1, verifyAffectedFiles
|
||||
} = getInitialState({
|
||||
configObj: { compilerOptions: { module: "system", outFile: outFilePath } },
|
||||
getEmitLine: (_, host) => getEmittedLineForSingleFileOutput(outFilePath, host)
|
||||
});
|
||||
|
||||
// openFilesForSession([moduleFile1, file1Consumer1], session);
|
||||
// sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer2] }]);
|
||||
//});
|
||||
moduleFile1.content = `export var T: number;export function Foo() { };`;
|
||||
verifyAffectedFiles([moduleFile1]);
|
||||
});
|
||||
|
||||
//it("should always return the file itself if '--isolatedModules' is specified", () => {
|
||||
// configFile = {
|
||||
// path: "/a/b/tsconfig.json",
|
||||
// content: `{
|
||||
// "compileOnSave": true,
|
||||
// "compilerOptions": {
|
||||
// "isolatedModules": true
|
||||
// }
|
||||
// }`
|
||||
// };
|
||||
it("should return cascaded affected file list", () => {
|
||||
const file1Consumer1Consumer1: FileOrFolder = {
|
||||
path: "/a/b/file1Consumer1Consumer1.ts",
|
||||
content: `import {y} from "./file1Consumer1";`
|
||||
};
|
||||
const {
|
||||
moduleFile1, file1Consumer1, file1Consumer2, verifyAffectedFiles, getFile
|
||||
} = getInitialState({
|
||||
getAdditionalFileOrFolder: () => [file1Consumer1Consumer1]
|
||||
});
|
||||
|
||||
// const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
|
||||
// const typingsInstaller = createTestTypingsInstaller(host);
|
||||
// const session = createSession(host, typingsInstaller);
|
||||
// openFilesForSession([moduleFile1], session);
|
||||
const file1Consumer1Consumer1Emit = getFile(file1Consumer1Consumer1.path);
|
||||
file1Consumer1.content += "export var T: number;";
|
||||
verifyAffectedFiles([file1Consumer1, file1Consumer1Consumer1Emit]);
|
||||
|
||||
// const file1ChangeShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
|
||||
// file: moduleFile1.path,
|
||||
// line: 1,
|
||||
// offset: 27,
|
||||
// endLine: 1,
|
||||
// endOffset: 27,
|
||||
// insertString: `Point,`
|
||||
// });
|
||||
// session.executeCommand(file1ChangeShapeRequest);
|
||||
// sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
|
||||
//});
|
||||
// Doesnt change the shape of file1Consumer1
|
||||
moduleFile1.content = `export var T: number;export function Foo() { };`;
|
||||
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2]);
|
||||
|
||||
//it("should always return the file itself if '--out' or '--outFile' is specified", () => {
|
||||
// configFile = {
|
||||
// path: "/a/b/tsconfig.json",
|
||||
// content: `{
|
||||
// "compileOnSave": true,
|
||||
// "compilerOptions": {
|
||||
// "module": "system",
|
||||
// "outFile": "/a/b/out.js"
|
||||
// }
|
||||
// }`
|
||||
// };
|
||||
// Change both files before the timeout
|
||||
file1Consumer1.content += "export var T2: number;";
|
||||
moduleFile1.content = `export var T2: number;export function Foo() { };`;
|
||||
verifyAffectedFiles([moduleFile1, file1Consumer1, file1Consumer2, file1Consumer1Consumer1Emit]);
|
||||
});
|
||||
|
||||
// const host = createServerHost([moduleFile1, file1Consumer1, configFile, libFile]);
|
||||
// const typingsInstaller = createTestTypingsInstaller(host);
|
||||
// const session = createSession(host, typingsInstaller);
|
||||
// openFilesForSession([moduleFile1], session);
|
||||
it("should work fine for files with circular references", () => {
|
||||
// TODO: do not exit on such errors? Just continue to watch the files for update in watch mode
|
||||
|
||||
// const file1ChangeShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
|
||||
// file: moduleFile1.path,
|
||||
// line: 1,
|
||||
// offset: 27,
|
||||
// endLine: 1,
|
||||
// endOffset: 27,
|
||||
// insertString: `Point,`
|
||||
// });
|
||||
// session.executeCommand(file1ChangeShapeRequest);
|
||||
// sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1] }]);
|
||||
//});
|
||||
const file1: FileOrFolder = {
|
||||
path: "/a/b/file1.ts",
|
||||
content: `
|
||||
/// <reference path="./file2.ts" />
|
||||
export var t1 = 10;`
|
||||
};
|
||||
const file2: FileOrFolder = {
|
||||
path: "/a/b/file2.ts",
|
||||
content: `
|
||||
/// <reference path="./file1.ts" />
|
||||
export var t2 = 10;`
|
||||
};
|
||||
const {
|
||||
configFile,
|
||||
getFile,
|
||||
verifyAffectedFiles
|
||||
} = getInitialState({
|
||||
firstCompilationEmitFiles: [file1.path, file2.path],
|
||||
getAdditionalFileOrFolder: () => [file1, file2],
|
||||
firstReloadFileList: [libFile.path, file1.path, file2.path, configFilePath]
|
||||
});
|
||||
const file1Emit = getFile(file1.path), file2Emit = getFile(file2.path);
|
||||
|
||||
//it("should return cascaded affected file list", () => {
|
||||
// const file1Consumer1Consumer1: FileOrFolder = {
|
||||
// path: "/a/b/file1Consumer1Consumer1.ts",
|
||||
// content: `import {y} from "./file1Consumer1";`
|
||||
// };
|
||||
// const host = createServerHost([moduleFile1, file1Consumer1, file1Consumer1Consumer1, globalFile3, configFile, libFile]);
|
||||
// const typingsInstaller = createTestTypingsInstaller(host);
|
||||
// const session = createSession(host, typingsInstaller);
|
||||
file1Emit.content += "export var t3 = 10;";
|
||||
verifyAffectedFiles([file1Emit, file2Emit], [file1, file2, libFile, configFile]);
|
||||
|
||||
// openFilesForSession([moduleFile1, file1Consumer1], session);
|
||||
// sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]);
|
||||
});
|
||||
|
||||
// const changeFile1Consumer1ShapeRequest = makeSessionRequest<server.protocol.ChangeRequestArgs>(CommandNames.Change, {
|
||||
// file: file1Consumer1.path,
|
||||
// line: 2,
|
||||
// offset: 1,
|
||||
// endLine: 2,
|
||||
// endOffset: 1,
|
||||
// insertString: `export var T: number;`
|
||||
// });
|
||||
// session.executeCommand(changeModuleFile1ShapeRequest1);
|
||||
// session.executeCommand(changeFile1Consumer1ShapeRequest);
|
||||
// sendAffectedFileRequestAndCheckResult(session, moduleFile1FileListRequest, [{ projectFileName: configFile.path, files: [moduleFile1, file1Consumer1, file1Consumer1Consumer1] }]);
|
||||
//});
|
||||
it("should detect removed code file", () => {
|
||||
const referenceFile1: FileOrFolder = {
|
||||
path: "/a/b/referenceFile1.ts",
|
||||
content: `
|
||||
/// <reference path="./moduleFile1.ts" />
|
||||
export var x = Foo();`
|
||||
};
|
||||
const {
|
||||
configFile,
|
||||
getFile,
|
||||
verifyAffectedFiles
|
||||
} = getInitialState({
|
||||
firstCompilationEmitFiles: [referenceFile1.path, moduleFile1Path],
|
||||
getAdditionalFileOrFolder: () => [referenceFile1],
|
||||
firstReloadFileList: [libFile.path, referenceFile1.path, moduleFile1Path, configFilePath]
|
||||
});
|
||||
|
||||
//it("should work fine for files with circular references", () => {
|
||||
// const file1: FileOrFolder = {
|
||||
// path: "/a/b/file1.ts",
|
||||
// content: `
|
||||
// /// <reference path="./file2.ts" />
|
||||
// export var t1 = 10;`
|
||||
// };
|
||||
// const file2: FileOrFolder = {
|
||||
// path: "/a/b/file2.ts",
|
||||
// content: `
|
||||
// /// <reference path="./file1.ts" />
|
||||
// export var t2 = 10;`
|
||||
// };
|
||||
// const host = createServerHost([file1, file2, configFile]);
|
||||
// const typingsInstaller = createTestTypingsInstaller(host);
|
||||
// const session = createSession(host, typingsInstaller);
|
||||
const referenceFile1Emit = getFile(referenceFile1.path);
|
||||
verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]);
|
||||
});
|
||||
|
||||
// openFilesForSession([file1, file2], session);
|
||||
// const file1AffectedListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
|
||||
// sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [{ projectFileName: configFile.path, files: [file1, file2] }]);
|
||||
//});
|
||||
it("should detect non-existing code file", () => {
|
||||
const referenceFile1: FileOrFolder = {
|
||||
path: "/a/b/referenceFile1.ts",
|
||||
content: `
|
||||
/// <reference path="./moduleFile2.ts" />
|
||||
export var x = Foo();`
|
||||
};
|
||||
const {
|
||||
configFile,
|
||||
moduleFile2,
|
||||
getFile,
|
||||
verifyAffectedFiles
|
||||
} = getInitialState({
|
||||
firstCompilationEmitFiles: [referenceFile1.path],
|
||||
getAdditionalFileOrFolder: () => [referenceFile1],
|
||||
firstReloadFileList: [libFile.path, referenceFile1.path, configFilePath]
|
||||
});
|
||||
|
||||
//it("should return results for all projects if not specifying projectFileName", () => {
|
||||
// const file1: FileOrFolder = { path: "/a/b/file1.ts", content: "export var t = 10;" };
|
||||
// const file2: FileOrFolder = { path: "/a/b/file2.ts", content: `import {t} from "./file1"; var t2 = 11;` };
|
||||
// const file3: FileOrFolder = { path: "/a/c/file2.ts", content: `import {t} from "../b/file1"; var t3 = 11;` };
|
||||
// const configFile1: FileOrFolder = { path: "/a/b/tsconfig.json", content: `{ "compileOnSave": true }` };
|
||||
// const configFile2: FileOrFolder = { path: "/a/c/tsconfig.json", content: `{ "compileOnSave": true }` };
|
||||
const referenceFile1Emit = getFile(referenceFile1.path);
|
||||
referenceFile1Emit.content += "export var yy = Foo();";
|
||||
verifyAffectedFiles([referenceFile1Emit], [libFile, referenceFile1Emit, configFile]);
|
||||
|
||||
// const host = createServerHost([file1, file2, file3, configFile1, configFile2]);
|
||||
// const session = createSession(host);
|
||||
|
||||
// openFilesForSession([file1, file2, file3], session);
|
||||
// const file1AffectedListRequest = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: file1.path });
|
||||
|
||||
// sendAffectedFileRequestAndCheckResult(session, file1AffectedListRequest, [
|
||||
// { projectFileName: configFile1.path, files: [file1, file2] },
|
||||
// { projectFileName: configFile2.path, files: [file1, file3] }
|
||||
// ]);
|
||||
//});
|
||||
|
||||
//it("should detect removed code file", () => {
|
||||
// const referenceFile1: FileOrFolder = {
|
||||
// path: "/a/b/referenceFile1.ts",
|
||||
// content: `
|
||||
// /// <reference path="./moduleFile1.ts" />
|
||||
// export var x = Foo();`
|
||||
// };
|
||||
// const host = createServerHost([moduleFile1, referenceFile1, configFile]);
|
||||
// const session = createSession(host);
|
||||
|
||||
// openFilesForSession([referenceFile1], session);
|
||||
// host.reloadFS([referenceFile1, configFile]);
|
||||
|
||||
// const request = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
|
||||
// sendAffectedFileRequestAndCheckResult(session, request, [
|
||||
// { projectFileName: configFile.path, files: [referenceFile1] }
|
||||
// ]);
|
||||
// const requestForMissingFile = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: moduleFile1.path });
|
||||
// sendAffectedFileRequestAndCheckResult(session, requestForMissingFile, []);
|
||||
//});
|
||||
|
||||
//it("should detect non-existing code file", () => {
|
||||
// const referenceFile1: FileOrFolder = {
|
||||
// path: "/a/b/referenceFile1.ts",
|
||||
// content: `
|
||||
// /// <reference path="./moduleFile2.ts" />
|
||||
// export var x = Foo();`
|
||||
// };
|
||||
// const host = createServerHost([referenceFile1, configFile]);
|
||||
// const session = createSession(host);
|
||||
|
||||
// openFilesForSession([referenceFile1], session);
|
||||
// const request = makeSessionRequest<server.protocol.FileRequestArgs>(CommandNames.CompileOnSaveAffectedFileList, { file: referenceFile1.path });
|
||||
// sendAffectedFileRequestAndCheckResult(session, request, [
|
||||
// { projectFileName: configFile.path, files: [referenceFile1] }
|
||||
// ]);
|
||||
//});
|
||||
// Create module File2 and see both files are saved
|
||||
verifyAffectedFiles([referenceFile1Emit, moduleFile2], [libFile, moduleFile2, referenceFile1Emit, configFile]);
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -130,7 +130,7 @@ namespace ts.server {
|
||||
/*@internal*/
|
||||
lsHost: LSHost;
|
||||
|
||||
builder: Builder<EmitOutput>;
|
||||
builder: Builder;
|
||||
/**
|
||||
* Set of files names that were updated since the last call to getChangesSinceVersion.
|
||||
*/
|
||||
@ -250,7 +250,7 @@ namespace ts.server {
|
||||
if (!this.builder) {
|
||||
this.builder = createBuilder(
|
||||
this.projectService.toCanonicalFileName,
|
||||
(_program, sourceFile, emitOnlyDts) => this.getFileEmitOutput(sourceFile, emitOnlyDts),
|
||||
(_program, sourceFile, emitOnlyDts, isDetailed) => this.getFileEmitOutput(sourceFile, emitOnlyDts, isDetailed),
|
||||
data => this.projectService.host.createHash(data),
|
||||
sourceFile => !this.projectService.getScriptInfoForPath(sourceFile.path).hasMixedContent
|
||||
);
|
||||
@ -418,11 +418,11 @@ namespace ts.server {
|
||||
});
|
||||
}
|
||||
|
||||
private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean) {
|
||||
private getFileEmitOutput(sourceFile: SourceFile, emitOnlyDtsFiles: boolean, isDetailed: boolean) {
|
||||
if (!this.languageServiceEnabled) {
|
||||
return undefined;
|
||||
}
|
||||
return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles);
|
||||
return this.getLanguageService().getEmitOutput(sourceFile.fileName, emitOnlyDtsFiles, isDetailed);
|
||||
}
|
||||
|
||||
getFileNames(excludeFilesFromExternalLibraries?: boolean, excludeConfigFiles?: boolean) {
|
||||
|
||||
@ -1468,12 +1468,12 @@ namespace ts {
|
||||
return ts.NavigateTo.getNavigateToItems(sourceFiles, program.getTypeChecker(), cancellationToken, searchValue, maxResultCount, excludeDtsFiles);
|
||||
}
|
||||
|
||||
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput {
|
||||
function getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean) {
|
||||
synchronizeHostData();
|
||||
|
||||
const sourceFile = getValidSourceFile(fileName);
|
||||
const customTransformers = host.getCustomTransformers && host.getCustomTransformers();
|
||||
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, cancellationToken, customTransformers);
|
||||
return getFileEmitOutput(program, sourceFile, emitOnlyDtsFiles, isDetailed, cancellationToken, customTransformers);
|
||||
}
|
||||
|
||||
// Signature help
|
||||
|
||||
@ -275,7 +275,7 @@ namespace ts {
|
||||
getApplicableRefactors(fileName: string, positionOrRaneg: number | TextRange): ApplicableRefactorInfo[];
|
||||
getEditsForRefactor(fileName: string, formatOptions: FormatCodeSettings, positionOrRange: number | TextRange, refactorName: string, actionName: string): RefactorEditInfo | undefined;
|
||||
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean): EmitOutput;
|
||||
getEmitOutput(fileName: string, emitOnlyDtsFiles?: boolean, isDetailed?: boolean): EmitOutput | EmitOutputDetailed;
|
||||
|
||||
getProgram(): Program;
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user