Skip to content
Open
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
29 changes: 29 additions & 0 deletions src/harness/fourslashImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -840,6 +840,35 @@ export class TestState {
return this.languageService.getDefinitionAndBoundSpan(this.activeFile.fileName, this.currentCaretPosition)!;
}

public verifyGoToDefinitionInferredIndex(expectedDefinitionMarkerName: string): void {
const result = this.getGoToDefinitionAndBoundSpan();
const expectedMarker = this.getMarkerByName(expectedDefinitionMarkerName);

if (!result.definitions || result.definitions.length === 0) {
this.raiseError(`Expected definitions to be present, but got none.`);
}

if (result.inferredIndex === undefined) {
this.raiseError(`Expected inferredIndex to be defined, but it was undefined.`);
}

const inferredDef = result.definitions[result.inferredIndex];
if (!inferredDef) {
this.raiseError(`inferredIndex ${result.inferredIndex} is out of bounds for definitions array of length ${result.definitions.length}.`);
}

// Check if the inferred definition points to the expected marker location
if (
inferredDef.fileName !== expectedMarker.fileName ||
inferredDef.textSpan.start !== expectedMarker.position
) {
this.raiseError(
`Expected inferredIndex to point to marker "${expectedDefinitionMarkerName}" at ${expectedMarker.fileName}:${expectedMarker.position}, ` +
`but it points to ${inferredDef.fileName}:${inferredDef.textSpan.start}.`,
);
}
}

