diff --git a/src/autocomplete/ResourceSectionCompletionProvider.ts b/src/autocomplete/ResourceSectionCompletionProvider.ts index 7aec1217..23365c1c 100644 --- a/src/autocomplete/ResourceSectionCompletionProvider.ts +++ b/src/autocomplete/ResourceSectionCompletionProvider.ts @@ -55,7 +55,8 @@ export class ResourceSectionCompletionProvider implements CompletionProvider { ?.getCompletions(context, params) as CompletionItem[]; } else if ( context.entitySection === 'Properties' || - ResourceAttributesSet.has(context.entitySection as string) + ResourceAttributesSet.has(context.entitySection as string) || + (context.matchPathWithLogicalId(TopLevelSection.Resources, 'Properties') && context.propertyPath.length > 3) ) { const schemaPropertyCompletions = this.resourceProviders .get(ResourceCompletionType.Property) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index 7c7a8a03..41ee7c26 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1451,6 +1451,47 @@ O`, }; template.executeScenario(scenario); }); + + it('test nested object property completion', () => { + const template = new TemplateBuilder(DocumentType.YAML); + const scenario: TemplateScenario = { + name: 'Nested object property completion', + steps: [ + { + action: 'type', + content: `AWSTemplateFormatVersion: '2010-09-09' +Resources: + MyBucket: + Type: AWS::S3::Bucket + Properties: + NotificationConfiguration: + `, + position: { line: 0, character: 0 }, + description: 'Test autocomplete in nested object within Properties', + verification: { + position: { line: 6, character: 8 }, + expectation: CompletionExpectationBuilder.create() + .expectContainsItems([ + 'TopicConfigurations', + 'QueueConfigurations', + 'LambdaConfigurations', + 'EventBridgeConfiguration', + ]) + .build(), + }, + }, + { + action: 'type', + content: `TopicConfigurations: + - Topic: arn:aws:sns:us-east-1:123456789012:my-topic + Event: s3:ObjectCreated:*`, + position: { line: 6, character: 8 }, + description: 'Complete the nested object structure', + }, + ], + }; + template.executeScenario(scenario); + }); }); describe('JSON', () => { @@ -1479,5 +1520,43 @@ O`, }; template.executeScenario(scenario); }); + + it('test nested object property completion', () => { + const template = new TemplateBuilder(DocumentType.JSON, ''); + const scenario: TemplateScenario = { + name: 'Nested object property completion', + steps: [ + { + action: 'type', + position: { line: 0, character: 0 }, + content: `{ + "AWSTemplateFormatVersion": "2010-09-09", + "Resources": { + "MyBucket": { + "Type": "AWS::S3::Bucket", + "Properties": { + "NotificationConfiguration": { + "" + } + } + } + } +}`, + verification: { + position: { line: 7, character: 11 }, + expectation: CompletionExpectationBuilder.create() + .expectContainsItems([ + 'TopicConfigurations', + 'QueueConfigurations', + 'LambdaConfigurations', + 'EventBridgeConfiguration', + ]) + .build(), + }, + }, + ], + }; + template.executeScenario(scenario); + }); }); }); diff --git a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts index d839b9d0..ae581b95 100644 --- a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts +++ b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts @@ -221,6 +221,37 @@ describe('ResourceSectionCompletionProvider', () => { spy.mockRestore(); }); + test('should delegate to property provider when in nested object within Properties (matchPathWithLogicalIds)', async () => { + const mockContext = createResourceContext('MyBucket', { + text: 'Topic', + propertyPath: ['Resources', 'MyBucket', 'Properties', 'NotificationConfiguration', 'Topic'], + data: { + Type: 'AWS::S3::Bucket', + Properties: { + NotificationConfiguration: {}, + }, + }, + }); + + const mockSchemas = createMockResourceSchemas(); + setupMockSchemas(mockSchemas); + + const propertyProvider = resourceProviders.get('Property' as any)!; + const mockCompletions = [ + { label: 'TopicConfigurations', kind: CompletionItemKind.Property }, + { label: 'QueueConfigurations', kind: CompletionItemKind.Property }, + { label: 'LambdaConfigurations', kind: CompletionItemKind.Property }, + { label: 'EventBridgeConfiguration', kind: CompletionItemKind.Property }, + ]; + const spy = vi.spyOn(propertyProvider, 'getCompletions').mockReturnValue(mockCompletions); + + const result = await provider.getCompletions(mockContext, mockParams); + + expect(spy).toHaveBeenCalledWith(mockContext, mockParams); + expect(result).toEqual(mockCompletions); + spy.mockRestore(); + }); + test('should return empty array when no provider matches', async () => { const mockContext = createResourceContext('MyResource', { text: '',