mirror of
https://github.com/microsoft/TypeScript.git
synced 2026-05-16 15:45:27 -05:00
Rename through all projects with the same file symLink
This commit is contained in:
@@ -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);
|
||||
@@ -768,7 +765,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
|
||||
@@ -785,7 +782,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();
|
||||
@@ -1019,11 +1016,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) {
|
||||
@@ -1736,6 +1741,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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user