Modify the api in builder so that it tracks changed files

This commit is contained in:
Sheetal Nandi 2017-08-03 00:27:46 -07:00
parent 2dd6aed654
commit 89c61e797c
8 changed files with 320 additions and 304 deletions

View File

@ -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());
}
}
}

View File

@ -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);

View File

@ -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) {

View File

@ -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);
}
});
}

View File

@ -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]);
});
});
}

View File

@ -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) {

View File

@ -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

View File

@ -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;