Skip to content

Commit dc4b6f1

Browse files
authored
Support "Go To Definition" and "Go to References" for #includes. (#40)
Fixes #36
1 parent 306b9e9 commit dc4b6f1

File tree

7 files changed

+154
-23
lines changed

7 files changed

+154
-23
lines changed

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# v0.6.0
2+
3+
## New Features
4+
5+
* ([#36](https://github.com/harrisont/fastbuild-vscode/issues/36)) Support "Go To Definition" and "Go to References" for `#include`s.
6+
17
# v0.5.11
28

39
## Changes

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Contains a language server and Visual Studio Code client for the [FASTBuild](htt
2020
* Variables
2121
* `#import`s
2222
* `#define`s
23+
* `#include`s
2324

2425
![Demo - go to definition and references](https://user-images.githubusercontent.com/144260/229382457-e15236ef-a0d6-4815-9f5c-6763d346399f.gif)
2526

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"name": "fastbuild-support",
33
"displayName": "FASTBuild Support",
44
"description": "FASTBuild language support. Includes go-to definition, find references, variable evaluation, syntax errors, etc.",
5-
"version": "0.5.11",
5+
"version": "0.6.0",
66
"preview": true,
77
"publisher": "HarrisonT",
88
"author": {

server/src/evaluator.ts

Lines changed: 24 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,17 +139,26 @@ export interface TargetReference {
139139
range: SourceRange;
140140
}
141141

142+
export interface IncludeReference {
143+
includeUri: UriStr;
144+
range: SourceRange;
145+
}
146+
142147
export class EvaluatedData {
143148
evaluatedVariables: EvaluatedVariable[] = [];
144149

145-
variableReferences: VariableReference[] = [];
146-
147150
variableDefinitions: VariableDefinition[] = [];
148151

149-
targetReferences: TargetReference[] = [];
152+
variableReferences: VariableReference[] = [];
150153

151154
// Maps a target name to its definition
152155
targetDefinitions = new Map<string, TargetDefinition>();
156+
157+
targetReferences: TargetReference[] = [];
158+
159+
includeDefinitions = new Set<UriStr>();
160+
161+
includeReferences: IncludeReference[] = [];
153162
}
154163

155164
type ScopeLocation = 'current' | 'parent';
@@ -1330,15 +1339,24 @@ function evaluateStatements(statements: Statement[], context: EvaluationContext)
13301339
} else if (isParsedStatementInclude(statement)) { // #include
13311340
const thisFbuildUriDir = vscodeUri.Utils.dirname(vscodeUri.URI.parse(context.thisFbuildUri));
13321341
const includeUri = vscodeUri.Utils.resolvePath(thisFbuildUriDir, statement.path.value);
1333-
if (!context.onceIncludeUrisAlreadyIncluded.includes(includeUri.toString())) {
1342+
const includeUriStr = includeUri.toString();
1343+
const includeRange = new SourceRange(context.thisFbuildUri, statement.path.range);
1344+
1345+
context.evaluatedData.includeDefinitions.add(includeUriStr);
1346+
const includeReference: IncludeReference = {
1347+
includeUri: includeUriStr,
1348+
range: includeRange,
1349+
};
1350+
context.evaluatedData.includeReferences.push(includeReference);
1351+
1352+
if (!context.onceIncludeUrisAlreadyIncluded.includes(includeUriStr)) {
13341353
const maybeIncludeParseData = context.parseDataProvider.getParseData(includeUri);
13351354
if (maybeIncludeParseData.hasError) {
13361355
const includeError = maybeIncludeParseData.getError();
13371356
let error: Error;
13381357
if (includeError instanceof ParseError) {
13391358
error = includeError;
13401359
} else {
1341-
const includeRange = new SourceRange(context.thisFbuildUri, statement.path.range);
13421360
error = new EvaluationError(includeRange, `Unable to open include: ${includeError.message}`);
13431361
}
13441362
return error;
@@ -1364,7 +1382,7 @@ function evaluateStatements(statements: Statement[], context: EvaluationContext)
13641382
defines: context.defines,
13651383
userFunctions: context.userFunctions,
13661384
rootFbuildDirUri: context.rootFbuildDirUri,
1367-
thisFbuildUri: includeUri.toString(),
1385+
thisFbuildUri: includeUriStr,
13681386
fileSystem: context.fileSystem,
13691387
parseDataProvider: context.parseDataProvider,
13701388
onceIncludeUrisAlreadyIncluded: context.onceIncludeUrisAlreadyIncluded,

server/src/features/definitionProvider.ts

Lines changed: 25 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
} from 'vscode-languageserver-protocol';
55

66
import {
7+
createRange,
78
isPositionInRange,
89
} from '../parser';
910

@@ -15,17 +16,34 @@ export class DefinitionProvider {
1516
getDefinition(params: DefinitionParams, evaluatedData: EvaluatedData): DefinitionLink[] | null {
1617
const uri = params.textDocument.uri;
1718
const position = params.position;
18-
const variableReferences = evaluatedData.variableReferences;
1919

20-
for (let i = 0; i < variableReferences.length; i++) {
21-
const variableReference = variableReferences[i];
22-
if (uri == variableReference.range.uri
23-
&& isPositionInRange(position, variableReference.range))
20+
// Check for a matching #include definition.
21+
for (let i = 0; i < evaluatedData.includeReferences.length; i++) {
22+
const reference = evaluatedData.includeReferences[i];
23+
if (uri == reference.range.uri
24+
&& isPositionInRange(position, reference.range))
2425
{
25-
const definition = variableReference.definition;
26+
const includeRange = createRange(0, 0, 0, 0);
27+
const definitionLink: DefinitionLink = {
28+
originSelectionRange: reference.range,
29+
targetUri: reference.includeUri,
30+
targetRange: includeRange,
31+
targetSelectionRange: includeRange,
32+
};
33+
return [definitionLink];
34+
}
35+
}
36+
37+
// Check for a matching variable definition.
38+
for (let i = 0; i < evaluatedData.variableReferences.length; i++) {
39+
const reference = evaluatedData.variableReferences[i];
40+
if (uri == reference.range.uri
41+
&& isPositionInRange(position, reference.range))
42+
{
43+
const definition = reference.definition;
2644

2745
const definitionLink: DefinitionLink = {
28-
originSelectionRange: variableReference.range,
46+
originSelectionRange: reference.range,
2947
targetUri: definition.range.uri,
3048
targetRange: definition.range,
3149
targetSelectionRange: definition.range,

server/src/features/referenceProvider.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export class ReferenceProvider {
2121
const position = params.position;
2222

2323
const references = this.getTargetReferences(uri, position, evaluatedData);
24+
references.push(...this.getIncludeReferences(uri, position, evaluatedData));
2425
references.push(...this.getVariableReferences(uri, position, evaluatedData));
2526
return references;
2627
}
@@ -53,6 +54,34 @@ export class ReferenceProvider {
5354
return [...locations.values()];
5455
}
5556

57+
getIncludeReferences(uri: string, position: Position, evaluatedData: EvaluatedData): Location[] {
58+
const references = evaluatedData.includeReferences;
59+
60+
const referenceAtPosition = references.find(ref => (ref.range.uri == uri && isPositionInRange(position, ref.range)));
61+
if (referenceAtPosition === undefined) {
62+
return [];
63+
}
64+
65+
// Search algorithm: for each references, check if the URI matches.
66+
// This is not very optimized.
67+
68+
// Map JSON.stringify(Location) to Location in order to deduplicate referencs in a 'ForEach' loop.
69+
const locations = new Map<string, Location>();
70+
71+
for (const reference of references)
72+
{
73+
if (reference.includeUri === referenceAtPosition.includeUri) {
74+
const location: Location = {
75+
uri: reference.range.uri,
76+
range: reference.range
77+
};
78+
locations.set(JSON.stringify(location), location);
79+
}
80+
}
81+
82+
return [...locations.values()];
83+
}
84+
5685
getVariableReferences(uri: string, position: Position, evaluatedData: EvaluatedData): Location[] {
5786
const references = evaluatedData.variableReferences;
5887

@@ -80,4 +109,4 @@ export class ReferenceProvider {
80109

81110
return [...locations.values()];
82111
}
83-
}
112+
}

server/src/test/2-evaluator.test.ts

Lines changed: 67 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
evaluate,
2020
EvaluatedData,
2121
EvaluatedVariable,
22+
IncludeReference,
2223
SourceRange,
2324
Struct,
2425
StructMember,
@@ -4571,7 +4572,7 @@ Expecting to see the following:
45714572
]
45724573
]), true /*enableDiagnostics*/);
45734574

4574-
assert.deepStrictEqual(result.evaluatedVariables, [
4575+
const expectedEvaluatedVariables: EvaluatedVariable[] = [
45754576
{
45764577
value: 1,
45774578
range: createFileRange('file:///helper.bff', 1, 24, 1, 35),
@@ -4580,7 +4581,8 @@ Expecting to see the following:
45804581
value: 1,
45814582
range: createFileRange('file:///fbuild.bff', 2, 31, 2, 42),
45824583
}
4583-
]);
4584+
];
4585+
assert.deepStrictEqual(result.evaluatedVariables, expectedEvaluatedVariables);
45844586

45854587
const definitionFromHelper: VariableDefinition = {
45864588
id: 1,
@@ -4592,7 +4594,7 @@ Expecting to see the following:
45924594
definitionFromHelper, // FromHelper
45934595
]);
45944596

4595-
assert.deepStrictEqual(result.variableReferences, [
4597+
const expectedVariableReferences: VariableReference[] = [
45964598
// helper.bff ".FromHelper = 1" LHS
45974599
{
45984600
definition: definitionFromHelper,
@@ -4603,7 +4605,20 @@ Expecting to see the following:
46034605
definition: definitionFromHelper,
46044606
range: createFileRange('file:///fbuild.bff', 2, 31, 2, 42),
46054607
},
4606-
]);
4608+
];
4609+
assert.deepStrictEqual(result.variableReferences, expectedVariableReferences);
4610+
4611+
assert.deepStrictEqual(result.includeDefinitions, new Set<UriStr>([
4612+
'file:///helper.bff',
4613+
]));
4614+
4615+
const expectedIncludeReferences: IncludeReference[] = [
4616+
{
4617+
includeUri: 'file:///helper.bff',
4618+
range: createFileRange('file:///fbuild.bff', 1, 33, 1, 45),
4619+
},
4620+
];
4621+
assert.deepStrictEqual(result.includeReferences, expectedIncludeReferences);
46074622
});
46084623

46094624
it('include the same file multiple times in a row', () => {
@@ -4624,7 +4639,7 @@ Expecting to see the following:
46244639
],
46254640
]), true /*enableDiagnostics*/);
46264641

4627-
assert.deepStrictEqual(result.evaluatedVariables, [
4642+
const expectedEvaluatedVariables: EvaluatedVariable[] = [
46284643
{
46294644
value: 'Bobo',
46304645
range: createFileRange('file:///some/path/fbuild.bff', 1, 24, 1, 29),
@@ -4637,7 +4652,24 @@ Expecting to see the following:
46374652
value: 'Bobo',
46384653
range: createFileRange('file:///some/path/greetings.bff', 1, 38, 1, 44),
46394654
},
4640-
]);
4655+
];
4656+
assert.deepStrictEqual(result.evaluatedVariables, expectedEvaluatedVariables);
4657+
4658+
assert.deepStrictEqual(result.includeDefinitions, new Set<UriStr>([
4659+
'file:///some/path/greetings.bff',
4660+
]));
4661+
4662+
const expectedIncludeReferences: IncludeReference[] = [
4663+
{
4664+
includeUri: 'file:///some/path/greetings.bff',
4665+
range: createFileRange('file:///some/path/fbuild.bff', 2, 33, 2, 48),
4666+
},
4667+
{
4668+
includeUri: 'file:///some/path/greetings.bff',
4669+
range: createFileRange('file:///some/path/fbuild.bff', 3, 33, 3, 48),
4670+
},
4671+
];
4672+
assert.deepStrictEqual(result.includeReferences, expectedIncludeReferences);
46414673
});
46424674

46434675
it('include with ".."', () => {
@@ -4673,7 +4705,7 @@ Expecting to see the following:
46734705
]
46744706
]), true /*enableDiagnostics*/);
46754707

