Merge pull request #21171 from Microsoft/renameSymLinks

Rename through all projects with same file through symLink
This commit is contained in:
Sheetal Nandi 2018-01-17 15:08:27 -08:00 committed by GitHub
commit 9ad9dc106c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 391 additions and 64 deletions

View file

@ -524,7 +524,12 @@ namespace ts {
process.exit(exitCode);
},
realpath(path: string): string {
return _fs.realpathSync(path);
try {
return _fs.realpathSync(path);
}
catch {
return path;
}
},
debugMode: some(<string[]>process.execArgv, arg => /^--(inspect|debug)(-brk)?(=\d+)?$/i.test(arg)),
tryEnableSourceMapsForHost() {

View file

@ -6616,4 +6616,118 @@ namespace ts.projectSystem {
checkProjectActualFiles(project, [file.path]);
});
});
describe("tsserverProjectSystem with symLinks", () => {
it("rename in common file renames all project", () => {
const projects = "/users/username/projects";
const folderA = `${projects}/a`;
const aFile: FileOrFolder = {
path: `${folderA}/a.ts`,
content: `import {C} from "./c/fc"; console.log(C)`
};
const aTsconfig: FileOrFolder = {
path: `${folderA}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { module: "commonjs" } })
};
const aC: FileOrFolder = {
path: `${folderA}/c`,
symLink: "../c"
};
const aFc = `${folderA}/c/fc.ts`;
const folderB = `${projects}/b`;
const bFile: FileOrFolder = {
path: `${folderB}/b.ts`,
content: `import {C} from "./c/fc"; console.log(C)`
};
const bTsconfig: FileOrFolder = {
path: `${folderB}/tsconfig.json`,
content: JSON.stringify({ compilerOptions: { module: "commonjs" } })
};
const bC: FileOrFolder = {
path: `${folderB}/c`,
symLink: "../c"
};
const bFc = `${folderB}/c/fc.ts`;
const folderC = `${projects}/c`;
const cFile: FileOrFolder = {
path: `${folderC}/fc.ts`,
content: `export const C = 8`
};
const files = [cFile, libFile, aFile, aTsconfig, aC, bFile, bTsconfig, bC];
const host = createServerHost(files);
const session = createSession(host);
const projectService = session.getProjectService();
debugger;
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: aFile.path,
projectRootPath: folderA
}
});
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: bFile.path,
projectRootPath: folderB
}
});
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: aFc,
projectRootPath: folderA
}
});
session.executeCommandSeq<protocol.OpenRequest>({
command: protocol.CommandTypes.Open,
arguments: {
file: bFc,
projectRootPath: folderB
}
});
checkNumberOfProjects(projectService, { configuredProjects: 2 });
assert.isDefined(projectService.configuredProjects.get(aTsconfig.path));
assert.isDefined(projectService.configuredProjects.get(bTsconfig.path));
debugger;
verifyRenameResponse(session.executeCommandSeq<protocol.RenameRequest>({
command: protocol.CommandTypes.Rename,
arguments: {
file: aFc,
line: 1,
offset: 14,
findInStrings: false,
findInComments: false
}
}).response as protocol.RenameResponseBody);
function verifyRenameResponse({ info, locs }: protocol.RenameResponseBody) {
assert.isTrue(info.canRename);
assert.equal(locs.length, 4);
verifyLocations(0, aFile.path, aFc);
verifyLocations(2, bFile.path, bFc);
function verifyLocations(locStartIndex: number, firstFile: string, secondFile: string) {
assert.deepEqual(locs[locStartIndex], {
file: firstFile,
locs: [
{ start: { line: 1, offset: 39 }, end: { line: 1, offset: 40 } },
{ start: { line: 1, offset: 9 }, end: { line: 1, offset: 10 } }
]
});
assert.deepEqual(locs[locStartIndex + 1], {
file: secondFile,
locs: [
{ start: { line: 1, offset: 14 }, end: { line: 1, offset: 15 } }
]
});
}
}
});
});
}

View file

@ -70,6 +70,7 @@ interface Array<T> {}`
path: string;
content?: string;
fileSize?: number;
symLink?: string;
}
interface FSEntry {
@ -86,6 +87,10 @@ interface Array<T> {}`
entries: FSEntry[];
}
interface SymLink extends FSEntry {
symLink: string;
}
function isFolder(s: FSEntry): s is Folder {
return s && isArray((<Folder>s).entries);
}
@ -94,6 +99,10 @@ interface Array<T> {}`
return s && isString((<File>s).content);
}
function isSymLink(s: FSEntry): s is SymLink {
return s && isString((<SymLink>s).symLink);
}
function invokeWatcherCallbacks<T>(callbacks: T[], invokeCallback: (cb: T) => void): void {
if (callbacks) {
// The array copy is made to ensure that even if one of the callback removes the callbacks,
@ -316,9 +325,12 @@ interface Array<T> {}`
}
}
else {
// TODO: Changing from file => folder
// TODO: Changing from file => folder/Symlink
}
}
else if (isSymLink(currentEntry)) {
// TODO: update symlinks
}
else {
// Folder
if (isString(fileOrDirectory.content)) {
@ -339,7 +351,7 @@ interface Array<T> {}`
// If this entry is not from the new file or folder
if (!mapNewLeaves.get(path)) {
// Leaf entries that arent in new list => remove these
if (isFile(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) {
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory) || isFolder(fileOrDirectory) && fileOrDirectory.entries.length === 0) {
this.removeFileOrFolder(fileOrDirectory, folder => !mapNewLeaves.get(folder.path));
}
}
@ -387,6 +399,12 @@ interface Array<T> {}`
const baseFolder = this.ensureFolder(getDirectoryPath(file.fullPath));
this.addFileOrFolderInFolder(baseFolder, file, ignoreWatchInvokedWithTriggerAsFileCreate);
}
else if (isString(fileOrDirectory.symLink)) {
const symLink = this.toSymLink(fileOrDirectory);
Debug.assert(!this.fs.get(symLink.path));
const baseFolder = this.ensureFolder(getDirectoryPath(symLink.fullPath));
this.addFileOrFolderInFolder(baseFolder, symLink, ignoreWatchInvokedWithTriggerAsFileCreate);
}
else {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
this.ensureFolder(fullPath);
@ -414,20 +432,20 @@ interface Array<T> {}`
return folder;
}
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder, ignoreWatch?: boolean) {
private addFileOrFolderInFolder(folder: Folder, fileOrDirectory: File | Folder | SymLink, ignoreWatch?: boolean) {
folder.entries.push(fileOrDirectory);
this.fs.set(fileOrDirectory.path, fileOrDirectory);
if (ignoreWatch) {
return;
}
if (isFile(fileOrDirectory)) {
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Created);
}
this.invokeDirectoryWatcher(folder.fullPath, fileOrDirectory.fullPath);
}
private removeFileOrFolder(fileOrDirectory: File | Folder, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) {
private removeFileOrFolder(fileOrDirectory: File | Folder | SymLink, isRemovableLeafFolder: (folder: Folder) => boolean, isRenaming?: boolean) {
const basePath = getDirectoryPath(fileOrDirectory.path);
const baseFolder = this.fs.get(basePath) as Folder;
if (basePath !== fileOrDirectory.path) {
@ -436,7 +454,7 @@ interface Array<T> {}`
}
this.fs.delete(fileOrDirectory.path);
if (isFile(fileOrDirectory)) {
if (isFile(fileOrDirectory) || isSymLink(fileOrDirectory)) {
this.invokeFileWatcher(fileOrDirectory.fullPath, FileWatcherEventKind.Deleted);
}
else {
@ -503,6 +521,15 @@ interface Array<T> {}`
};
}
private toSymLink(fileOrDirectory: FileOrFolder): SymLink {
const fullPath = getNormalizedAbsolutePath(fileOrDirectory.path, this.currentDirectory);
return {
path: this.toPath(fullPath),
fullPath,
symLink: getNormalizedAbsolutePath(fileOrDirectory.symLink, getDirectoryPath(fullPath))
};
}
private toFolder(path: string): Folder {
const fullPath = getNormalizedAbsolutePath(path, this.currentDirectory);
return {
@ -512,14 +539,52 @@ interface Array<T> {}`
};
}
fileExists(s: string) {
const path = this.toFullPath(s);
return isFile(this.fs.get(path));
private getRealFsEntry<T extends FSEntry>(isFsEntry: (fsEntry: FSEntry) => fsEntry is T, path: Path, fsEntry = this.fs.get(path)): T | undefined {
if (isFsEntry(fsEntry)) {
return fsEntry;
}
if (isSymLink(fsEntry)) {
return this.getRealFsEntry(isFsEntry, this.toPath(fsEntry.symLink));
}
if (fsEntry) {
// This fs entry is something else
return undefined;
}
const realpath = this.realpath(path);
if (path !== realpath) {
return this.getRealFsEntry(isFsEntry, realpath as Path);
}
return undefined;
}
readFile(s: string) {
const fsEntry = this.fs.get(this.toFullPath(s));
return isFile(fsEntry) ? fsEntry.content : undefined;
private isFile(fsEntry: FSEntry) {
return !!this.getRealFile(fsEntry.path, fsEntry);
}
private getRealFile(path: Path, fsEntry?: FSEntry): File | undefined {
return this.getRealFsEntry(isFile, path, fsEntry);
}
private isFolder(fsEntry: FSEntry) {
return !!this.getRealFolder(fsEntry.path, fsEntry);
}
private getRealFolder(path: Path, fsEntry = this.fs.get(path)): Folder | undefined {
return this.getRealFsEntry(isFolder, path, fsEntry);
}
fileExists(s: string) {
const path = this.toFullPath(s);
return !!this.getRealFile(path);
}
readFile(s: string): string {
const fsEntry = this.getRealFile(this.toFullPath(s));
return fsEntry ? fsEntry.content : undefined;
}
getFileSize(s: string) {
@ -533,14 +598,14 @@ interface Array<T> {}`
directoryExists(s: string) {
const path = this.toFullPath(s);
return isFolder(this.fs.get(path));
return !!this.getRealFolder(path);
}
getDirectories(s: string) {
getDirectories(s: string): string[] {
const path = this.toFullPath(s);
const folder = this.fs.get(path);
if (isFolder(folder)) {
return mapDefined(folder.entries, entry => isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined);
const folder = this.getRealFolder(path);
if (folder) {
return mapDefined(folder.entries, entry => this.isFolder(entry) ? getBaseFileName(entry.fullPath) : undefined);
}
Debug.fail(folder ? "getDirectories called on file" : "getDirectories called on missing folder");
return [];
@ -550,13 +615,13 @@ interface Array<T> {}`
return ts.matchFiles(path, extensions, exclude, include, this.useCaseSensitiveFileNames, this.getCurrentDirectory(), depth, (dir) => {
const directories: string[] = [];
const files: string[] = [];
const dirEntry = this.fs.get(this.toPath(dir));
if (isFolder(dirEntry)) {
dirEntry.entries.forEach((entry) => {
if (isFolder(entry)) {
const folder = this.getRealFolder(this.toPath(dir));
if (folder) {
folder.entries.forEach((entry) => {
if (this.isFolder(entry)) {
directories.push(getBaseFileName(entry.fullPath));
}
else if (isFile(entry)) {
else if (this.isFile(entry)) {
files.push(getBaseFileName(entry.fullPath));
}
else {
@ -682,6 +747,23 @@ interface Array<T> {}`
clear(this.output);
}
realpath(s: string): string {
const fullPath = this.toNormalizedAbsolutePath(s);
const path = this.toPath(fullPath);
if (getDirectoryPath(path) === path) {
// Root
return s;
}
const dirFullPath = this.realpath(getDirectoryPath(fullPath));
const realFullPath = combinePaths(dirFullPath, getBaseFileName(fullPath));
const fsEntry = this.fs.get(this.toPath(realFullPath));
if (isSymLink(fsEntry)) {
return this.realpath(fsEntry.symLink);
}
return realFullPath;
}
readonly existMessage = "System Exit";
exitCode: number;
readonly resolvePath = (s: string) => s;

View file

@ -198,16 +198,6 @@ namespace ts.server {
}
}
/**
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
*/
export function combineProjectOutput<T>(projects: ReadonlyArray<Project>, action: (project: Project) => ReadonlyArray<T>, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean) {
const outputs = flatMap(projects, action);
return comparer
? sortAndDeduplicate(outputs, comparer, areEqual)
: deduplicate(outputs, areEqual);
}
export interface HostConfiguration {
formatCodeOptions: FormatCodeSettings;
hostInfo: string;
@ -335,6 +325,11 @@ namespace ts.server {
* Container of all known scripts
*/
private readonly filenameToScriptInfo = createMap<ScriptInfo>();
/**
* Map to the real path of the infos
*/
/* @internal */
readonly realpathToScriptInfos: MultiMap<ScriptInfo> | undefined;
/**
* maps external project file name to list of config files that were the part of this project
*/
@ -427,7 +422,9 @@ namespace ts.server {
this.typesMapLocation = (opts.typesMapLocation === undefined) ? combinePaths(this.getExecutingFilePath(), "../typesMap.json") : opts.typesMapLocation;
Debug.assert(!!this.host.createHash, "'ServerHost.createHash' is required for ProjectService");
if (this.host.realpath) {
this.realpathToScriptInfos = createMultiMap();
}
this.currentDirectory = this.host.getCurrentDirectory();
this.toCanonicalFileName = createGetCanonicalFileName(this.host.useCaseSensitiveFileNames);
this.throttledOperations = new ThrottledOperations(this.host, this.logger);
@ -759,7 +756,7 @@ namespace ts.server {
if (info.containingProjects.length === 0) {
// Orphan script info, remove it as we can always reload it on next open file request
this.stopWatchingScriptInfo(info);
this.filenameToScriptInfo.delete(info.path);
this.deleteScriptInfo(info);
}
else {
// file has been changed which might affect the set of referenced files in projects that include
@ -776,7 +773,7 @@ namespace ts.server {
// TODO: handle isOpen = true case
if (!info.isScriptOpen()) {
this.filenameToScriptInfo.delete(info.path);
this.deleteScriptInfo(info);
// capture list of projects since detachAllProjects will wipe out original list
const containingProjects = info.containingProjects.slice();
@ -1010,11 +1007,19 @@ namespace ts.server {
if (!info.isScriptOpen() && info.isOrphan()) {
// if there are not projects that include this script info - delete it
this.stopWatchingScriptInfo(info);
this.filenameToScriptInfo.delete(info.path);
this.deleteScriptInfo(info);
}
});
}
private deleteScriptInfo(info: ScriptInfo) {
this.filenameToScriptInfo.delete(info.path);
const realpath = info.getRealpathIfDifferent();
if (realpath) {
this.realpathToScriptInfos.remove(realpath, info);
}
}
private configFileExists(configFileName: NormalizedPath, canonicalConfigFilePath: string, info: ScriptInfo) {
let configFileExistenceInfo = this.configFileExistenceInfoCache.get(canonicalConfigFilePath);
if (configFileExistenceInfo) {
@ -1727,6 +1732,43 @@ namespace ts.server {
return this.getScriptInfoForNormalizedPath(toNormalizedPath(uncheckedFileName));
}
/**
* Returns the projects that contain script info through SymLink
* Note that this does not return projects in info.containingProjects
*/
/*@internal*/
getSymlinkedProjects(info: ScriptInfo): MultiMap<Project> | undefined {
let projects: MultiMap<Project> | undefined;
if (this.realpathToScriptInfos) {
const realpath = info.getRealpathIfDifferent();
if (realpath) {
forEach(this.realpathToScriptInfos.get(realpath), combineProjects);
}
forEach(this.realpathToScriptInfos.get(info.path), combineProjects);
}
return projects;
function combineProjects(toAddInfo: ScriptInfo) {
if (toAddInfo !== info) {
for (const project of toAddInfo.containingProjects) {
// Add the projects only if they can use symLink targets and not already in the list
if (project.languageServiceEnabled &&
!project.getCompilerOptions().preserveSymlinks &&
!contains(info.containingProjects, project)) {
if (!projects) {
projects = createMultiMap();
projects.add(toAddInfo.path, project);
}
else if (!forEachEntry(projects, (projs, path) => path === toAddInfo.path ? false : contains(projs, project))) {
projects.add(toAddInfo.path, project);
}
}
}
}
}
}
private watchClosedScriptInfo(info: ScriptInfo) {
Debug.assert(!info.fileWatcher);
// do not watch files with mixed content - server doesn't know how to interpret it

View file

@ -213,6 +213,10 @@ namespace ts.server {
/*@internal*/
readonly isDynamic: boolean;
/*@internal*/
/** Set to real path if path is different from info.path */
private realpath: Path | undefined;
constructor(
private readonly host: ServerHost,
readonly fileName: NormalizedPath,
@ -224,6 +228,7 @@ namespace ts.server {
this.textStorage = new TextStorage(host, fileName);
if (hasMixedContent || this.isDynamic) {
this.textStorage.reload("");
this.realpath = this.path;
}
this.scriptKind = scriptKind
? scriptKind
@ -264,6 +269,30 @@ namespace ts.server {
return this.textStorage.getSnapshot();
}
private ensureRealPath() {
if (this.realpath === undefined) {
// Default is just the path
this.realpath = this.path;
if (this.host.realpath) {
Debug.assert(!!this.containingProjects.length);
const project = this.containingProjects[0];
const realpath = this.host.realpath(this.path);
if (realpath) {
this.realpath = project.toPath(realpath);
// If it is different from this.path, add to the map
if (this.realpath !== this.path) {
project.projectService.realpathToScriptInfos.add(this.realpath, this);
}
}
}
}
}
/*@internal*/
getRealpathIfDifferent(): Path | undefined {
return this.realpath && this.realpath !== this.path ? this.realpath : undefined;
}
getFormatCodeSettings() {
return this.formatCodeSettings;
}
@ -272,6 +301,9 @@ namespace ts.server {
const isNew = !this.isAttached(project);
if (isNew) {
this.containingProjects.push(project);
if (!project.getCompilerOptions().preserveSymlinks) {
this.ensureRealPath();
}
}
return isNew;
}

View file

@ -255,6 +255,32 @@ namespace ts.server {
};
}
type Projects = ReadonlyArray<Project> | {
projects: ReadonlyArray<Project>;
symLinkedProjects: MultiMap<Project>;
};
function isProjectsArray(projects: Projects): projects is ReadonlyArray<Project> {
return !!(<ReadonlyArray<Project>>projects).length;
}
/**
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
*/
function combineProjectOutput<T, U>(defaultValue: T, getValue: (path: Path) => T, projects: Projects, action: (project: Project, value: T) => ReadonlyArray<U> | U | undefined, comparer?: (a: U, b: U) => number, areEqual?: (a: U, b: U) => boolean) {
const outputs = flatMap(isProjectsArray(projects) ? projects : projects.projects, project => action(project, defaultValue));
if (!isProjectsArray(projects) && projects.symLinkedProjects) {
projects.symLinkedProjects.forEach((projects, path) => {
const value = getValue(path as Path);
outputs.push(...flatMap(projects, project => action(project, value)));
});
}
return comparer
? sortAndDeduplicate(outputs, comparer, areEqual)
: deduplicate(outputs, areEqual);
}
export interface SessionOptions {
host: ServerHost;
cancellationToken: ServerCancellationToken;
@ -789,8 +815,9 @@ namespace ts.server {
return project.getLanguageService().getRenameInfo(file, position);
}
private getProjects(args: protocol.FileRequestArgs) {
let projects: Project[];
private getProjects(args: protocol.FileRequestArgs): Projects {
let projects: ReadonlyArray<Project>;
let symLinkedProjects: MultiMap<Project> | undefined;
if (args.projectFileName) {
const project = this.getProject(args.projectFileName);
if (project) {
@ -800,13 +827,14 @@ namespace ts.server {
else {
const scriptInfo = this.projectService.getScriptInfo(args.file);
projects = scriptInfo.containingProjects;
symLinkedProjects = this.projectService.getSymlinkedProjects(scriptInfo);
}
// filter handles case when 'projects' is undefined
projects = filter(projects, p => p.languageServiceEnabled);
if (!projects || !projects.length) {
if ((!projects || !projects.length) && !symLinkedProjects) {
return Errors.ThrowNoProject();
}
return projects;
return symLinkedProjects ? { projects, symLinkedProjects } : projects;
}
private getDefaultProject(args: protocol.FileRequestArgs) {
@ -841,8 +869,10 @@ namespace ts.server {
}
const fileSpans = combineProjectOutput(
file,
path => this.projectService.getScriptInfoForPath(path).fileName,
projects,
(project: Project) => {
(project, file) => {
const renameLocations = project.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments);
if (!renameLocations) {
return emptyArray;
@ -881,8 +911,10 @@ namespace ts.server {
}
else {
return combineProjectOutput(
file,
path => this.projectService.getScriptInfoForPath(path).fileName,
projects,
p => p.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments),
(p, file) => p.getLanguageService().findRenameLocations(file, position, args.findInStrings, args.findInComments),
/*comparer*/ undefined,
renameLocationIsEqualTo
);
@ -938,9 +970,11 @@ namespace ts.server {
const nameSpan = nameInfo.textSpan;
const nameColStart = scriptInfo.positionToLineOffset(nameSpan.start).offset;
const nameText = scriptInfo.getSnapshot().getText(nameSpan.start, textSpanEnd(nameSpan));
const refs = combineProjectOutput<protocol.ReferencesResponseItem>(
const refs = combineProjectOutput<NormalizedPath, protocol.ReferencesResponseItem>(
file,
path => this.projectService.getScriptInfoForPath(path).fileName,
projects,
(project: Project) => {
(project, file) => {
const references = project.getLanguageService().getReferencesAtPosition(file, position);
if (!references) {
return emptyArray;
@ -974,8 +1008,10 @@ namespace ts.server {
}
else {
return combineProjectOutput(
file,
path => this.projectService.getScriptInfoForPath(path).fileName,
projects,
project => project.getLanguageService().findReferences(file, position),
(project, file) => project.getLanguageService().findReferences(file, position),
/*comparer*/ undefined,
equateValues
);
@ -1240,20 +1276,25 @@ namespace ts.server {
return emptyArray;
}
const result: protocol.CompileOnSaveAffectedFileListSingleProject[] = [];
// if specified a project, we only return affected file list in this project
const projectsToSearch = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects;
for (const project of projectsToSearch) {
if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.getCompilationSettings().noEmit) {
result.push({
projectFileName: project.getProjectName(),
fileNames: project.getCompileOnSaveAffectedFileList(info),
projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out
});
const projects = args.projectFileName ? [this.projectService.findProject(args.projectFileName)] : info.containingProjects;
const symLinkedProjects = !args.projectFileName && this.projectService.getSymlinkedProjects(info);
return combineProjectOutput(
info,
path => this.projectService.getScriptInfoForPath(path),
symLinkedProjects ? { projects, symLinkedProjects } : projects,
(project, info) => {
let result: protocol.CompileOnSaveAffectedFileListSingleProject;
if (project.compileOnSaveEnabled && project.languageServiceEnabled && !project.getCompilationSettings().noEmit) {
result = {
projectFileName: project.getProjectName(),
fileNames: project.getCompileOnSaveAffectedFileList(info),
projectUsesOutFile: !!project.getCompilationSettings().outFile || !!project.getCompilationSettings().out
};
}
return result;
}
}
return result;
);
}
private emitFile(args: protocol.CompileOnSaveEmitFileRequestArgs) {
@ -1406,8 +1447,14 @@ namespace ts.server {
const fileName = args.currentFileOnly ? args.file && normalizeSlashes(args.file) : undefined;
if (simplifiedResult) {
return combineProjectOutput(
fileName,
() => undefined,
projects,
project => {
(project, file) => {
if (fileName && !file) {
return undefined;
}
const navItems = project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject());
if (!navItems) {
return emptyArray;
@ -1443,8 +1490,15 @@ namespace ts.server {
}
else {
return combineProjectOutput(
fileName,
() => undefined,
projects,
project => project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject()),
(project, file) => {
if (fileName && !file) {
return undefined;
}
return project.getLanguageService().getNavigateToItems(args.searchValue, args.maxResultCount, fileName, /*excludeDts*/ project.isNonTsProject());
},
/*comparer*/ undefined,
navigateToItemIsEqualTo);
}

View file

@ -7297,6 +7297,7 @@ declare namespace ts.server {
open(newText: string): void;
close(fileExists?: boolean): void;
getSnapshot(): IScriptSnapshot;
private ensureRealPath();
getFormatCodeSettings(): FormatCodeSettings;
attachToProject(project: Project): boolean;
isAttached(project: Project): boolean;
@ -7669,10 +7670,6 @@ declare namespace ts.server {
function convertCompilerOptions(protocolOptions: protocol.ExternalProjectCompilerOptions): CompilerOptions & protocol.CompileOnSaveMixin;
function tryConvertScriptKindName(scriptKindName: protocol.ScriptKindName | ScriptKind): ScriptKind;
function convertScriptKindName(scriptKindName: protocol.ScriptKindName): ScriptKind.Unknown | ScriptKind.JS | ScriptKind.JSX | ScriptKind.TS | ScriptKind.TSX;
/**
* This helper function processes a list of projects and return the concatenated, sortd and deduplicated output of processing each project.
*/
function combineProjectOutput<T>(projects: ReadonlyArray<Project>, action: (project: Project) => ReadonlyArray<T>, comparer?: (a: T, b: T) => number, areEqual?: (a: T, b: T) => boolean): T[];
interface HostConfiguration {
formatCodeOptions: FormatCodeSettings;
hostInfo: string;
@ -7807,6 +7804,7 @@ declare namespace ts.server {
*/
private closeOpenFile(info);
private deleteOrphanScriptInfoNotInAnyProject();
private deleteScriptInfo(info);
private configFileExists(configFileName, canonicalConfigFilePath, info);
private setConfigFileExistenceByNewConfiguredProject(project);
/**