Skip to content

Commit a560611

Browse files
committed
Fix and test onReferences not respecting includeDeclaration
1 parent 397b5ba commit a560611

File tree

5 files changed

+218
-20
lines changed

5 files changed

+218
-20
lines changed

server/src/__tests__/server.test.ts

Lines changed: 176 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,7 @@ describe('server', () => {
699699
uri: FIXTURE_URI.ISSUE206,
700700
},
701701
position: {
702-
// readonly cannot be parsed as a word
702+
// readonly is a declaration command so not parsed correctly by findOccurrences
703703
line: 0,
704704
character: 0,
705705
},
@@ -1026,7 +1026,7 @@ describe('server', () => {
10261026
})
10271027

10281028
it.skip('returns documentation from explainshell', async () => {
1029-
// Skipped as this requires a running explainshell server
1029+
// Skipped as this requires a running explainshell server (and the code is hard to mock)
10301030
// docker container run --name explainshell --restart always -p 127.0.0.1:6000:5000 -d spaceinvaderone/explainshell
10311031

10321032
const { connection } = await initializeServer({
@@ -1067,7 +1067,180 @@ describe('server', () => {
10671067
})
10681068

10691069
describe('onReferences', () => {
1070-
// FIXME
1070+
async function getOnReferencesTestCase() {
1071+
const { connection } = await initializeServer()
1072+
const onReferences = connection.onReferences.mock.calls[0][0]
1073+
1074+
const callOnReferences = ({
1075+
includeDeclarationOfCurrentSymbol,
1076+
uri,
1077+
position,
1078+
}: {
1079+
includeDeclarationOfCurrentSymbol: boolean
1080+
uri: string
1081+
position: LSP.Position
1082+
}) =>
1083+
updateSnapshotUris(
1084+
onReferences(
1085+
{
1086+
textDocument: {
1087+
uri,
1088+
},
1089+
position,
1090+
context: {
1091+
includeDeclaration: includeDeclarationOfCurrentSymbol,
1092+
},
1093+
},
1094+
{} as any,
1095+
{} as any,
1096+
),
1097+
)
1098+
1099+
return {
1100+
callOnReferences,
1101+
}
1102+
}
1103+
1104+
it('returns null if the word is not found', async () => {
1105+
const { callOnReferences } = await getOnReferencesTestCase()
1106+
const result = await callOnReferences({
1107+
position: { line: 34, character: 1 }, // empty line
1108+
uri: FIXTURE_URI.INSTALL,
1109+
includeDeclarationOfCurrentSymbol: true,
1110+
})
1111+
expect(result).toBeNull()
1112+
})
1113+
1114+
it('returns references to builtins and executables across the workspace', async () => {
1115+
const { callOnReferences } = await getOnReferencesTestCase()
1116+
const result = await callOnReferences({
1117+
position: { line: 263, character: 5 }, // echo
1118+
uri: FIXTURE_URI.INSTALL,
1119+
includeDeclarationOfCurrentSymbol: true,
1120+
})
1121+
expect(Array.isArray(result)).toBe(true)
1122+
if (Array.isArray(result)) {
1123+
expect(result.length).toBeGreaterThan(50)
1124+
expect(new Set(result.map((v) => v.uri)).size).toBeGreaterThan(5)
1125+
}
1126+
})
1127+
1128+
it('returns references depending on the context flag', async () => {
1129+
const { callOnReferences } = await getOnReferencesTestCase()
1130+
1131+
const resultIncludingCurrentSymbol = await callOnReferences({
1132+
position: { line: 50, character: 10 }, // npm_config_loglevel
1133+
uri: FIXTURE_URI.INSTALL,
1134+
includeDeclarationOfCurrentSymbol: true,
1135+
})
1136+
1137+
const resultExcludingCurrentSymbol = await callOnReferences({
1138+
position: { line: 50, character: 10 }, // npm_config_loglevel
1139+
uri: FIXTURE_URI.INSTALL,
1140+
includeDeclarationOfCurrentSymbol: false,
1141+
})
1142+
1143+
expect(resultIncludingCurrentSymbol).toMatchInlineSnapshot(`
1144+
Array [
1145+
Object {
1146+
"range": Object {
1147+
"end": Object {
1148+
"character": 19,
1149+
"line": 40,
1150+
},
1151+
"start": Object {
1152+
"character": 0,
1153+
"line": 40,
1154+
},
1155+
},
1156+
"uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/install.sh",
1157+
},
1158+
Object {
1159+
"range": Object {
1160+
"end": Object {
1161+
"character": 21,
1162+
"line": 48,
1163+
},
1164+
"start": Object {
1165+
"character": 2,
1166+
"line": 48,
1167+
},
1168+
},
1169+
"uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/install.sh",
1170+
},
1171+
Object {
1172+
"range": Object {
1173+
"end": Object {
1174+
"character": 26,
1175+
"line": 50,
1176+
},
1177+
"start": Object {
1178+
"character": 7,
1179+
"line": 50,
1180+
},
1181+
},
1182+
"uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/install.sh",
1183+
},
1184+
Object {
1185+
"range": Object {
1186+
"end": Object {
1187+
"character": 26,
1188+
"line": 42,
1189+
},
1190+
"start": Object {
1191+
"character": 7,
1192+
"line": 42,
1193+
},
1194+
},
1195+
"uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/scope.sh",
1196+
},
1197+
]
1198+
`)
1199+
1200+
expect(resultExcludingCurrentSymbol).toMatchInlineSnapshot(`
1201+
Array [
1202+
Object {
1203+
"range": Object {
1204+
"end": Object {
1205+
"character": 19,
1206+
"line": 40,
1207+
},
1208+
"start": Object {
1209+
"character": 0,
1210+
"line": 40,
1211+
},
1212+
},
1213+
"uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/install.sh",
1214+
},
1215+
Object {
1216+
"range": Object {
1217+
"end": Object {
1218+
"character": 21,
1219+
"line": 48,
1220+
},
1221+
"start": Object {
1222+
"character": 2,
1223+
"line": 48,
1224+
},
1225+
},
1226+
"uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/install.sh",
1227+
},
1228+
Object {
1229+
"range": Object {
1230+
"end": Object {
1231+
"character": 26,
1232+
"line": 42,
1233+
},
1234+
"start": Object {
1235+
"character": 7,
1236+
"line": 42,
1237+
},
1238+
},
1239+
"uri": "file://__REPO_ROOT_FOLDER__/testing/fixtures/scope.sh",
1240+
},
1241+
]
1242+
`)
1243+
})
10711244
})
10721245

10731246
describe('onWorkspaceSymbol', () => {

server/src/analyser.ts

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -264,21 +264,24 @@ export default class Analyzer {
264264

265265
/**
266266
* Find all the locations where the given word was defined or referenced.
267+
* This will include commands, functions, variables, etc.
267268
*
268-
* FIXME: take position into account
269-
* FIXME: take file into account
269+
* It's currently not scope-aware, see findOccurrences.
270270
*/
271271
public findReferences(word: string): LSP.Location[] {
272272
const uris = Object.keys(this.uriToAnalyzedDocument)
273273
return flattenArray(uris.map((uri) => this.findOccurrences(uri, word)))
274274
}
275275

276276
/**
277-
* Find all occurrences (references or definitions) of a word in the given file.
277+
* Find all occurrences of a word in the given file.
278278
* It's currently not scope-aware.
279279
*
280-
* FIXME: should this take the scope into account? I guess it should
281-
* as this is used for highlighting.
280+
* This will include commands, functions, variables, etc.
281+
*
282+
* It's currently not scope-aware, meaning references does include
283+
* references to functions and variables that has the same name but
284+
* are defined in different files.
282285
*/
283286
public findOccurrences(uri: string, word: string): LSP.Location[] {
284287
const analyzedDocument = this.uriToAnalyzedDocument[uri]
@@ -294,6 +297,7 @@ export default class Analyzer {
294297
let namedNode: Parser.SyntaxNode | null = null
295298

296299
if (TreeSitterUtil.isReference(n)) {
300+
// NOTE: a reference can be a command, variable, function, etc.
297301
namedNode = n.firstNamedChild || n
298302
} else if (TreeSitterUtil.isDefinition(n)) {
299303
namedNode = n.firstNamedChild

server/src/server.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { SNIPPETS } from './snippets'
1818
import { BashCompletionItem, CompletionItemDataType } from './types'
1919
import { uniqueBasedOnHash } from './util/array'
2020
import { logger, setLogConnection, setLogLevel } from './util/logger'
21+
import { isPositionIncludedInRange } from './util/lsp'
2122
import { getShellDocumentation } from './util/sh'
2223

2324
const PARAMETER_EXPANSION_PREFIXES = new Set(['$', '${'])
@@ -708,7 +709,14 @@ export default class BashServer {
708709
if (!word) {
709710
return null
710711
}
711-
return this.analyzer.findReferences(word)
712+
713+
const isCurrentDeclaration = (l: LSP.Location) =>
714+
l.uri === params.textDocument.uri &&
715+
isPositionIncludedInRange(params.position, l.range)
716+
717+
return this.analyzer
718+
.findReferences(word)
719+
.filter((l) => params.context.includeDeclaration || !isCurrentDeclaration(l))
712720
}
713721

714722
private onWorkspaceSymbol(params: LSP.WorkspaceSymbolParams): LSP.SymbolInformation[] {

testing/fixtures.ts

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -40,17 +40,28 @@ export const FIXTURE_DOCUMENT: Record<FIXTURE_KEY, TextDocument> = (
4040

4141
export const REPO_ROOT_FOLDER = path.resolve(path.join(FIXTURE_FOLDER, '../..'))
4242

43-
export function updateSnapshotUris(obj: any) {
44-
if (obj.uri) {
45-
obj.uri = obj.uri.replace(REPO_ROOT_FOLDER, '__REPO_ROOT_FOLDER__')
46-
}
47-
Object.values(obj).forEach((child) => {
48-
if (Array.isArray(child)) {
49-
child.forEach((el) => updateSnapshotUris(el))
50-
} else if (typeof child === 'object' && child != null) {
51-
updateSnapshotUris(child)
43+
export function updateSnapshotUris<
44+
T extends Record<string, any> | Array<any> | null | undefined,
45+
>(data: T): T {
46+
if (data != null) {
47+
if (Array.isArray(data)) {
48+
data.forEach((el) => updateSnapshotUris(el))
49+
return data
50+
}
51+
52+
if (typeof data === 'object') {
53+
if (data.uri) {
54+
data.uri = data.uri.replace(REPO_ROOT_FOLDER, '__REPO_ROOT_FOLDER__')
55+
}
56+
Object.values(data).forEach((child) => {
57+
if (Array.isArray(child)) {
58+
child.forEach((el) => updateSnapshotUris(el))
59+
} else if (typeof child === 'object' && child != null) {
60+
updateSnapshotUris(child)
61+
}
62+
})
5263
}
53-
})
64+
}
5465

55-
return obj
66+
return data
5667
}

testing/fixtures/scope.sh

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,5 @@ for i in 1 2 3 4 5
3939
do
4040
echo "$GLOBAL_1 $i"
4141
done
42+
43+
echo "$npm_config_loglevel" # this is an undefined variable, but defined in install.sh

0 commit comments

Comments
 (0)