Skip to content

Open bigger set of configured projects when opening composite project for operations that operate over multiple projects like rename #33287

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 13 commits into from
Dec 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/compiler/checker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -612,6 +612,7 @@ namespace ts {
},

getLocalTypeParametersOfClassOrInterfaceOrTypeAlias,
isDeclarationVisible,
};

function getResolvedSignatureWorker(nodeIn: CallLikeExpression, candidatesOutArray: Signature[] | undefined, argumentCount: number | undefined, checkMode: CheckMode): Signature | undefined {
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -792,9 +792,17 @@ namespace ts {
{
name: "disableSourceOfProjectReferenceRedirect",
type: "boolean",
isTSConfigOnly: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_use_of_source_files_instead_of_declaration_files_from_referenced_projects
},
{
name: "disableSolutionSearching",
type: "boolean",
isTSConfigOnly: true,
category: Diagnostics.Advanced_Options,
description: Diagnostics.Disable_solution_searching_for_this_project
},
{
name: "noImplicitUseStrict",
type: "boolean",
Expand Down
11 changes: 11 additions & 0 deletions src/compiler/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -498,6 +498,17 @@ namespace ts {
};
}

export function mapDefinedMap<T, U>(map: ReadonlyMap<T>, mapValue: (value: T, key: string) => U | undefined, mapKey: (key: string) => string = identity): Map<U> {
const result = createMap<U>();
map.forEach((value, key) => {
const mapped = mapValue(value, key);
if (mapped !== undefined) {
result.set(mapKey(key), mapped);
}
});
return result;
}

export const emptyIterator: Iterator<never> = { next: () => ({ value: undefined as never, done: true }) };

export function singleIterator<T>(value: T): Iterator<T> {
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -4156,6 +4156,10 @@
"category": "Message",
"code": 6223
},
"Disable solution searching for this project.": {
"category": "Message",
"code": 6224
},

"Projects to reference": {
"category": "Message",
Expand Down
2 changes: 2 additions & 0 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3526,6 +3526,7 @@ namespace ts {
runWithCancellationToken<T>(token: CancellationToken, cb: (checker: TypeChecker) => T): T;

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

/* @internal */
Expand Down Expand Up @@ -4949,6 +4950,7 @@ namespace ts {
/* @internal */ extendedDiagnostics?: boolean;
disableSizeLimit?: boolean;
disableSourceOfProjectReferenceRedirect?: boolean;
disableSolutionSearching?: boolean;
downlevelIteration?: boolean;
emitBOM?: boolean;
emitDecoratorMetadata?: boolean;
Expand Down
216 changes: 181 additions & 35 deletions src/server/editorServices.ts

Large diffs are not rendered by default.

19 changes: 12 additions & 7 deletions src/server/project.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1248,9 +1248,8 @@ namespace ts.server {
}

filesToString(writeProjectFileNames: boolean) {
if (!this.program) {
return "\tFiles (0)\n";
}
if (this.isInitialLoadPending()) return "\tFiles (0) InitialLoadPending\n";
if (!this.program) return "\tFiles (0) NoProgram\n";
const sourceFiles = this.program.getSourceFiles();
let strBuilder = `\tFiles (${sourceFiles.length})\n`;
if (writeProjectFileNames) {
Expand Down Expand Up @@ -1705,10 +1704,15 @@ namespace ts.server {

private projectReferences: readonly ProjectReference[] | undefined;

/** Potential project references before the project is actually loaded (read config file) */
/*@internal*/
potentialProjectReferences: Map<true> | undefined;

/*@internal*/
projectOptions?: ProjectOptions | true;

protected isInitialLoadPending: () => boolean = returnTrue;
/*@internal*/
isInitialLoadPending: () => boolean = returnTrue;

/*@internal*/
sendLoadingProjectFinish = false;
Expand Down Expand Up @@ -1933,12 +1937,13 @@ namespace ts.server {

updateReferences(refs: readonly ProjectReference[] | undefined) {
this.projectReferences = refs;
this.potentialProjectReferences = undefined;
}

/*@internal*/
forEachResolvedProjectReference<T>(cb: (resolvedProjectReference: ResolvedProjectReference | undefined, resolvedProjectReferencePath: Path) => T | undefined): T | undefined {
const program = this.getCurrentProgram();
return program && program.forEachResolvedProjectReference(cb);
setPotentialProjectReference(canonicalConfigPath: NormalizedPath) {
Debug.assert(this.isInitialLoadPending());
(this.potentialProjectReferences || (this.potentialProjectReferences = createMap())).set(canonicalConfigPath, true);
}

/*@internal*/
Expand Down
6 changes: 3 additions & 3 deletions src/server/scriptInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -497,9 +497,9 @@ namespace ts.server {
// - first configured project
// - first external project
// - first inferred project
let firstExternalProject;
let firstConfiguredProject;
let firstNonSourceOfProjectReferenceRedirect;
let firstExternalProject: ExternalProject | undefined;
let firstConfiguredProject: ConfiguredProject | undefined;
let firstNonSourceOfProjectReferenceRedirect: ConfiguredProject | undefined;
let defaultConfiguredProject: ConfiguredProject | false | undefined;
for (let index = 0; index < this.containingProjects.length; index++) {
const project = this.containingProjects[index];
Expand Down
49 changes: 29 additions & 20 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ namespace ts.server {

function combineProjectOutputFromEveryProject<T>(projectService: ProjectService, action: (project: Project) => readonly T[], areEqual: (a: T, b: T) => boolean) {
const outputs: T[] = [];
projectService.loadAncestorProjectTree();
projectService.forEachEnabledProject(project => {
const theseOutputs = action(project);
outputs.push(...theseOutputs.filter(output => !outputs.some(o => areEqual(o, output))));
Expand All @@ -299,7 +300,7 @@ namespace ts.server {
resultsEqual: (a: T, b: T) => boolean,
): T[] {
const outputs: T[] = [];
combineProjectOutputWorker<undefined>(
combineProjectOutputWorker(
projects,
defaultProject,
/*initialLocation*/ undefined,
Expand All @@ -310,7 +311,7 @@ namespace ts.server {
}
}
},
/*getDefinition*/ undefined);
);
return outputs;
}

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

combineProjectOutputWorker<DocumentPosition>(
combineProjectOutputWorker(
projects,
defaultProject,
initialLocation,
Expand All @@ -335,7 +336,6 @@ namespace ts.server {
}
}
},
() => getDefinitionLocation(defaultProject, initialLocation)
);

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

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

combineProjectOutputWorker<DocumentPosition>(
combineProjectOutputWorker(
projects,
defaultProject,
initialLocation,
Expand Down Expand Up @@ -384,7 +384,6 @@ namespace ts.server {
}
}
},
() => getDefinitionLocation(defaultProject, initialLocation)
);

return outputs.filter(o => o.references.length !== 0);
Expand Down Expand Up @@ -417,8 +416,7 @@ namespace ts.server {
projects: Projects,
defaultProject: Project,
initialLocation: TLocation,
cb: CombineProjectOutputCallback<TLocation>,
getDefinition: (() => DocumentPosition | undefined) | undefined,
cb: CombineProjectOutputCallback<TLocation>
): void {
const projectService = defaultProject.projectService;
let toDo: ProjectAndLocation<TLocation>[] | undefined;
Expand All @@ -430,15 +428,18 @@ namespace ts.server {
});

// After initial references are collected, go over every other project and see if it has a reference for the symbol definition.
if (getDefinition) {
const memGetDefinition = memoize(getDefinition);
projectService.forEachEnabledProject(project => {
if (!addToSeen(seenProjects, project.projectName)) return;
const definition = mapDefinitionInProject(memGetDefinition(), defaultProject, project);
if (definition) {
toDo = callbackProjectAndLocation<TLocation>({ project, location: definition as TLocation }, projectService, toDo, seenProjects, cb);
}
});
if (initialLocation) {
const defaultDefinition = getDefinitionLocation(defaultProject, initialLocation!);
if (defaultDefinition) {
projectService.loadAncestorProjectTree(seenProjects);
projectService.forEachEnabledProject(project => {
if (!addToSeen(seenProjects, project)) return;
const definition = mapDefinitionInProject(defaultDefinition, defaultProject, project);
if (definition) {
toDo = callbackProjectAndLocation<TLocation>({ project, location: definition as TLocation }, projectService, toDo, seenProjects, cb);
}
});
}
}

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

Expand All @@ -509,7 +510,15 @@ namespace ts.server {
}

function addToTodo<TLocation extends DocumentPosition | undefined>(projectAndLocation: ProjectAndLocation<TLocation>, toDo: Push<ProjectAndLocation<TLocation>>, seenProjects: Map<true>): void {
if (addToSeen(seenProjects, projectAndLocation.project.projectName)) toDo.push(projectAndLocation);
if (addToSeen(seenProjects, projectAndLocation.project)) toDo.push(projectAndLocation);
}

function addToSeen(seenProjects: Map<true>, project: Project) {
return ts.addToSeen(seenProjects, getProjectKey(project));
}

function getProjectKey(project: Project) {
return isConfiguredProject(project) ? project.canonicalConfigFilePath : project.getProjectName();
}

function documentSpanLocation({ fileName, textSpan }: DocumentSpan): DocumentPosition {
Expand Down
12 changes: 7 additions & 5 deletions src/services/goToDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,14 @@ namespace ts.GoToDefinition {
}
const { parent } = node;

const typeChecker = program.getTypeChecker();

// Labels
if (isJumpStatementTarget(node)) {
const label = getTargetLabel(node.parent, node.text);
return label ? [createDefinitionInfoFromName(label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
return label ? [createDefinitionInfoFromName(typeChecker, label, ScriptElementKind.label, node.text, /*containerName*/ undefined!)] : undefined; // TODO: GH#18217
}

const typeChecker = program.getTypeChecker();
const symbol = getSymbol(node, typeChecker);

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

/** Creates a DefinitionInfo directly from the name of a declaration. */
function createDefinitionInfoFromName(declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
function createDefinitionInfoFromName(checker: TypeChecker, declaration: Declaration, symbolKind: ScriptElementKind, symbolName: string, containerName: string): DefinitionInfo {
const name = getNameOfDeclaration(declaration) || declaration;
const sourceFile = name.getSourceFile();
const textSpan = createTextSpanFromNode(name, sourceFile);
Expand All @@ -294,7 +295,8 @@ namespace ts.GoToDefinition {
textSpan,
sourceFile,
FindAllReferences.getContextNode(declaration)
)
),
isLocal: !checker.isDeclarationVisible(declaration)
};
}

Expand Down
1 change: 1 addition & 0 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,7 @@ namespace ts {
name: string;
containerKind: ScriptElementKind;
containerName: string;
/* @internal */ isLocal?: boolean;
}

export interface DefinitionInfoAndBoundSpan {
Expand Down
9 changes: 5 additions & 4 deletions src/testRunner/unittests/tsserver/declarationFileMaps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ namespace ts.projectSystem {

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

Expand Down Expand Up @@ -224,15 +225,15 @@ namespace ts.projectSystem {
})
],
});
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 1 });
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
verifyUserTsConfigProject(session);

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

// UserTs configured project should be alive
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 2 });
checkNumberOfProjects(session.getProjectService(), { configuredProjects: 3 });
verifyUserTsConfigProject(session);
verifyATsConfigProject(session);

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

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

Expand Down
13 changes: 7 additions & 6 deletions src/testRunner/unittests/tsserver/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -495,8 +495,8 @@ namespace ts.projectSystem {
checkArray(`ScriptInfos files: ${additionInfo || ""}`, arrayFrom(projectService.filenameToScriptInfo.values(), info => info.fileName), expectedFiles);
}

export function protocolLocationFromSubstring(str: string, substring: string): protocol.Location {
const start = str.indexOf(substring);
export function protocolLocationFromSubstring(str: string, substring: string, options?: SpanFromSubstringOptions): protocol.Location {
const start = nthIndexOf(str, substring, options ? options.index : 0);
Debug.assert(start !== -1);
return protocolToLocation(str)(start);
}
Expand Down Expand Up @@ -587,8 +587,8 @@ namespace ts.projectSystem {
return createTextSpan(start, substring.length);
}

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

export interface SpanFromSubstringOptions {
Expand Down Expand Up @@ -732,14 +732,15 @@ namespace ts.projectSystem {

export interface MakeReferenceItem extends DocumentSpanFromSubstring {
isDefinition: boolean;
isWriteAccess?: boolean;
lineText: string;
}

export function makeReferenceItem({ isDefinition, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem {
export function makeReferenceItem({ isDefinition, isWriteAccess, lineText, ...rest }: MakeReferenceItem): protocol.ReferencesResponseItem {
return {
...protocolFileSpanWithContextFromSubstring(rest),
isDefinition,
isWriteAccess: isDefinition,
isWriteAccess: isWriteAccess === undefined ? isDefinition : isWriteAccess,
lineText,
};
}
Expand Down
Loading