@@ -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 ( ) ;
0 commit comments