1+ import { Position } from 'vscode-languageserver-protocol' ;
12import { Context } from '../context/Context' ;
2- import { IntrinsicFunction , ResourceAttribute , ResourceAttributesSet } from '../context/ContextType' ;
3+ import { IntrinsicFunction , ResourceAttribute , ResourceAttributesSet , TopLevelSection } from '../context/ContextType' ;
34import { ContextWithRelatedEntities } from '../context/ContextWithRelatedEntities' ;
5+ import { Resource } from '../context/semantic/Entity' ;
6+ import { EntityType } from '../context/semantic/SemanticTypes' ;
7+ import { SchemaRetriever } from '../schema/SchemaRetriever' ;
8+ import { LoggerFactory } from '../telemetry/LoggerFactory' ;
9+ import { determineGetAttPosition , extractAttributeName , extractGetAttResourceLogicalId } from '../utils/GetAttUtils' ;
410import { formatIntrinsicArgumentHover , getResourceAttributeValueDoc } from './HoverFormatter' ;
511import { HoverProvider } from './HoverProvider' ;
612
13+ const log = LoggerFactory . getLogger ( 'IntrinsicFunctionArgumentHoverProvider' ) ;
14+
715export class IntrinsicFunctionArgumentHoverProvider implements HoverProvider {
8- constructor ( ) { }
16+ constructor ( private readonly schemaRetriever : SchemaRetriever ) { }
917
10- getInformation ( context : Context ) : string | undefined {
18+ getInformation ( context : Context , position ?: Position ) : string | undefined {
1119 // Only handle contexts that are inside intrinsic functions
12- if ( ! context . intrinsicContext . inIntrinsic ( ) ) {
20+ if ( ! context . intrinsicContext . inIntrinsic ( ) || context . isIntrinsicFunc ) {
1321 return undefined ;
1422 }
1523
@@ -28,7 +36,7 @@ export class IntrinsicFunctionArgumentHoverProvider implements HoverProvider {
2836 return this . handleRefArgument ( context ) ;
2937 }
3038 case IntrinsicFunction . GetAtt : {
31- return this . handleGetAttArgument ( context ) ;
39+ return this . handleGetAttArgument ( context , position ) ;
3240 }
3341 // Add other intrinsic function types as needed
3442 default : {
@@ -43,9 +51,13 @@ export class IntrinsicFunctionArgumentHoverProvider implements HoverProvider {
4351 return undefined ;
4452 }
4553
54+ // Extract logical ID (handle dot notation like "MyBucket.Arn")
55+ const dotIndex = context . text . indexOf ( '.' ) ;
56+ const logicalId = dotIndex === - 1 ? context . text : context . text . slice ( 0 , dotIndex ) ;
57+
4658 // Look for the referenced entity in related entities
4759 for ( const [ , section ] of context . relatedEntities . entries ( ) ) {
48- const relatedContext = section . get ( context . text ) ;
60+ const relatedContext = section . get ( logicalId ) ;
4961 if ( relatedContext ) {
5062 return this . buildSchemaAndFormat ( relatedContext ) ;
5163 }
@@ -54,11 +66,27 @@ export class IntrinsicFunctionArgumentHoverProvider implements HoverProvider {
5466 return undefined ;
5567 }
5668
57- private handleGetAttArgument ( context : Context ) : string | undefined {
58- // For !GetAtt, we might want to handle resource.attribute references
59- // This could be implemented similarly to handleRefArgument but with
60- // additional logic for attribute-specific information
61- return this . handleRefArgument ( context ) ; // For now, use same logic as Ref
69+ private handleGetAttArgument ( context : Context , position ?: Position ) : string | undefined {
70+ if ( ! ( context instanceof ContextWithRelatedEntities ) ) {
71+ return undefined ;
72+ }
73+
74+ const intrinsicFunction = context . intrinsicContext . intrinsicFunction ( ) ;
75+ if ( ! intrinsicFunction ) {
76+ return undefined ;
77+ }
78+
79+ const getAttPosition = determineGetAttPosition ( intrinsicFunction . args , context , position ) ;
80+
81+ if ( getAttPosition === 1 ) {
82+ // Hovering over resource name
83+ return this . handleRefArgument ( context ) ;
84+ } else if ( getAttPosition === 2 ) {
85+ // Hovering over attribute name
86+ return this . getGetAttAttributeHover ( context , intrinsicFunction . args ) ;
87+ }
88+
89+ return undefined ;
6290 }
6391
6492 private buildSchemaAndFormat ( relatedContext : Context ) : string | undefined {
@@ -80,4 +108,73 @@ export class IntrinsicFunctionArgumentHoverProvider implements HoverProvider {
80108
81109 return undefined ;
82110 }
111+
112+ /**
113+ * Gets hover information for GetAtt attribute names
114+ */
115+ private getGetAttAttributeHover ( context : ContextWithRelatedEntities , args : unknown ) : string | undefined {
116+ const resourceLogicalId = extractGetAttResourceLogicalId ( args ) ;
117+ if ( ! resourceLogicalId ) {
118+ return undefined ;
119+ }
120+
121+ const resourcesSection = context . relatedEntities . get ( TopLevelSection . Resources ) ;
122+ if ( ! resourcesSection ) {
123+ return undefined ;
124+ }
125+
126+ const resourceContext = resourcesSection . get ( resourceLogicalId ) ;
127+ if ( ! resourceContext ?. entity || resourceContext . entity . entityType !== EntityType . Resource ) {
128+ return undefined ;
129+ }
130+
131+ const resource = resourceContext . entity as Resource ;
132+ const resourceType = resource . Type ;
133+ if ( ! resourceType || typeof resourceType !== 'string' ) {
134+ return undefined ;
135+ }
136+
137+ const attributeName = extractAttributeName ( args , context ) ;
138+ if ( ! attributeName ) {
139+ return undefined ;
140+ }
141+
142+ return this . getAttributeDocumentation ( resourceType , attributeName ) ;
143+ }
144+
145+ /**
146+ * Gets documentation for a specific resource attribute from the schema
147+ */
148+ private getAttributeDocumentation ( resourceType : string , attributeName : string ) : string | undefined {
149+ const schema = this . schemaRetriever . getDefault ( ) . schemas . get ( resourceType ) ;
150+
151+ // Provide fallback description even when schema is not available
152+ let description = `**${ attributeName } ** attribute of **${ resourceType } **\n\nReturns the value of this attribute when used with the GetAtt intrinsic function.` ;
153+
154+ if ( schema ) {
155+ const jsonPointerPath = `/properties/${ attributeName . replaceAll ( '.' , '/' ) } ` ;
156+
157+ try {
158+ const resolvedSchemas = schema . resolveJsonPointerPath ( jsonPointerPath ) ;
159+ if ( resolvedSchemas . length === 1 ) {
160+ const firstSchema = resolvedSchemas [ 0 ] ;
161+ if ( firstSchema . description ) {
162+ description = firstSchema . description ;
163+ }
164+ }
165+ } catch ( error ) {
166+ log . debug ( { error, resourceType, attributeName } , 'Error resolving attribute documentation' ) ;
167+ }
168+ }
169+
170+ return this . formatAttributeHover ( resourceType , description ) ;
171+ }
172+
173+ /**
174+ * Formats the hover information for GetAtt attributes
175+ */
176+ private formatAttributeHover ( resourceType : string , description : string ) : string {
177+ const lines = [ `**GetAtt attribute for ${ resourceType } **` , '' , description ] ;
178+ return lines . join ( '\n' ) ;
179+ }
83180}
0 commit comments