@@ -185,8 +185,10 @@ export enum ScopeType {
185185 VARIABLE ,
186186 /** A variable declaration in a signature */
187187 PARAMETER ,
188- /** Any reference type that isn't a declaration. */
189- REFERENCE
188+ /** A reference to some type of declaration. */
189+ REFERENCE ,
190+ /** A special reference type. */
191+ ATTRIBUTE
190192}
191193
192194export enum AssignmentType {
@@ -211,6 +213,7 @@ export class ScopeItemCapability {
211213 } ;
212214 parameters ?: Map < string , ScopeItemCapability [ ] > ;
213215 references ?: Map < string , ScopeItemCapability [ ] > ;
216+ attributes ?: Map < string , ScopeItemCapability [ ] > ;
214217
215218 // Special scope references for easier resolution of names.
216219 implicitDeclarations ?: Map < string , ScopeItemCapability [ ] > ;
@@ -288,8 +291,10 @@ export class ScopeItemCapability {
288291 * Recursively build from this node down.
289292 */
290293 build ( ) : void {
291- if ( this . type === ScopeType . REFERENCE ) {
292- // Link to declaration if it exists.
294+ if ( this . type === ScopeType . ATTRIBUTE ) {
295+ this . resolveAttribute ( ) ;
296+ this . validateAttributes ( ) ;
297+ } else if ( this . type === ScopeType . REFERENCE ) {
293298 this . resolveLinks ( ) ;
294299 this . validateLink ( ) ;
295300 } else {
@@ -308,6 +313,7 @@ export class ScopeItemCapability {
308313 this . properties ?. letters ?. forEach ( items => items . forEach ( item => item . build ( ) ) ) ;
309314 this . properties ?. setters ?. forEach ( items => items . forEach ( item => item . build ( ) ) ) ;
310315 this . references ?. forEach ( items => items . forEach ( item => item . build ( ) ) ) ;
316+ this . attributes ?. forEach ( items => items . forEach ( item => item . build ( ) ) ) ;
311317
312318 this . isDirty = false ;
313319 }
@@ -460,6 +466,88 @@ export class ScopeItemCapability {
460466 }
461467 }
462468
469+ private resolveAttribute ( ) {
470+ /**
471+ * Most attributes will be belong to the parent. Variable attributes
472+ * will belong to the same scope as the item they refer to.
473+ *
474+ * We set one way links here to facilitate renaming.
475+ * Setting an attribute as a back link will impact unused diagnostics.
476+ */
477+
478+ if ( ! this . parent ) {
479+ Services . logger . error ( `Expected parent for attribute ${ this . name } ` ) ;
480+ throw new Error ( "Attribute scope item has no parent." ) ;
481+ }
482+
483+ // The immediate parent is probably the linked item.
484+ if ( this . name === this . parent . name ) {
485+ this . link = this . parent ;
486+ return ;
487+ }
488+
489+ // If not, we may be a variable attribute (shared parent).
490+ const declarations = this . parent . properties ?. getters ?. get ( this . name ) ;
491+ if ( ! declarations ) {
492+ return ;
493+ }
494+
495+ // Handle a single declaration found.
496+ if ( declarations . length === 1 ) {
497+ this . link = declarations [ 0 ] ;
498+ this . parent . moveAttribute ( this , declarations [ 0 ] ) ;
499+ return ;
500+ }
501+
502+ // Handle duplicate declarations by attaching to the closest above.
503+ const targetRow = this . range ?. start . line ?? 0 ;
504+ let closestDeclaration : ScopeItemCapability | undefined ;
505+ for ( const declaration of declarations ) {
506+ const declarationRow = declaration ?. range ?. start . line ?? 0 ;
507+ if ( declarationRow === 0 || declarationRow >= targetRow ) {
508+ return ;
509+ }
510+
511+ if ( ! closestDeclaration ) {
512+ closestDeclaration = declaration ;
513+ return ;
514+ }
515+
516+ const closestRow = closestDeclaration . range ?. start . line ?? 0 ;
517+ if ( targetRow > declarationRow && declarationRow > closestRow ) {
518+ closestDeclaration = declaration ;
519+ }
520+ }
521+
522+ if ( closestDeclaration ) {
523+ this . link = closestDeclaration ;
524+ this . parent . moveAttribute ( this , closestDeclaration ) ;
525+ }
526+ }
527+
528+ moveAttribute ( attr : ScopeItemCapability , destination : ScopeItemCapability ) {
529+ const items = this . attributes ?. get ( attr . name ) ;
530+ if ( ! items || items . length === 0 ) {
531+ return ;
532+ }
533+
534+ const unmoved : ScopeItemCapability [ ] = [ ] ;
535+ items . forEach ( item => {
536+ const isLocMatch = item . locationUri === attr . locationUri ;
537+ const isRangeMatch = rangeEquals ( item . element ?. context . range , attr . range ) ;
538+ if ( isLocMatch && isRangeMatch ) {
539+ destination . attributes ??= new Map ( ) ;
540+ destination . addItem ( destination . attributes , item ) ;
541+ } else {
542+ unmoved . push ( item ) ;
543+ }
544+ } ) ;
545+ }
546+
547+ private validateAttributes ( ) {
548+ // Attributes must be in specific locations to work.
549+ }
550+
463551 private resolveLinks ( ) {
464552
465553 // Resolve where we have no member access names.
@@ -726,6 +814,13 @@ export class ScopeItemCapability {
726814 return this ;
727815 }
728816
817+ // Register attributes
818+ if ( item . type === ScopeType . ATTRIBUTE ) {
819+ item . parent . attributes ??= new Map ( ) ;
820+ item . parent . addItem ( item . parent . attributes , item ) ;
821+ return this ;
822+ }
823+
729824 // Add implicitly accessible names to the project scope.
730825 if ( item . isPublicScope && this . project && this !== this . project ) {
731826 this . project . implicitDeclarations ??= new Map ( ) ;
@@ -913,10 +1008,16 @@ export class ScopeItemCapability {
9131008
9141009 private getItemsIdentifiedAtPosition ( position : Position , results : ScopeItemCapability [ ] = [ ] , searchItems : ScopeItemCapability [ ] = [ ] ) : void {
9151010 while ( searchItems . length > 0 ) {
1011+ // Get the next scope to search.
9161012 const scope = searchItems . pop ( ) ;
1013+ if ( scope === undefined ) continue ;
1014+
1015+ // Get the standard maps and add attributes to them if they exist.
1016+ const scopeMaps = scope . maps ?? [ ] ;
1017+ if ( scope . attributes ) scopeMaps . push ( scope . attributes ) ;
9171018
9181019 // Check all items for whether they have a name overlap or a scope overlap.
919- scope ?. maps . forEach ( map => map . forEach ( items => items . forEach ( item => {
1020+ scopeMaps . forEach ( map => map . forEach ( items => items . forEach ( item => {
9201021 const elementRange = item . range ;
9211022 const identifierRange = item . element ?. identifierCapability ?. range ;
9221023 if ( identifierRange && isPositionInsideRange ( position , identifierRange ) ) {
@@ -931,13 +1032,13 @@ export class ScopeItemCapability {
9311032 }
9321033
9331034 getRenameItems ( uri : string , position : Position ) : ScopeItemCapability [ ] {
934- const module = this . findModuleByUri ( uri ) ;
935- if ( ! module ) {
1035+ const moduleScope = this . findModuleByUri ( uri ) ;
1036+ if ( ! moduleScope ) {
9361037 return [ ] ;
9371038 }
9381039
9391040 const itemsAtPosition : ScopeItemCapability [ ] = [ ] ;
940- this . getItemsIdentifiedAtPosition ( position , itemsAtPosition , [ module ] ) ;
1041+ this . getItemsIdentifiedAtPosition ( position , itemsAtPosition , [ moduleScope ] ) ;
9411042 if ( itemsAtPosition . length === 0 ) {
9421043 Services . logger . warn ( `Nothing to rename.` ) ;
9431044 return [ ] ;
@@ -956,14 +1057,15 @@ export class ScopeItemCapability {
9561057 item . parent . properties . letters ?. get ( item . identifier )
9571058 ]
9581059 : item
959- ) . flat ( ) . flat ( ) . flat ( ) . filter ( x => ! ! x ) ;
1060+ ) . flat ( 2 ) . filter ( x => ! ! x ) ;
9601061
961- // Add backlinks for each item.
962- const addedBacklinks = propertyIncludedItems . map ( item =>
963- item . backlinks ? [ item , ...item . backlinks ] : item
964- ) . flat ( ) . flat ( ) ;
1062+ // Add backlinks and attributes for each item.
1063+ const addedReferences = propertyIncludedItems . map ( item => [
1064+ item . backlinks ? [ item , ...item . backlinks ] : item ,
1065+ item . attributes ?. get ( item . name ) ? [ item , ...item . attributes . get ( item . name ) ! ] : item
1066+ ] ) . flat ( 2 ) ;
9651067
966- const uniqueItemsAtPosition = this . removeDuplicatesByRange ( addedBacklinks ) ;
1068+ const uniqueItemsAtPosition = this . removeDuplicatesByRange ( addedReferences ) ;
9671069 return uniqueItemsAtPosition ;
9681070 }
9691071
0 commit comments