Skip to content

Commit cca9a26

Browse files
authored
Merge pull request bash-lsp#222 from bash-lsp/strict-tsconfig
Enable TypeScript strict mode for LSP
2 parents 56e9c4b + 47b175f commit cca9a26

File tree

8 files changed

+107
-46
lines changed

8 files changed

+107
-46
lines changed

server/src/__tests__/executables.test.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import * as path from 'path'
22

33
import Executables from '../executables'
44

5-
let executables: Executables = null
5+
let executables: Executables
66

77
beforeAll(async () => {
88
executables = await Executables.fromPath(

server/src/__tests__/server.test.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,13 @@ import LspServer from '../server'
66
import { CompletionItemDataType } from '../types'
77

88
async function initializeServer() {
9-
const diagnostics: Array<lsp.PublishDiagnosticsParams | undefined> = undefined
9+
const diagnostics: Array<lsp.PublishDiagnosticsParams | undefined> = []
1010

1111
const connection = getMockConnection()
1212

1313
const server = await LspServer.initialize(connection, {
1414
rootPath: FIXTURE_FOLDER,
15-
rootUri: undefined,
15+
rootUri: null,
1616
processId: 42,
1717
capabilities: {} as any,
1818
workspaceFolders: null,
@@ -154,7 +154,7 @@ describe('server', () => {
154154
{} as any,
155155
)
156156

157-
expect(result2).toMatchInlineSnapshot(`Array []`)
157+
expect(result2).toMatchInlineSnapshot(`null`)
158158
})
159159

160160
it('responds to onWorkspaceSymbol', async () => {
@@ -225,7 +225,7 @@ describe('server', () => {
225225
)
226226

227227
// Limited set (not using snapshot due to different executables on CI and locally)
228-
expect('length' in result && result.length < 5).toBe(true)
228+
expect(result && 'length' in result && result.length < 5).toBe(true)
229229
expect(result).toEqual(
230230
expect.arrayContaining([
231231
{
@@ -262,7 +262,7 @@ describe('server', () => {
262262
)
263263

264264
// Entire list
265-
expect('length' in result && result.length > 50).toBe(true)
265+
expect(result && 'length' in result && result.length > 50).toBe(true)
266266
})
267267

268268
it('responds to onCompletion with empty list when word is a comment', async () => {

server/src/analyser.ts

+29-14
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export default class Analyzer {
4040
parser,
4141
}: {
4242
connection: LSP.Connection
43-
rootPath: string | null
43+
rootPath: LSP.InitializeParams['rootPath']
4444
parser: Parser
4545
}): Promise<Analyzer> {
4646
const analyzer = new Analyzer(parser)
@@ -154,6 +154,13 @@ export default class Analyzer {
154154
// encounters newlines.
155155
const interestingNode = leafNode.type === 'word' ? leafNode.parent : leafNode
156156

157+
if (!interestingNode) {
158+
return {
159+
status: 'error',
160+
message: 'no interestingNode found',
161+
}
162+
}
163+
157164
const cmd = this.uriToFileContent[params.textDocument.uri].slice(
158165
interestingNode.startIndex,
159166
interestingNode.endIndex,
@@ -216,21 +223,23 @@ export default class Analyzer {
216223
const locations: LSP.Location[] = []
217224

218225
TreeSitterUtil.forEach(tree.rootNode, n => {
219-
let name: string = null
220-
let rng: LSP.Range = null
226+
let name: null | string = null
227+
let range: null | LSP.Range = null
221228

222229
if (TreeSitterUtil.isReference(n)) {
223230
const node = n.firstNamedChild || n
224231
name = contents.slice(node.startIndex, node.endIndex)
225-
rng = TreeSitterUtil.range(node)
232+
range = TreeSitterUtil.range(node)
226233
} else if (TreeSitterUtil.isDefinition(n)) {
227234
const namedNode = n.firstNamedChild
228-
name = contents.slice(namedNode.startIndex, namedNode.endIndex)
229-
rng = TreeSitterUtil.range(n.firstNamedChild)
235+
if (namedNode) {
236+
name = contents.slice(namedNode.startIndex, namedNode.endIndex)
237+
range = TreeSitterUtil.range(namedNode)
238+
}
230239
}
231240

232-
if (name === query) {
233-
locations.push(LSP.Location.create(uri, rng))
241+
if (name === query && range !== null) {
242+
locations.push(LSP.Location.create(uri, range))
234243
}
235244
})
236245

@@ -294,16 +303,22 @@ export default class Analyzer {
294303
return
295304
} else if (TreeSitterUtil.isDefinition(n)) {
296305
const named = n.firstNamedChild
306+
307+
if (named === null) {
308+
return
309+
}
310+
297311
const name = contents.slice(named.startIndex, named.endIndex)
298312
const namedDeclarations = this.uriToDeclarations[uri][name] || []
299313

300314
const parent = TreeSitterUtil.findParent(n, p => p.type === 'function_definition')
301-
const parentName = parent
302-
? contents.slice(
303-
parent.firstNamedChild.startIndex,
304-
parent.firstNamedChild.endIndex,
305-
)
306-
: null
315+
const parentName =
316+
parent && parent.firstNamedChild
317+
? contents.slice(
318+
parent.firstNamedChild.startIndex,
319+
parent.firstNamedChild.endIndex,
320+
)
321+
: '' // TODO: unsure what we should do here?
307322

308323
namedDeclarations.push(
309324
LSP.SymbolInformation.create(

server/src/server.ts

+35-16
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,14 @@ export default class BashServer {
2727
): Promise<BashServer> {
2828
const parser = await initializeParser()
2929

30+
const { PATH } = process.env
31+
32+
if (!PATH) {
33+
throw new Error('Expected PATH environment variable to be set')
34+
}
35+
3036
return Promise.all([
31-
Executables.fromPath(process.env.PATH),
37+
Executables.fromPath(PATH),
3238
Analyzer.fromRoot({ connection, rootPath, parser }),
3339
]).then(xs => {
3440
const executables = xs[0]
@@ -128,11 +134,17 @@ export default class BashServer {
128134
)
129135
}
130136

131-
private async onHover(params: LSP.TextDocumentPositionParams): Promise<LSP.Hover> {
137+
private async onHover(
138+
params: LSP.TextDocumentPositionParams,
139+
): Promise<LSP.Hover | null> {
132140
const word = this.getWordAtPoint(params)
133141

134142
this.logRequest({ request: 'onHover', params, word })
135143

144+
if (!word) {
145+
return null
146+
}
147+
136148
const explainshellEndpoint = config.getExplainshellEndpoint()
137149
if (explainshellEndpoint) {
138150
this.connection.console.log(`Query ${explainshellEndpoint}`)
@@ -185,9 +197,12 @@ export default class BashServer {
185197
return null
186198
}
187199

188-
private onDefinition(params: LSP.TextDocumentPositionParams): LSP.Definition {
200+
private onDefinition(params: LSP.TextDocumentPositionParams): LSP.Definition | null {
189201
const word = this.getWordAtPoint(params)
190202
this.logRequest({ request: 'onDefinition', params, word })
203+
if (!word) {
204+
return null
205+
}
191206
return this.analyzer.findDefinition(word)
192207
}
193208

@@ -203,22 +218,23 @@ export default class BashServer {
203218

204219
private onDocumentHighlight(
205220
params: LSP.TextDocumentPositionParams,
206-
): LSP.DocumentHighlight[] {
221+
): LSP.DocumentHighlight[] | null {
207222
const word = this.getWordAtPoint(params)
208223
this.logRequest({ request: 'onDocumentHighlight', params, word })
209-
210224
if (!word) {
211-
return []
225+
return null
212226
}
213-
214227
return this.analyzer
215228
.findOccurrences(params.textDocument.uri, word)
216229
.map(n => ({ range: n.range }))
217230
}
218231

219-
private onReferences(params: LSP.ReferenceParams): LSP.Location[] {
232+
private onReferences(params: LSP.ReferenceParams): LSP.Location[] | null {
220233
const word = this.getWordAtPoint(params)
221234
this.logRequest({ request: 'onReferences', params, word })
235+
if (!word) {
236+
return null
237+
}
222238
return this.analyzer.findReferences(word)
223239
}
224240

@@ -228,12 +244,15 @@ export default class BashServer {
228244

229245
const currentUri = params.textDocument.uri
230246

231-
const symbolCompletions = getCompletionItemsForSymbols({
232-
symbols: this.analyzer.findSymbolsMatchingWord({
233-
word,
234-
}),
235-
currentUri,
236-
})
247+
const symbolCompletions =
248+
word === null
249+
? []
250+
: getCompletionItemsForSymbols({
251+
symbols: this.analyzer.findSymbolsMatchingWord({
252+
word,
253+
}),
254+
currentUri,
255+
})
237256

238257
const reservedWordsCompletions = ReservedWords.LIST.map(reservedWord => ({
239258
label: reservedWord,
@@ -288,11 +307,11 @@ export default class BashServer {
288307
}
289308

290309
private async onCompletionResolve(
291-
item: BashCompletionItem,
310+
item: LSP.CompletionItem,
292311
): Promise<LSP.CompletionItem> {
293312
const {
294313
data: { name, type },
295-
} = item
314+
} = item as BashCompletionItem
296315

297316
this.connection.console.log(`onCompletionResolve name=${name} type=${type}`)
298317

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { uniqueBasedOnHash } from '../array'
2+
3+
describe('uniqueBasedOnHash', () => {
4+
it('returns a list of unique elements', () => {
5+
type Item = { x: string; y: number }
6+
7+
const items: Item[] = [
8+
{ x: '1', y: 1 },
9+
{ x: '1', y: 2 },
10+
{ x: '2', y: 3 },
11+
]
12+
13+
const hashFunction = ({ x }: Item) => x
14+
const result = uniqueBasedOnHash(items, hashFunction)
15+
expect(result).toEqual([
16+
{ x: '1', y: 1 },
17+
{ x: '2', y: 3 },
18+
])
19+
})
20+
})

server/src/util/array.ts

+14-7
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,24 @@ export function uniq<A>(a: A[]): A[] {
1515

1616
/**
1717
* Removed all duplicates from the list based on the hash function.
18+
* First element matching the hash function wins.
1819
*/
19-
export function uniqueBasedOnHash<A>(list: A[], elementToHash: (a: A) => string): A[] {
20-
const hashSet = new Set()
20+
export function uniqueBasedOnHash<A extends Record<string, any>>(
21+
list: A[],
22+
elementToHash: (a: A) => string,
23+
__result: A[] = [],
24+
): A[] {
25+
const result: typeof list = []
26+
const hashSet = new Set<string>()
2127

22-
return list.reduce((accumulator, currentValue) => {
23-
const hash = elementToHash(currentValue)
28+
list.forEach(element => {
29+
const hash = elementToHash(element)
2430
if (hashSet.has(hash)) {
25-
return accumulator
31+
return
2632
}
2733
hashSet.add(hash)
34+
result.push(element)
35+
})
2836

29-
return [...accumulator, currentValue]
30-
}, [])
37+
return result
3138
}

server/src/util/tree-sitter.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,8 @@ export function namedLeafDescendantForPosition(
8383
}
8484

8585
function searchForLeafNode(point: Point, parent: SyntaxNode): SyntaxNode | null {
86-
let child: SyntaxNode = parent.firstNamedChild
86+
let child = parent.firstNamedChild
87+
8788
while (child) {
8889
if (
8990
pointsEqual(child.startPosition, point) ||

server/tsconfig.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
"dom", // Required by @types/turndown
88
"es2016"
99
],
10-
// TODO: enable:
11-
"strict": false,
10+
"strict": true,
1211
}
1312
}

0 commit comments

Comments
 (0)