@@ -278,9 +278,6 @@ export default class Analyzer {
278278 /**
279279 * Find a symbol's original declaration and parent scope based on its original
280280 * definition with respect to its scope.
281- *
282- * TODO: Improve handling of multiple variable assignments and declarations in
283- * one declaration command.
284281 */
285282 public findOriginalDeclaration ( {
286283 position,
@@ -304,7 +301,23 @@ export default class Analyzer {
304301 let continueSearching = false
305302 let boundary = position . line
306303
307- const findUsingGlobalSemantics = ( base : Parser . SyntaxNode ) => {
304+ /**
305+ * Estimates if the `position` of the `word` given is within the expression
306+ * (after the equals sign) of the `definition` node (should be a declaration
307+ * command or variable assignment) given.
308+ *
309+ * Important in cases of `var="$var"`, if the `word` is `$var` then `var`
310+ * should be skipped and a higher scope should be checked for the original
311+ * declaration.
312+ */
313+ const isDefinedVariableInExpression = (
314+ definition : Parser . SyntaxNode ,
315+ variable : Parser . SyntaxNode ,
316+ ) =>
317+ definition . endPosition . row >= position . line &&
318+ ( variable . endPosition . column < position . character ||
319+ variable . endPosition . row < position . line )
320+ const findUsingGlobalSemantics = ( base : Parser . SyntaxNode , baseIsInUri = true ) => {
308321 TreeSitterUtil . forEach ( base , ( n ) => {
309322 if (
310323 ( declaration && ! continueSearching ) ||
@@ -314,47 +327,83 @@ export default class Analyzer {
314327 return false
315328 }
316329
317- let definedSymbol : Parser . SyntaxNode | null | undefined
318- // In cases of var="$var", if $var is being renamed, the definition
319- // containing $var should be skipped and a higher scope should be
320- // checked.
321- let definedVariableInExpression = false
322-
323- if ( kind === LSP . SymbolKind . Variable && n . type === 'variable_assignment' ) {
324- const declarationCommand = TreeSitterUtil . findParentOfType (
330+ // Declaration commands are handled separately from variable assignments
331+ // because declaration commands can declare variables without defining
332+ // them, while variable assignments require both declaration and
333+ // definition, so, there can be variable names within declaration
334+ // commands that are not children of variable assignments.
335+ if ( kind === LSP . SymbolKind . Variable && n . type === 'declaration_command' ) {
336+ const functionDefinition = TreeSitterUtil . findParentOfType (
325337 n ,
326- 'declaration_command ' ,
338+ 'function_definition ' ,
327339 )
340+ const isLocalDeclaration =
341+ ! ! functionDefinition &&
342+ functionDefinition . lastChild ?. type === 'compound_statement' &&
343+ [ 'local' , 'declare' , 'typeset' ] . includes ( n . firstChild ?. text as any ) &&
344+ ( base . type !== 'subshell' ||
345+ base . startPosition . row < functionDefinition . startPosition . row )
346+
347+ for ( const v of n . descendantsOfType ( 'variable_name' ) ) {
348+ if (
349+ v . text !== word ||
350+ TreeSitterUtil . findParentOfType ( v , [ 'simple_expansion' , 'expansion' ] )
351+ ) {
352+ continue
353+ }
328354
329- if (
330- declarationCommand &&
331- TreeSitterUtil . findParentOfType ( declarationCommand , 'function_definition' )
332- ?. lastChild ?. type === 'compound_statement' &&
333- [ 'local' , 'declare' , 'typeset' ] . includes (
334- declarationCommand . firstChild ?. text as any ,
335- )
336- ) {
337- return false
355+ if ( isLocalDeclaration ) {
356+ // Update boundary since any other instance of word below n can
357+ // now be considered local to a function or out of scope.
358+ boundary = n . startPosition . row
359+ break
360+ }
361+
362+ if ( baseIsInUri && ! isDefinedVariableInExpression ( n , v ) ) {
363+ declaration = v
364+ continueSearching = false
365+ break
366+ }
338367 }
339368
340- definedSymbol = n . descendantsOfType ( 'variable_name' ) . at ( 0 )
341- definedVariableInExpression =
342- n . endPosition . row >= position . line &&
343- ! ! definedSymbol &&
344- ( definedSymbol . endPosition . column < position . character ||
345- definedSymbol . endPosition . row < position . line )
346- } else if (
369+ // This return is important as it makes sure that the next if
370+ // statement only catches variable assignments outside of declaration
371+ // commands.
372+ return false
373+ }
374+
375+ if (
347376 kind === LSP . SymbolKind . Variable &&
348- ( n . type === 'for_statement' || ( n . type === 'command' && n . text . includes ( ':=' ) ) )
377+ ( [ 'variable_assignment' , 'for_statement' ] . includes ( n . type ) ||
378+ ( n . type === 'command' && n . text . includes ( ':=' ) ) )
349379 ) {
350- definedSymbol = n . descendantsOfType ( 'variable_name' ) . at ( 0 )
351- } else if ( kind === LSP . SymbolKind . Function && n . type === 'function_definition' ) {
352- definedSymbol = n . firstNamedChild
380+ const definedVariable = n . descendantsOfType ( 'variable_name' ) . at ( 0 )
381+ const definedVariableInExpression =
382+ baseIsInUri &&
383+ n . type === 'variable_assignment' &&
384+ ! ! definedVariable &&
385+ isDefinedVariableInExpression ( n , definedVariable )
386+
387+ if ( definedVariable ?. text === word && ! definedVariableInExpression ) {
388+ declaration = definedVariable
389+ continueSearching = n . type === 'command'
390+
391+ // The original declaration could be inside a for statement, so only
392+ // return false when the original declaration is found.
393+ return false
394+ }
395+
396+ return true
353397 }
354398
355- if ( definedSymbol ?. text === word && ! definedVariableInExpression ) {
356- declaration = definedSymbol
357- continueSearching = n . type === 'command'
399+ if (
400+ kind === LSP . SymbolKind . Function &&
401+ n . type === 'function_definition' &&
402+ n . firstNamedChild ?. text === word
403+ ) {
404+ declaration = n . firstNamedChild
405+ continueSearching = false
406+ return false
358407 }
359408
360409 return true
@@ -377,35 +426,30 @@ export default class Analyzer {
377426 return false
378427 }
379428
380- if ( n . type === 'declaration_command' ) {
381- if ( ! [ 'local' , 'declare' , 'typeset' ] . includes ( n . firstChild ?. text as any ) ) {
382- return false
383- }
429+ if ( n . type !== 'declaration_command' ) {
430+ return true
431+ }
384432
385- for ( const v of n . descendantsOfType ( 'variable_name' ) ) {
386- if ( TreeSitterUtil . findParentOfType ( v , [ 'simple_expansion' , 'expansion' ] ) ) {
387- continue
388- }
433+ if ( ! [ 'local' , 'declare' , 'typeset' ] . includes ( n . firstChild ?. text as any ) ) {
434+ return false
435+ }
389436
390- // In cases of var="$var", if $var is being renamed, var should be
391- // skipped and a higher scope should be checked for the original
392- // declaration.
393- const definedVariableInExpression =
394- n . endPosition . row >= position . line &&
395- ( v . endPosition . column < position . character ||
396- v . endPosition . row < position . line )
397-
398- if ( v . text === word && ! definedVariableInExpression ) {
399- declaration = v
400- continueSearching = false
401- break
402- }
437+ for ( const v of n . descendantsOfType ( 'variable_name' ) ) {
438+ if (
439+ v . text !== word ||
440+ TreeSitterUtil . findParentOfType ( v , [ 'simple_expansion' , 'expansion' ] )
441+ ) {
442+ continue
403443 }
404444
405- return false
445+ if ( ! isDefinedVariableInExpression ( n , v ) ) {
446+ declaration = v
447+ continueSearching = false
448+ break
449+ }
406450 }
407451
408- return true
452+ return false
409453 } )
410454 } else if ( parent . type === 'subshell' ) {
411455 findUsingGlobalSemantics ( parent )
@@ -415,6 +459,9 @@ export default class Analyzer {
415459 break
416460 }
417461
462+ // Update boundary since any other instance of word within or below the
463+ // current parent can now be considered local to that parent or out of
464+ // scope.
418465 boundary = parent . startPosition . row
419466 parent = this . parentScope ( parent )
420467 }
@@ -423,12 +470,20 @@ export default class Analyzer {
423470 for ( const u of this . getReachableUris ( { fromUri : uri } ) ) {
424471 const root = this . uriToAnalyzedDocument [ u ] ?. tree . rootNode
425472
426- if ( root ) {
427- if ( u !== uri ) {
428- boundary = root . endPosition . row
429- }
473+ if ( ! root ) {
474+ continue
475+ }
430476
477+ if ( u === uri ) {
478+ // Reset boundary so globally defined variables within any functions
479+ // already searched can be found.
480+ boundary = position . line
431481 findUsingGlobalSemantics ( root )
482+ } else {
483+ // Set boundary to EOF since any position taken from the original
484+ // uri/file does not apply to other uris/files.
485+ boundary = root . endPosition . row
486+ findUsingGlobalSemantics ( root , false )
432487 }
433488
434489 if ( declaration && ! continueSearching ) {
0 commit comments