private renderMarkers(markers: { text: string; fileName: string; position: number; }[], useTerminalBoldSequence = true) {
const filesToDisplay = ts.deduplicate(markers.map(m => m.fileName), ts.equateValues);
return filesToDisplay.map(fileName => {
Expand Down
4 changes: 4 additions & 0 deletions src/harness/fourslashInterfaceImpl.ts
Original file line number Diff line number Diff line change
Expand Up @@ -389,6 +389,10 @@ export class Verify extends VerifyNegatable {
this.state.baselineGetDefinitionAtPosition(/*markerOrRange*/ undefined, rangeText);
}

public goToDefinitionInferredIndex(expectedDefinitionMarkerName: string): void {
this.state.verifyGoToDefinitionInferredIndex(expectedDefinitionMarkerName);
}

public baselineGoToSourceDefinition(...markerOrRange: FourSlash.MarkerOrNameOrRange[]): void {
this.state.baselineGoToSourceDefinition(markerOrRange, /*rangeText*/ undefined);
}
Expand Down
14 changes: 14 additions & 0 deletions src/server/session.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1025,6 +1025,8 @@ export class Session<TMessage = string> implements EventSender {
/** @internal */
protected regionDiagLineCountThreshold = 500;

private recentAttemptedDefinitionInferenceNames: Set<string>;

constructor(opts: SessionOptions) {
this.host = opts.host;
this.cancellationToken = opts.cancellationToken;
Expand Down Expand Up @@ -1074,6 +1076,8 @@ export class Session<TMessage = string> implements EventSender {
this.projectService.setPerformanceEventHandler(this.performanceEventHandler.bind(this));
this.gcTimer = new GcTimer(this.host, /*delay*/ 7000, this.logger);

this.recentAttemptedDefinitionInferenceNames = new Set();

// Make sure to setup handlers to throw error for not allowed commands on syntax server
switch (this.projectService.serverMode) {
case LanguageServiceMode.Semantic:
Expand Down Expand Up @@ -1645,6 +1649,16 @@ export class Session<TMessage = string> implements EventSender {
};
}

if (unmappedDefinitionAndBoundSpan?.inferredIndex !== undefined) {
const name = unmappedDefinitionAndBoundSpan.definitions[unmappedDefinitionAndBoundSpan.inferredIndex].name;

if (!this.recentAttemptedDefinitionInferenceNames.has(name)) {
this.recentAttemptedDefinitionInferenceNames.add(name);
this.host.setTimeout(() => this.recentAttemptedDefinitionInferenceNames.delete(name), 5000);
unmappedDefinitionAndBoundSpan.definitions = [unmappedDefinitionAndBoundSpan.definitions[unmappedDefinitionAndBoundSpan.inferredIndex]];
}
}

const definitions = this.mapDefinitionInfoLocations(unmappedDefinitionAndBoundSpan.definitions, project);
const { textSpan } = unmappedDefinitionAndBoundSpan;

Expand Down
37 changes: 34 additions & 3 deletions src/services/goToDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,24 +517,55 @@ function tryGetReturnTypeOfFunction(symbol: Symbol, type: Type, checker: TypeChe
/** @internal */
export function getDefinitionAndBoundSpan(program: Program, sourceFile: SourceFile, position: number): DefinitionInfoAndBoundSpan | undefined {
const definitions = getDefinitionAtPosition(program, sourceFile, position);

if (!definitions || definitions.length === 0) {
return undefined;
}

const inferredIndex = getInferrableDefinitionIndex(definitions, getTouchingPropertyName(sourceFile, position), program);

// Check if position is on triple slash reference.
const comment = findReferenceInPosition(sourceFile.referencedFiles, position) ||
findReferenceInPosition(sourceFile.typeReferenceDirectives, position) ||
findReferenceInPosition(sourceFile.libReferenceDirectives, position);

if (comment) {
return { definitions, textSpan: createTextSpanFromRange(comment) };
return { definitions, inferredIndex, textSpan: createTextSpanFromRange(comment) };
}

const node = getTouchingPropertyName(sourceFile, position);
const textSpan = createTextSpan(node.getStart(), node.getWidth());

return { definitions, textSpan };
return { definitions, inferredIndex, textSpan };
}

function getInferrableDefinitionIndex(definitions: readonly DefinitionInfo[], node: Node, program: Program): number | undefined {
const TYPEY_SET = new Set([SyntaxKind.InterfaceDeclaration, SyntaxKind.TypeAliasDeclaration]);

const mainIsTypey = getUsageNodeKind(node) === SyntaxKind.TypeReference;
const optionNodes = definitions?.map(def => findNodeFromSpan(program.getSourceFile(def.fileName), def.textSpan)!);
const definitionsAreTypey = optionNodes.map(node => TYPEY_SET.has(node?.parent?.kind ?? undefined));

return definitionsAreTypey.filter(def => def === mainIsTypey).length === 1 ? definitionsAreTypey.indexOf(mainIsTypey) : undefined;
}

function getUsageNodeKind(node: Node): SyntaxKind {
return node.kind === SyntaxKind.QualifiedName || node.kind === SyntaxKind.Identifier ? getUsageNodeKind(node.parent) : node.kind;
}

function findNodeFromSpan(sourceFile: SourceFile | undefined, span: TextSpan): Node | undefined {
const { start, length } = span;
const end = start + length;

function visit(node: Node): Node | undefined {
const nodeStart = node.getFullStart();
const nodeEnd = node.getEnd();
if (start >= nodeStart && end <= nodeEnd) {
const childMatch = node.forEachChild(visit);
return childMatch || node;
}
return undefined;
}
return sourceFile ? visit(sourceFile) : undefined;
}

// At 'x.foo', see if the type of 'x' has an index signature, and if so find its declarations.
Expand Down
1 change: 1 addition & 0 deletions src/services/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1257,6 +1257,7 @@ export interface DefinitionInfo extends DocumentSpan {

export interface DefinitionInfoAndBoundSpan {
definitions?: readonly DefinitionInfo[];
inferredIndex?: number;
textSpan: TextSpan;
}

Expand Down
2 changes: 2 additions & 0 deletions tests/baselines/reference/api/typescript.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3477,6 +3477,7 @@ declare namespace ts {
private suppressDiagnosticEvents?;
private eventHandler;
private readonly noGetErrOnBackgroundUpdate?;
private recentAttemptedDefinitionInferenceNames;
constructor(opts: SessionOptions);
private sendRequestCompletedEvent;
private addPerformanceData;
Expand Down Expand Up @@ -10730,6 +10731,7 @@ declare namespace ts {
}
interface DefinitionInfoAndBoundSpan {
definitions?: readonly DefinitionInfo[];
inferredIndex?: number;
textSpan: TextSpan;
}
interface ReferencedSymbolDefinitionInfo extends DefinitionInfo {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -279,6 +279,9 @@ FsWatchesRecursive::
/home/src/projects/project/b:
{}

Timeout callback:: count: 1
1: undefined *new*

Projects::
/dev/null/inferredProject1* (Inferred) *changed*
projectStateVersion: 1
Expand Down Expand Up @@ -401,6 +404,10 @@ FsWatchesRecursive::
/home/src/projects/project/b:
{}

Timeout callback:: count: 2
1: undefined
2: undefined *new*

Projects::
/dev/null/inferredProject1* (Inferred) *changed*
projectStateVersion: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -990,6 +990,10 @@ Info seq [hh:mm:ss:mss] response:
}
After request

Timeout callback:: count: 2
2: /home/src/projects/project/b/tsconfig.json
3: undefined *new*

Before request

Info seq [hh:mm:ss:mss] request:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,10 @@ FsWatchesRecursive::
/home/src/projects/project/b:
{}

Timeout callback:: count: 2
2: /home/src/projects/project/b/tsconfig.json
3: undefined *new*

Projects::
/dev/null/inferredProject1* (Inferred) *changed*
projectStateVersion: 1
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -140,3 +140,6 @@ Info seq [hh:mm:ss:mss] response:
"responseRequired": true
}
After request

Timeout callback:: count: 1
1: undefined *new*
Original file line number Diff line number Diff line change
Expand Up @@ -913,6 +913,9 @@ FsWatchesRecursive::
/user/username/projects/myproject/random:
{}

Timeout callback:: count: 1
1: undefined *new*

Projects::
/user/username/projects/myproject/dependency/tsconfig.json (Configured)
projectStateVersion: 1
Expand Down Expand Up @@ -1090,11 +1093,12 @@ Info seq [hh:mm:ss:mss] Elapsed:: *ms DirectoryWatcher:: Triggered with /user/u
Before request
//// [/user/username/projects/myproject/decls/FnS.d.ts] deleted

Timeout callback:: count: 4
1: /user/username/projects/myproject/main/tsconfig.json *new*
3: /user/username/projects/myproject/dependency/tsconfig.json *new*
4: *ensureProjectForOpenFiles* *new*
5: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *new*
Timeout callback:: count: 5
1: undefined
2: /user/username/projects/myproject/main/tsconfig.json *new*
4: /user/username/projects/myproject/dependency/tsconfig.json *new*
5: *ensureProjectForOpenFiles* *new*
6: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *new*

Projects::
/user/username/projects/myproject/dependency/tsconfig.json (Configured) *changed*
Expand Down Expand Up @@ -1217,11 +1221,12 @@ Info seq [hh:mm:ss:mss] response:
}
After request

Timeout callback:: count: 3
5: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *deleted*
1: /user/username/projects/myproject/main/tsconfig.json
3: /user/username/projects/myproject/dependency/tsconfig.json
4: *ensureProjectForOpenFiles*
Timeout callback:: count: 4
6: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *deleted*
1: undefined
2: /user/username/projects/myproject/main/tsconfig.json
4: /user/username/projects/myproject/dependency/tsconfig.json
5: *ensureProjectForOpenFiles*

Projects::
/user/username/projects/myproject/dependency/tsconfig.json (Configured)
Expand Down Expand Up @@ -1295,6 +1300,13 @@ Info seq [hh:mm:ss:mss] response:
}
After request

Timeout callback:: count: 5
1: undefined
2: /user/username/projects/myproject/main/tsconfig.json
4: /user/username/projects/myproject/dependency/tsconfig.json
5: *ensureProjectForOpenFiles*
7: undefined *new*

Before request

Info seq [hh:mm:ss:mss] request:
Expand Down Expand Up @@ -1468,13 +1480,15 @@ export declare function fn6(): void;
//# sourceMappingURL=FnS.d.ts.map


Timeout callback:: count: 4
3: /user/username/projects/myproject/dependency/tsconfig.json *deleted*
4: *ensureProjectForOpenFiles* *deleted*
1: /user/username/projects/myproject/main/tsconfig.json
8: /user/username/projects/myproject/dependency/tsconfig.json *new*
9: *ensureProjectForOpenFiles* *new*
10: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *new*
Timeout callback:: count: 6
4: /user/username/projects/myproject/dependency/tsconfig.json *deleted*
5: *ensureProjectForOpenFiles* *deleted*
1: undefined
2: /user/username/projects/myproject/main/tsconfig.json
7: undefined
10: /user/username/projects/myproject/dependency/tsconfig.json *new*
11: *ensureProjectForOpenFiles* *new*
12: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *new*

Projects::
/user/username/projects/myproject/dependency/tsconfig.json (Configured) *changed*
Expand Down Expand Up @@ -1598,12 +1612,14 @@ Info seq [hh:mm:ss:mss] response:
}
After request

Timeout callback:: count: 3
9: *ensureProjectForOpenFiles* *deleted*
10: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *deleted*
1: /user/username/projects/myproject/main/tsconfig.json
8: /user/username/projects/myproject/dependency/tsconfig.json
11: *ensureProjectForOpenFiles* *new*
Timeout callback:: count: 5
11: *ensureProjectForOpenFiles* *deleted*
12: /user/username/projects/myproject/main/tsconfig.jsonFailedLookupInvalidation *deleted*
1: undefined
2: /user/username/projects/myproject/main/tsconfig.json
7: undefined
10: /user/username/projects/myproject/dependency/tsconfig.json
13: *ensureProjectForOpenFiles* *new*

Projects::
/user/username/projects/myproject/dependency/tsconfig.json (Configured)
Expand Down
Loading