Skip to content

Commit d1e8a76

Browse files
committed
Use AST instead of nasty regular expressions
1 parent 838dbcb commit d1e8a76

File tree

3 files changed

+44
-47
lines changed

3 files changed

+44
-47
lines changed

server/src/analyser.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ export default class Analyzer {
7878
document,
7979
globalDeclarations,
8080
sourcedUris: sourcing.getSourcedUris({
81-
fileContent,
8281
fileUri: uri,
8382
rootPath: this.workspaceFolder,
8483
tree,

server/src/util/declarations.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export function getAllDeclarationsInTree({
7777
}): LSP.SymbolInformation[] {
7878
const symbols: LSP.SymbolInformation[] = []
7979

80-
TreeSitterUtil.forEach(tree.rootNode, (node: Parser.SyntaxNode) => {
80+
TreeSitterUtil.forEach(tree.rootNode, (node) => {
8181
if (TreeSitterUtil.isDefinition(node)) {
8282
const symbol = nodeToSymbolInformation({ node, uri })
8383

@@ -189,7 +189,7 @@ function getAllGlobalVariableDeclarations({
189189
}) {
190190
const declarations: Declarations = {}
191191

192-
TreeSitterUtil.forEach(rootNode, (node: Parser.SyntaxNode) => {
192+
TreeSitterUtil.forEach(rootNode, (node) => {
193193
if (
194194
node.type === 'variable_assignment' &&
195195
// exclude local variables

server/src/util/sourcing.ts

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -4,49 +4,65 @@ import * as LSP from 'vscode-languageserver'
44
import * as Parser from 'web-tree-sitter'
55

66
import { untildify } from './fs'
7+
import { logger } from './logger'
8+
import * as TreeSitterUtil from './tree-sitter'
79

8-
// Until the grammar supports sourcing, we use this little regular expression
9-
const SOURCING_STATEMENTS = /^(?:\t|[ ])*(?:source|[.])\s*(\S*)/
1010
const SOURCING_COMMANDS = ['source', '.']
1111

1212
/**
1313
* Analysis the given file content and returns a set of URIs that are
1414
* sourced. Note that the URIs are resolved.
1515
*/
1616
export function getSourcedUris({
17-
fileContent,
1817
fileUri,
1918
rootPath,
2019
tree,
2120
}: {
22-
fileContent: string
2321
fileUri: string
2422
rootPath: string | null
2523
tree: Parser.Tree
2624
}): Set<string> {
2725
const uris: Set<string> = new Set([])
2826
const rootPaths = [path.dirname(fileUri), rootPath].filter(Boolean) as string[]
2927

30-
fileContent.split(/\r?\n/).forEach((line, lineIndex) => {
31-
const match = line.match(SOURCING_STATEMENTS)
32-
if (match) {
33-
const [statement, word] = match
34-
35-
if (tree.rootNode) {
36-
const node = tree.rootNode.descendantForPosition({
37-
row: lineIndex,
38-
column: statement.length - 2,
39-
})
40-
if (['heredoc_body', 'raw_string'].includes(node?.type)) {
41-
return
28+
// find all source commands in the tree
29+
TreeSitterUtil.forEach(tree.rootNode, (node) => {
30+
if (node.type === 'command') {
31+
const [commandNameNode, argumentNode] = node.namedChildren
32+
if (
33+
commandNameNode.type === 'command_name' &&
34+
SOURCING_COMMANDS.includes(commandNameNode?.text)
35+
) {
36+
let word = null
37+
if (argumentNode.type === 'word') {
38+
word = argumentNode.text
39+
} else if (argumentNode.type === 'string') {
40+
if (argumentNode.namedChildren.length === 0) {
41+
word = argumentNode.text.slice(1, -1)
42+
} else if (
43+
argumentNode.namedChildren.every((n) => n.type === 'simple_expansion')
44+
) {
45+
// not supported
46+
} else {
47+
logger.warn(
48+
'Sourcing: unhandled argumentNode=string case',
49+
argumentNode.namedChildren.map((c) => ({ type: c.type, text: c.text })),
50+
)
51+
}
52+
} else {
53+
logger.warn('Sourcing: unhandled argumentNode case', argumentNode.type)
4254
}
43-
}
4455

45-
const sourcedUri = getSourcedUri({ rootPaths, word })
46-
if (sourcedUri) {
47-
uris.add(sourcedUri)
56+
if (word) {
57+
const sourcedUri = getSourcedUri({ rootPaths, word })
58+
if (sourcedUri) {
59+
uris.add(sourcedUri)
60+
}
61+
}
4862
}
4963
}
64+
65+
return true
5066
})
5167

5268
return uris
@@ -102,17 +118,6 @@ export function getSourcedLocation({
102118
return null
103119
}
104120

105-
const stripQuotes = (path: string): string => {
106-
const first = path[0]
107-
const last = path[path.length - 1]
108-
109-
if (first === last && [`"`, `'`].includes(first)) {
110-
return path.slice(1, -1)
111-
}
112-
113-
return path
114-
}
115-
116121
/**
117122
* Tries to parse the given path and returns a URI if possible.
118123
* - Filters out dynamic sources
@@ -131,27 +136,20 @@ function getSourcedUri({
131136
rootPaths: string[]
132137
word: string
133138
}): string | null {
134-
let unquotedPath = stripQuotes(word)
135-
136-
if (unquotedPath.includes('$')) {
137-
// NOTE: we don't support dynamic sourcing
138-
return null
139-
}
140-
141-
if (unquotedPath.startsWith('~')) {
142-
unquotedPath = untildify(unquotedPath)
139+
if (word.startsWith('~')) {
140+
word = untildify(word)
143141
}
144142

145-
if (unquotedPath.startsWith('/')) {
146-
if (fs.existsSync(unquotedPath)) {
147-
return `file://${unquotedPath}`
143+
if (word.startsWith('/')) {
144+
if (fs.existsSync(word)) {
145+
return `file://${word}`
148146
}
149147
return null
150148
}
151149

152150
// resolve relative path
153151
for (const rootPath of rootPaths) {
154-
const potentialPath = path.join(rootPath.replace('file://', ''), unquotedPath)
152+
const potentialPath = path.join(rootPath.replace('file://', ''), word)
155153

156154
// check if path is a file
157155
if (fs.existsSync(potentialPath)) {

0 commit comments

Comments
 (0)