4676-
assert.deepStrictEqual(result.evaluatedVariables, [
4708+
const expectedEvaluatedVariables: EvaluatedVariable[] = [
46774709
{
46784710
value: 'dog',
46794711
range: createFileRange('file:///some/path/animals/dog.bff', 1, 24, 1, 29),
@@ -4706,7 +4738,34 @@ Expecting to see the following:
47064738
value: 'Hello cat',
47074739
range: createFileRange('file:///some/path/animals/cat.bff', 3, 31, 3, 39),
47084740
},
4709-
]);
4741+
];
4742+
assert.deepStrictEqual(result.evaluatedVariables, expectedEvaluatedVariables);
4743+
4744+
assert.deepStrictEqual(result.includeDefinitions, new Set<UriStr>([
4745+
'file:///some/path/animals/dog.bff',
4746+
'file:///some/path/greetings.bff',
4747+
'file:///some/path/animals/cat.bff',
4748+
]));
4749+
4750+
const expectedIncludeReferences: IncludeReference[] = [
4751+
{
4752+
includeUri: 'file:///some/path/animals/dog.bff',
4753+
range: createFileRange('file:///some/path/fbuild.bff', 1, 33, 1, 50),
4754+
},
4755+
{
4756+
includeUri: 'file:///some/path/greetings.bff',
4757+
range: createFileRange('file:///some/path/animals/dog.bff', 2, 33, 2, 51),
4758+
},
4759+
{
4760+
includeUri: 'file:///some/path/animals/cat.bff',
4761+
range: createFileRange('file:///some/path/fbuild.bff', 2, 33, 2, 50),
4762+
},
4763+
{
4764+
includeUri: 'file:///some/path/greetings.bff',
4765+
range: createFileRange('file:///some/path/animals/cat.bff', 2, 33, 2, 51),
4766+
},
4767+
];
4768+
assert.deepStrictEqual(result.includeReferences, expectedIncludeReferences);
47104769
});
47114770
});
47124771

0 commit comments

Comments
 (0)