-
Notifications
You must be signed in to change notification settings - Fork 132
/
Copy pathdeclarations.ts
232 lines (199 loc) · 5.94 KB
/
declarations.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
import * as LSP from 'vscode-languageserver/node'
import * as Parser from 'web-tree-sitter'
import * as TreeSitterUtil from './tree-sitter'
const TREE_SITTER_TYPE_TO_LSP_KIND: { [type: string]: LSP.SymbolKind | undefined } = {
// These keys are using underscores as that's the naming convention in tree-sitter.
environment_variable_assignment: LSP.SymbolKind.Variable,
function_definition: LSP.SymbolKind.Function,
variable_assignment: LSP.SymbolKind.Variable,
}
export type GlobalDeclarations = { [word: string]: LSP.SymbolInformation }
export type Declarations = { [word: string]: LSP.SymbolInformation[] }
const GLOBAL_DECLARATION_LEAF_NODE_TYPES = new Set([
'if_statement',
'function_definition',
])
/**
* Returns declarations (functions or variables) from a given root node
* that would be available after sourcing the file. This currently does
* not include global variables defined inside if statements or functions
* as we do not do any flow tracing.
*
* Will only return one declaration per symbol name – the latest definition.
* This behavior is consistent with how Bash behaves, but differs between
* LSP servers.
*
* Used when finding declarations for sourced files and to get declarations
* for the entire workspace.
*/
export function getGlobalDeclarations({
tree,
uri,
}: {
tree: Parser.Tree
uri: string
}): GlobalDeclarations {
const globalDeclarations: GlobalDeclarations = {}
TreeSitterUtil.forEach(tree.rootNode, (node) => {
const followChildren = !GLOBAL_DECLARATION_LEAF_NODE_TYPES.has(node.type)
if (TreeSitterUtil.isDefinition(node)) {
const symbol = nodeToSymbolInformation({ node, uri })
if (symbol) {
const word = symbol.name
globalDeclarations[word] = symbol
}
}
return followChildren
})
return globalDeclarations
}
/**
* Returns all declarations (functions or variables) from a given tree.
* This includes local variables.
*/
export function getAllDeclarationsInTree({
tree,
uri,
}: {
tree: Parser.Tree
uri: string
}): LSP.SymbolInformation[] {
const symbols: LSP.SymbolInformation[] = []
TreeSitterUtil.forEach(tree.rootNode, (node) => {
if (TreeSitterUtil.isDefinition(node)) {
const symbol = nodeToSymbolInformation({ node, uri })
if (symbol) {
symbols.push(symbol)
}
}
return
})
return symbols
}
/**
* Returns declarations available for the given file and location.
* The heuristics used is a simplification compared to bash behaviour,
* but deemed good enough, compared to the complexity of flow tracing.
*
* Used when getting declarations for the current scope.
*/
export function getLocalDeclarations({
node,
uri,
}: {
node: Parser.SyntaxNode | null
uri: string
}): Declarations {
const declarations: Declarations = {}
// Bottom up traversal to capture all local and scoped declarations
const walk = (node: Parser.SyntaxNode | null) => {
// NOTE: there is also node.walk
if (node) {
for (const childNode of node.children) {
let symbol: LSP.SymbolInformation | null = null
// local variables
if (childNode.type === 'declaration_command') {
const variableAssignmentNode = childNode.children.filter(
(child) => child.type === 'variable_assignment',
)[0]
if (variableAssignmentNode) {
symbol = nodeToSymbolInformation({
node: variableAssignmentNode,
uri,
})
}
} else if (TreeSitterUtil.isDefinition(childNode)) {
symbol = nodeToSymbolInformation({ node: childNode, uri })
} else if (childNode.type === 'for_statement') {
const variableNode = childNode.child(1)
if (variableNode && variableNode.type === 'variable_name') {
symbol = LSP.SymbolInformation.create(
variableNode.text,
LSP.SymbolKind.Variable,
TreeSitterUtil.range(variableNode),
uri,
)
}
}
if (symbol) {
if (!declarations[symbol.name]) {
declarations[symbol.name] = []
}
declarations[symbol.name].push(symbol)
}
}
walk(node.parent)
}
}
walk(node)
// Top down traversal to add missing global variables from within functions
if (node) {
const rootNode =
node.type === 'program'
? node
: TreeSitterUtil.findParent(node, (p) => p.type === 'program')
if (rootNode) {
// In case of parsing errors, the root node might not be found
Object.entries(
getAllGlobalVariableDeclarations({
rootNode,
uri,
}),
).map(([name, symbols]) => {
if (!declarations[name]) {
declarations[name] = symbols
}
})
}
}
return declarations
}
function getAllGlobalVariableDeclarations({
uri,
rootNode,
}: {
uri: string
rootNode: Parser.SyntaxNode
}) {
const declarations: Declarations = {}
TreeSitterUtil.forEach(rootNode, (node) => {
if (
node.type === 'variable_assignment' &&
// exclude local variables
node.parent?.type !== 'declaration_command'
) {
const symbol = nodeToSymbolInformation({ node, uri })
if (symbol) {
if (!declarations[symbol.name]) {
declarations[symbol.name] = []
}
declarations[symbol.name].push(symbol)
}
}
return
})
return declarations
}
function nodeToSymbolInformation({
node,
uri,
}: {
node: Parser.SyntaxNode
uri: string
}): LSP.SymbolInformation | null {
const named = node.firstNamedChild
if (named === null) {
return null
}
const containerName =
TreeSitterUtil.findParent(node, (p) => p.type === 'function_definition')
?.firstNamedChild?.text || ''
const kind = TREE_SITTER_TYPE_TO_LSP_KIND[node.type]
return LSP.SymbolInformation.create(
named.text,
kind || LSP.SymbolKind.Variable,
TreeSitterUtil.range(node),
uri,
containerName,
)
}