@@ -53,16 +53,19 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
5353 getCompletions ( context : Context , params : CompletionParams ) : CompletionItem [ ] | undefined {
5454 const syntaxTree = this . syntaxTreeManager . getSyntaxTree ( params . textDocument . uri ) ;
5555 if ( ! syntaxTree ) {
56+ log . debug ( 'No syntax tree found' ) ;
5657 return ;
5758 }
5859
5960 // Only handle contexts that are inside intrinsic functions
6061 if ( ! context ?. intrinsicContext ?. inIntrinsic ( ) ) {
62+ log . debug ( 'Not in intrinsic context' ) ;
6163 return undefined ;
6264 }
6365
6466 const intrinsicFunction = context . intrinsicContext . intrinsicFunction ( ) ;
6567 if ( ! intrinsicFunction ) {
68+ log . debug ( 'No intrinsic function found' ) ;
6669 return undefined ;
6770 }
6871
@@ -71,6 +74,7 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
7174 provider : 'IntrinsicFunctionArgument' ,
7275 context : context . record ( ) ,
7376 intrinsicFunction : intrinsicFunction . type ,
77+ args : intrinsicFunction . args ,
7478 } ,
7579 'Processing intrinsic function argument completion request' ,
7680 ) ;
@@ -515,38 +519,98 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
515519 context : Context ,
516520 ) : CompletionItem [ ] | undefined {
517521 // Validate arguments structure for second-level keys
518- if ( ! Array . isArray ( args ) || args . length < 2 || typeof args [ 0 ] !== 'string' || typeof args [ 1 ] !== 'string' ) {
522+ if ( ! this . isValidSecondLevelKeyArgs ( args ) ) {
519523 log . debug ( 'Invalid arguments for second-level key completions' ) ;
520524 return undefined ;
521525 }
522526
523527 try {
524528 const mappingName = args [ 0 ] ;
525- const topLevelKey = args [ 1 ] ;
529+ const topLevelKey = args [ 1 ] as string | { Ref : unknown } | { '!Ref' : unknown } ;
526530
527531 const mappingEntity = this . getMappingEntity ( mappingsEntities , mappingName ) ;
528532 if ( ! mappingEntity ) {
529533 log . debug ( `Mapping entity not found: ${ mappingName } ` ) ;
530534 return undefined ;
531535 }
532536
533- const secondLevelKeys = mappingEntity . getSecondLevelKeys ( topLevelKey ) ;
537+ const secondLevelKeys = this . getSecondLevelKeysForTopLevelKey ( mappingEntity , topLevelKey ) ;
534538 if ( secondLevelKeys . length === 0 ) {
535- log . debug ( `No second-level keys found for mapping: ${ mappingName } , top-level key: ${ topLevelKey } ` ) ;
539+ log . debug ( `No second-level keys found for mapping: ${ mappingName } ` ) ;
536540 return undefined ;
537541 }
538542
539543 const items = secondLevelKeys . map ( ( key ) =>
540544 createCompletionItem ( key , CompletionItemKind . EnumMember , { context } ) ,
541545 ) ;
542546
543- return context . text . length > 0 ? this . fuzzySearch ( items , context . text ) : items ;
547+ return this . filterSecondLevelKeyItems ( items , context , topLevelKey ) ;
544548 } catch ( error ) {
545- log . error ( { error } , 'Error creating second-level key completions' ) ;
549+ log . debug ( { error } , 'Error creating second-level key completions' ) ;
546550 return undefined ;
547551 }
548552 }
549553
554+ private isValidSecondLevelKeyArgs ( args : unknown ) : args is [ string , string | object ] {
555+ if ( ! Array . isArray ( args ) || args . length < 2 || typeof args [ 0 ] !== 'string' ) {
556+ return false ;
557+ }
558+
559+ // Second argument valid if it is a string i.e. 'us-east-1' or object '{Ref: AWS::Region}' or '{!Ref: AWS::Region}'
560+ return typeof args [ 1 ] === 'string' || this . isRefObject ( args [ 1 ] ) ;
561+ }
562+
563+ private getSecondLevelKeysForTopLevelKey (
564+ mappingEntity : Mapping ,
565+ topLevelKey : string | { Ref : unknown } | { '!Ref' : unknown } ,
566+ ) : string [ ] {
567+ if ( typeof topLevelKey === 'string' ) {
568+ return mappingEntity . getSecondLevelKeys ( topLevelKey ) ;
569+ } else {
570+ // For dynamic references, get all possible keys
571+ return mappingEntity . getSecondLevelKeysDynamic ( mappingEntity ) ;
572+ }
573+ }
574+
575+ private filterSecondLevelKeyItems (
576+ items : CompletionItem [ ] ,
577+ context : Context ,
578+ topLevelKey : string | { Ref : unknown } | { '!Ref' : unknown } ,
579+ ) : CompletionItem [ ] {
580+ // Check if context.text contains the full FindInMap syntax (empty third argument case)
581+ if ( context . text . startsWith ( '[' ) && context . text . endsWith ( ']' ) ) {
582+ return items ;
583+ }
584+
585+ // If no text typed, return all items
586+ if ( context . text . length === 0 ) {
587+ return items ;
588+ }
589+
590+ return this . applySecondLevelKeyFiltering ( items , context , topLevelKey ) ;
591+ }
592+
593+ private applySecondLevelKeyFiltering (
594+ items : CompletionItem [ ] ,
595+ context : Context ,
596+ topLevelKey : string | { Ref : unknown } | { '!Ref' : unknown } ,
597+ ) : CompletionItem [ ] {
598+ // For dynamic keys, try prefix matching first, then fall back to fuzzy search
599+ if ( typeof topLevelKey === 'object' ) {
600+ const prefixMatches = items . filter ( ( item ) =>
601+ item . label . toLowerCase ( ) . startsWith ( context . text . toLowerCase ( ) ) ,
602+ ) ;
603+
604+ if ( prefixMatches . length === 0 ) {
605+ return this . fuzzySearch ( items , context . text ) ;
606+ }
607+
608+ return prefixMatches ;
609+ }
610+
611+ return this . fuzzySearch ( items , context . text ) ;
612+ }
613+
550614 private getMappingEntity ( mappingsEntities : Map < string , Context > , mappingName : string ) : Mapping | undefined {
551615 try {
552616 const mappingContext = mappingsEntities . get ( mappingName ) ;
@@ -702,4 +766,8 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
702766
703767 return undefined ;
704768 }
769+
770+ private isRefObject ( value : unknown ) : value is { Ref : unknown } | { '!Ref' : unknown } {
771+ return typeof value === 'object' && value !== null && ( 'Ref' in value || '!Ref' in value ) ;
772+ }
705773}
0 commit comments