Skip to content

Commit 4212484

Browse files
authored
Open bigger set of configured projects when opening composite project for operations that operate over multiple projects like rename (microsoft#33287)
* Add isInferredProject, isConfiguredProject and isExternalProject * Skip refreshing configured project on change of config file if its not loaded * Open a tree of projects when doing findAllRefs or rename operations * Fix addToSeen project key * Refactor combineProjectsOutputWorker * if the definition is local, no need to load and look in other projects * Add disableSearchSolution as option to disable looking for solution * Rename the option to disableSolutionSearching
1 parent f1f8746 commit 4212484

File tree

17 files changed

+584
-83
lines changed

17 files changed

+584
-83
lines changed

src/compiler/checker.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -612,6 +612,7 @@ namespace ts {
612612
},
613613

614614
getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
615+
isDeclarationVisible,
615616
};
616617

617618
function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {

src/compiler/commandLineParser.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -792,9 +792,17 @@ namespace ts {
792792
{
793793
name: "disableSourceOfProjectReferenceRedirect",
794794
type: "boolean",
795+
isTSConfigOnly: true,
795796
category: Diagnostics.Advanced_Options,
796797
description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects
797798
},
799+
{
800+
name: "disableSolutionSearching",
801+
type: "boolean",
802+
isTSConfigOnly: true,
803+
category: Diagnostics.Advanced_Options,
804+
description: Diagnostics.Disable_solution_searching_for_this_project
805+
},
798806
{
799807
name: "noImplicitUseStrict",
800808
type: "boolean",

src/compiler/core.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,17 @@ namespace ts {
498498
};
499499
}
500500

501+
export function mapDefinedMap<T, U>(map: ReadonlyMap<T>, mapValue: (value: T, key: string) => U | undefined, mapKey: (key: string) => string = identity): Map<U> {
502+
const result = createMap<U>();
503+
map.forEach((value, key) => {
504+
const mapped = mapValue(value, key);
505+
if (mapped !== undefined) {
506+
result.set(mapKey(key), mapped);
507+
}
508+
});
509+
return result;
510+
}
511+
501512
export const emptyIterator: Iterator<never> = { next: () => ({ value: undefined as never, done: true }) };
502513

503514
export function singleIterator<T>(value: T): Iterator<T> {

src/compiler/diagnosticMessages.json

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4156,6 +4156,10 @@
41564156
"category": "Message",
41574157
"code": 6223
41584158
},
4159+
"Disable solution searching for this project.": {
4160+
"category": "Message",
4161+
"code": 6224
4162+
},
41594163

41604164
"Projects to reference": {
41614165
"category": "Message",

src/compiler/types.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3533,6 +3533,7 @@ namespace ts {
35333533
runWithCancellationToken<T>(token: CancellationToken, cb: (checker: TypeChecker) => T): T;
35343534

35353535
/* @internal */ getLocalTypeParametersOfClassOrInterfaceOrTypeAlias(symbol: Symbol): readonly TypeParameter[] | undefined;
3536+
/* @internal */ isDeclarationVisible(node: Declaration | AnyImportSyntax): boolean;
35363537
}
35373538

35383539
/* @internal */
@@ -4956,6 +4957,7 @@ namespace ts {
49564957
/* @internal */ extendedDiagnostics?: boolean;
49574958
disableSizeLimit?: boolean;
49584959
disableSourceOfProjectReferenceRedirect?: boolean;
4960+
disableSolutionSearching?: boolean;
49594961
downlevelIteration?: boolean;
49604962
emitBOM?: boolean;
49614963
emitDecoratorMetadata?: boolean;

src/server/editorServices.ts

Lines changed: 181 additions & 35 deletions
Large diffs are not rendered by default.

src/server/project.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1248,9 +1248,8 @@ namespace ts.server {
12481248
}
12491249

12501250
filesToString(writeProjectFileNames: boolean) {
1251-
if (!this.program) {
1252-
return "\tFiles (0)\n";
1253-
}
1251+
if (this.isInitialLoadPending()) return "\tFiles (0) InitialLoadPending\n";
1252+
if (!this.program) return "\tFiles (0) NoProgram\n";
12541253
const sourceFiles = this.program.getSourceFiles();
12551254
let strBuilder = `\tFiles (${sourceFiles.length})\n`;
12561255
if (writeProjectFileNames) {
@@ -1705,10 +1704,15 @@ namespace ts.server {
17051704

17061705
private projectReferences: readonly ProjectReference[] | undefined;
17071706

1707+
/** Potential project references before the project is actually loaded (read config file) */
1708+
/*@internal*/
1709+
potentialProjectReferences: Map<true> | undefined;
1710+
17081711
/*@internal*/
17091712
projectOptions?: ProjectOptions | true;
17101713

1711-
protected isInitialLoadPending: () => boolean = returnTrue;
1714+
/*@internal*/
1715+
isInitialLoadPending: () => boolean = returnTrue;
17121716

17131717
/*@internal*/
17141718
sendLoadingProjectFinish = false;
@@ -1933,12 +1937,13 @@ namespace ts.server {
19331937

19341938
updateReferences(refs: readonly ProjectReference[] | undefined) {
19351939
this.projectReferences = refs;
1940+
this.potentialProjectReferences = undefined;
19361941
}
19371942

19381943
/*@internal*/
1939-
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined {
1940-
const program = this.getCurrentProgram();
1941-
return program && program.forEachResolvedProjectReference(cb);
1944+
setPotentialProjectReference(canonicalConfigPath: NormalizedPath) {
1945+
Debug.assert(this.isInitialLoadPending());
1946+
(this.potentialProjectReferences || (this.potentialProjectReferences = createMap())).set(canonicalConfigPath, true);
19421947
}
19431948

19441949
/*@internal*/

src/server/scriptInfo.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -497,9 +497,9 @@ namespace ts.server {
497497
// - first configured project
498498
// - first external project
499499
// - first inferred project
500-
let firstExternalProject;
501-
let firstConfiguredProject;
502-
let firstNonSourceOfProjectReferenceRedirect;
500+
let firstExternalProject: ExternalProject | undefined;
501+
let firstConfiguredProject: ConfiguredProject | undefined;
502+
let firstNonSourceOfProjectReferenceRedirect: ConfiguredProject | undefined;
503503
let defaultConfiguredProject: ConfiguredProject | false | undefined;
504504
for (let index = 0; index < this.containingProjects.length; index++) {
505505
const project = this.containingProjects[index];

src/server/session.ts

Lines changed: 29 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -284,6 +284,7 @@ namespace ts.server {
284284

285285
function combineProjectOutputFromEveryProject<T>(projectService: ProjectService, action: (project: Project) => readonly T[], areEqual: (a: T, b: T) => boolean) {
286286
const outputs: T[] = [];
287+
projectService.loadAncestorProjectTree();
287288
projectService.forEachEnabledProject(project => {
288289
const theseOutputs = action(project);
289290
outputs.push(...theseOutputs.filter(output => !outputs.some(o => areEqual(o, output))));
@@ -299,7 +300,7 @@ namespace ts.server {
299300
resultsEqual: (a: T, b: T) => boolean,
300301
): T[] {
301302
const outputs: T[] = [];
302-
combineProjectOutputWorker<undefined>(
303+
combineProjectOutputWorker(
303304
projects,
304305
defaultProject,
305306
/*initialLocation*/ undefined,
@@ -310,7 +311,7 @@ namespace ts.server {
310311
}
311312
}
312313
},
313-
/*getDefinition*/ undefined);
314+
);
314315
return outputs;
315316
}
316317

@@ -324,7 +325,7 @@ namespace ts.server {
324325
): readonly RenameLocation[] {
325326
const outputs: RenameLocation[] = [];
326327

327-
combineProjectOutputWorker<DocumentPosition>(
328+
combineProjectOutputWorker(
328329
projects,
329330
defaultProject,
330331
initialLocation,
@@ -335,7 +336,6 @@ namespace ts.server {
335336
}
336337
}
337338
},
338-
() => getDefinitionLocation(defaultProject, initialLocation)
339339
);
340340

341341
return outputs;
@@ -344,7 +344,7 @@ namespace ts.server {
344344
function getDefinitionLocation(defaultProject: Project, initialLocation: DocumentPosition): DocumentPosition | undefined {
345345
const infos = defaultProject.getLanguageService().getDefinitionAtPosition(initialLocation.fileName, initialLocation.pos);
346346
const info = infos && firstOrUndefined(infos);
347-
return info && { fileName: info.fileName, pos: info.textSpan.start };
347+
return info && !info.isLocal ? { fileName: info.fileName, pos: info.textSpan.start } : undefined;
348348
}
349349

350350
function combineProjectOutputForReferences(
@@ -354,7 +354,7 @@ namespace ts.server {
354354
): readonly ReferencedSymbol[] {
355355
const outputs: ReferencedSymbol[] = [];
356356

357-
combineProjectOutputWorker<DocumentPosition>(
357+
combineProjectOutputWorker(
358358
projects,
359359
defaultProject,
360360
initialLocation,
@@ -384,7 +384,6 @@ namespace ts.server {
384384
}
385385
}
386386
},
387-
() => getDefinitionLocation(defaultProject, initialLocation)
388387
);
389388

390389
return outputs.filter(o => o.references.length !== 0);
@@ -417,8 +416,7 @@ namespace ts.server {
417416
projects: Projects,
418417
defaultProject: Project,
419418
initialLocation: TLocation,
420-
cb: CombineProjectOutputCallback<TLocation>,
421-
getDefinition: (() => DocumentPosition | undefined) | undefined,
419+
cb: CombineProjectOutputCallback<TLocation>
422420
): void {
423421
const projectService = defaultProject.projectService;
424422
let toDo: ProjectAndLocation<TLocation>[] | undefined;
@@ -430,15 +428,18 @@ namespace ts.server {
430428
});
431429

432430
// After initial references are collected, go over every other project and see if it has a reference for the symbol definition.
433-
if (getDefinition) {
434-
const memGetDefinition = memoize(getDefinition);
435-
projectService.forEachEnabledProject(project => {
436-
if (!addToSeen(seenProjects, project.projectName)) return;
437-
const definition = mapDefinitionInProject(memGetDefinition(), defaultProject, project);
438-
if (definition) {
439-
toDo = callbackProjectAndLocation<TLocation>({ project, location: definition as TLocation }, projectService, toDo, seenProjects, cb);
440-
}
441-
});
431+
if (initialLocation) {
432+
const defaultDefinition = getDefinitionLocation(defaultProject, initialLocation!);
433+
if (defaultDefinition) {
434+
projectService.loadAncestorProjectTree(seenProjects);
435+
projectService.forEachEnabledProject(project => {
436+
if (!addToSeen(seenProjects, project)) return;
437+
const definition = mapDefinitionInProject(defaultDefinition, defaultProject, project);
438+
if (definition) {
439+
toDo = callbackProjectAndLocation<TLocation>({ project, location: definition as TLocation }, projectService, toDo, seenProjects, cb);
440+
}
441+
});
442+
}
442443
}
443444

444445
while (toDo && toDo.length) {
@@ -487,7 +488,7 @@ namespace ts.server {
487488
// If this is not the file we were actually looking, return rest of the toDo
488489
if (isLocationProjectReferenceRedirect(project, location)) return toDo;
489490
cb(projectAndLocation, (project, location) => {
490-
seenProjects.set(projectAndLocation.project.projectName, true);
491+
addToSeen(seenProjects, projectAndLocation.project);
491492
const originalLocation = projectService.getOriginalLocationEnsuringConfiguredProject(project, location);
492493
if (!originalLocation) return undefined;
493494

@@ -509,7 +510,15 @@ namespace ts.server {
509510
}
510511

511512
function addToTodo<TLocation extends DocumentPosition | undefined>(projectAndLocation: ProjectAndLocation<TLocation>, toDo: Push<ProjectAndLocation<TLocation>>, seenProjects: Map<true>): void {
512-
if (addToSeen(seenProjects, projectAndLocation.project.projectName)) toDo.push(projectAndLocation);
513+
if (addToSeen(seenProjects, projectAndLocation.project)) toDo.push(projectAndLocation);
514+
}
515+
516+
function addToSeen(seenProjects: Map<true>, project: Project) {
517+
return ts.addToSeen(seenProjects, getProjectKey(project));
518+
}
519+
520+
function getProjectKey(project: Project) {
521+
return isConfiguredProject(project) ? project.canonicalConfigFilePath : project.getProjectName();
513522
}
514523

515524
function documentSpanLocation({ fileName, textSpan }: DocumentSpan): DocumentPosition {

src/services/goToDefinition.ts

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ namespace ts.GoToDefinition {
1212
}
1313
const { parent } = node;
1414

15+
const typeChecker = program.getTypeChecker();
16+
1517
// Labels
1618
if (isJumpStatementTarget(node)) {
1719
const label = getTargetLabel(node.parent, node.text);
18-
return label ? [createDefinitionInfoFromName(label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
20+
return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
1921
}
2022

21-
const typeChecker = program.getTypeChecker();
2223
const symbol = getSymbol(node, typeChecker);
2324

2425
// Could not find a symbol e.g. node is string or number keyword,
@@ -275,11 +276,11 @@ namespace ts.GoToDefinition {
275276
const symbolName = checker.symbolToString(symbol); // Do not get scoped name, just the name of the symbol
276277
const symbolKind = SymbolDisplay.getSymbolKind(checker, symbol, node);
277278
const containerName = symbol.parent ? checker.symbolToString(symbol.parent, node) : "";
278-
return createDefinitionInfoFromName(declaration, symbolKind, symbolName, containerName);
279+
return createDefinitionInfoFromName(checker, declaration, symbolKind, symbolName, containerName);
279280
}
280281

281282
/** Creates a DefinitionInfo directly from the name of a declaration. */
282-
function createDefinitionInfoFromName(declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
283+
function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
283284
const name = getNameOfDeclaration(declaration) || declaration;
284285
const sourceFile = name.getSourceFile();
285286
const textSpan = createTextSpanFromNode(name, sourceFile);
@@ -294,7 +295,8 @@ namespace ts.GoToDefinition {
294295
textSpan,
295296
sourceFile,
296297
FindAllReferences.getContextNode(declaration)
297-
)
298+
),
299+
isLocal: !checker.isDeclarationVisible(declaration)
298300
};
299301
}
300302

src/services/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -810,6 +810,7 @@ namespace ts {
810810
name: string;
811811
containerKind: ScriptElementKind;
812812
containerName: string;
813+
/* @internal */ isLocal?: boolean;
813814
}
814815

815816
export interface DefinitionInfoAndBoundSpan {

src/testRunner/unittests/tsserver/declarationFileMaps.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,8 @@ namespace ts.projectSystem {
126126

127127
openFilesForSession([userTs], session);
128128
const service = session.getProjectService();
129-
checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 1 } : { inferredProjects: 1 });
129+
// If config file then userConfig project and bConfig project since it is referenced
130+
checkNumberOfProjects(service, addUserTsConfig ? { configuredProjects: 2 } : { inferredProjects: 1 });
130131
return session;
131132
}
132133

@@ -224,15 +225,15 @@ namespace ts.projectSystem {
224225
})
225226
],
226227
});
227-
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
228+
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
228229
verifyUserTsConfigProject(session);
229230

230231
// Navigate to the definition
231232
closeFilesForSession([userTs], session);
232233
openFilesForSession([aTs], session);
233234

234235
// UserTs configured project should be alive
235-
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
236+
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 });
236237
verifyUserTsConfigProject(session);
237238
verifyATsConfigProject(session);
238239

@@ -421,7 +422,7 @@ namespace ts.projectSystem {
421422
const session = createSession(createServerHost([aTs, aTsconfig, bTs, bTsconfig, aDts, aDtsMap]));
422423
checkDeclarationFiles(aTs, session, [aDtsMap, aDts]);
423424
openFilesForSession([bTs], session);
424-
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
425+
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 }); // configured project of b is alive since a references b
425426

426427
const responseFull = executeSessionRequest<ReferencesFullRequest, ReferencesFullResponse>(session, protocol.CommandTypes.ReferencesFull, protocolFileLocationFromSubstring(bTs, "f()"));
427428

src/testRunner/unittests/tsserver/helpers.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -495,8 +495,8 @@ namespace ts.projectSystem {
495495
checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
496496
}
497497

498-
export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location {
499-
const start = str.indexOf(substring);
498+
export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.Location {
499+
const start = nthIndexOf(str, substring, options ? options.index : 0);
500500
Debug.assert(start !== -1);
501501
return protocolToLocation(str)(start);
502502
}
@@ -587,8 +587,8 @@ namespace ts.projectSystem {
587587
return createTextSpan(start, substring.length);
588588
}
589589

590-
export function protocolFileLocationFromSubstring(file: File, substring: string): protocol.FileLocationRequestArgs {
591-
return { file: file.path, ...protocolLocationFromSubstring(file.content, substring) };
590+
export function protocolFileLocationFromSubstring(file: File, substring: string, options?: SpanFromSubstringOptions): protocol.FileLocationRequestArgs {
591+
return { file: file.path, ...protocolLocationFromSubstring(file.content, substring, options) };
592592
}
593593

594594
export interface SpanFromSubstringOptions {
@@ -732,14 +732,15 @@ namespace ts.projectSystem {
732732

733733
export interface MakeReferenceItem extends DocumentSpanFromSubstring {
734734
isDefinition: boolean;
735+
isWriteAccess?: boolean;
735736
lineText: string;
736737
}
737738

738-
export function makeReferenceItem({ isDefinition, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem {
739+
export function makeReferenceItem({ isDefinition, isWriteAccess, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem {
739740
return {
740741
...protocolFileSpanWithContextFromSubstring(rest),
741742
isDefinition,
742-
isWriteAccess: isDefinition,
743+
isWriteAccess: isWriteAccess === undefined ? isDefinition : isWriteAccess,
743744
lineText,
744745
};
745746
}

0 commit comments

Comments
 (0)