@@ -102,20 +102,32 @@ function getSourcedPathInfoFromNode({
102
102
}
103
103
}
104
104
105
- if ( argumentNode . type === 'word' ) {
105
+ const strValue = TreeSitterUtil . resolveStaticString ( argumentNode )
106
+ if ( strValue !== null ) {
106
107
return {
107
- sourcedPath : argumentNode . text ,
108
+ sourcedPath : strValue ,
108
109
}
109
110
}
110
111
111
- if ( argumentNode . type === 'string' || argumentNode . type === 'raw_string' ) {
112
- const children = argumentNode . namedChildren
113
- if (
114
- children . length === 0 ||
115
- ( children . length === 1 && children [ 0 ] . type === 'string_content' )
116
- ) {
112
+ // Strip one leading dynamic section.
113
+ if ( argumentNode . type === 'string' && argumentNode . namedChildren . length === 1 ) {
114
+ const [ variableNode ] = argumentNode . namedChildren
115
+ if ( TreeSitterUtil . isExpansion ( variableNode ) ) {
116
+ const stringContents = argumentNode . text . slice ( 1 , - 1 )
117
+ if ( stringContents . startsWith ( `${ variableNode . text } /` ) ) {
118
+ return {
119
+ sourcedPath : `.${ stringContents . slice ( variableNode . text . length ) } ` ,
120
+ }
121
+ }
122
+ }
123
+ }
124
+
125
+ if ( argumentNode . type === 'concatenation' ) {
126
+ // Strip one leading dynamic section from a concatenation node.
127
+ const sourcedPath = resolveSourceFromConcatenation ( argumentNode )
128
+ if ( sourcedPath ) {
117
129
return {
118
- sourcedPath : argumentNode . text . slice ( 1 , - 1 ) ,
130
+ sourcedPath,
119
131
}
120
132
}
121
133
}
@@ -171,3 +183,49 @@ function resolveSourcedUri({
171
183
172
184
return null
173
185
}
186
+
187
+ /*
188
+ * Resolves the source path from a concatenation node, stripping a leading dynamic directory segment.
189
+ * Returns null if the source path can't be statically determined after stripping a segment.
190
+ * Note: If a non-concatenation node is passed, null will be returned. This is likely a programmer error.
191
+ */
192
+ function resolveSourceFromConcatenation ( node : Parser . SyntaxNode ) : string | null {
193
+ if ( node . type !== 'concatenation' ) return null
194
+ const stringValue = TreeSitterUtil . resolveStaticString ( node )
195
+ if ( stringValue !== null ) return stringValue // This string is fully static.
196
+
197
+ const values : string [ ] = [ ]
198
+ // Since the string must begin with the variable, the variable must be in the first child.
199
+ const [ firstNode , ...rest ] = node . namedChildren
200
+ // The first child is static, this means one of the other children is not!
201
+ if ( TreeSitterUtil . resolveStaticString ( firstNode ) !== null ) return null
202
+
203
+ // if the string is unquoted, the first child is the variable, so there's no more text in it.
204
+ if ( ! TreeSitterUtil . isExpansion ( firstNode ) ) {
205
+ if ( firstNode . namedChildCount > 1 ) return null // Only one variable is allowed.
206
+ // Since the string must begin with the variable, the variable must be first child.
207
+ const variableNode = firstNode . namedChildren [ 0 ] // Get the variable (quoted case)
208
+ // This is command substitution!
209
+ if ( ! TreeSitterUtil . isExpansion ( variableNode ) ) return null
210
+ const stringContents = firstNode . text . slice ( 1 , - 1 )
211
+ // The string doesn't start with the variable!
212
+ if ( ! stringContents . startsWith ( variableNode . text ) ) return null
213
+ // Get the remaining static portion the string
214
+ values . push ( stringContents . slice ( variableNode . text . length ) )
215
+ }
216
+
217
+ for ( const child of rest ) {
218
+ const value = TreeSitterUtil . resolveStaticString ( child )
219
+ // The other values weren't statically determinable!
220
+ if ( value === null ) return null
221
+ values . push ( value )
222
+ }
223
+
224
+ // Join all our found static values together.
225
+ const staticResult = values . join ( '' )
226
+ // The path starts with slash, so trim the leading variable and replace with a dot
227
+ if ( staticResult . startsWith ( '/' ) ) return `.${ staticResult } `
228
+ // The path doesn't start with a slash, so it's invalid
229
+ // PERF: can we fail earlier than this?
230
+ return null
231
+ }
0 commit comments