From 757bdda36a38654e2bc3f4bd7ad9254c36db774b Mon Sep 17 00:00:00 2001 From: gemammercado Date: Fri, 10 Oct 2025 11:54:27 -0400 Subject: [PATCH 1/9] Autocomplete in nested object bug fix --- src/context/Context.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/context/Context.ts b/src/context/Context.ts index a0504633..3ddea85f 100644 --- a/src/context/Context.ts +++ b/src/context/Context.ts @@ -311,6 +311,19 @@ export class Context { isValue: this.isValue(), }; } + + public isInSchemaDefinedObject(): boolean { + // Nested inside Properties check + if ( + this.section === TopLevelSection.Resources && + this.propertyPath.length > 3 && + this.propertyPath[2] === 'Properties' + ) { + return true; + } + + return false; + } } export function logicalIdAndSection(propertyPath: PropertyPath) { From 8a8c9bc8fb24399c44bcb7901196e45bc8bc6bff Mon Sep 17 00:00:00 2001 From: gemammercado Date: Sun, 12 Oct 2025 19:55:14 -0400 Subject: [PATCH 2/9] Added testing for the bug fix --- tst/e2e/autocomplete/Autocomplete.test.ts | 69 +++++++++++++++++++ .../ResourceSectionCompletionProvider.test.ts | 20 +++--- 2 files changed, 81 insertions(+), 8 deletions(-) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index 7c7a8a03..9f48d0a5 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1451,6 +1451,42 @@ 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']) + .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 +1515,38 @@ 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']) + .build(), + }, + }, + ], + }; + template.executeScenario(scenario); + }); }); }); diff --git a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts index d839b9d0..7e799889 100644 --- a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts +++ b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts @@ -194,13 +194,15 @@ describe('ResourceSectionCompletionProvider', () => { spy.mockRestore(); }); - test('should delegate to property provider when entitySection is a resource attribute (CreationPolicy)', async () => { - const mockContext = createResourceContext('MyInstance', { - text: '', - propertyPath: ['Resources', 'MyInstance', 'CreationPolicy', ''], + 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::EC2::Instance', - CreationPolicy: {}, + Type: 'AWS::S3::Bucket', + Properties: { + NotificationConfiguration: {} + }, }, }); @@ -209,8 +211,10 @@ describe('ResourceSectionCompletionProvider', () => { const propertyProvider = resourceProviders.get('Property' as any)!; const mockCompletions = [ - { label: 'ResourceSignal', kind: CompletionItemKind.Property }, - { label: 'AutoScalingCreationPolicy', kind: CompletionItemKind.Property }, + { 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); From 3fc0290f519bef59363f3b4a043557eecc79d287 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Sun, 12 Oct 2025 20:00:02 -0400 Subject: [PATCH 3/9] lint fix --- tst/e2e/autocomplete/Autocomplete.test.ts | 12 ++++++++++-- .../ResourceSectionCompletionProvider.test.ts | 2 +- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index 9f48d0a5..43df5b20 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1471,7 +1471,11 @@ Resources: verification: { position: { line: 6, character: 8 }, expectation: CompletionExpectationBuilder.create() - .expectContainsItems(['TopicConfigurations', 'QueueConfigurations', 'LambdaConfigurations']) + .expectContainsItems([ + 'TopicConfigurations', + 'QueueConfigurations', + 'LambdaConfigurations', + ]) .build(), }, }, @@ -1540,7 +1544,11 @@ Resources: verification: { position: { line: 7, character: 11 }, expectation: CompletionExpectationBuilder.create() - .expectContainsItems(['TopicConfigurations', 'QueueConfigurations', 'LambdaConfigurations']) + .expectContainsItems([ + 'TopicConfigurations', + 'QueueConfigurations', + 'LambdaConfigurations', + ]) .build(), }, }, diff --git a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts index 7e799889..9ec25e6b 100644 --- a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts +++ b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts @@ -201,7 +201,7 @@ describe('ResourceSectionCompletionProvider', () => { data: { Type: 'AWS::S3::Bucket', Properties: { - NotificationConfiguration: {} + NotificationConfiguration: {}, }, }, }); From 2908df21d769339a902aa55b81635fa422ca7209 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Mon, 13 Oct 2025 10:35:14 -0400 Subject: [PATCH 4/9] Use matchPathWithLogicalId --- .../ResourceSectionCompletionProvider.ts | 3 ++- src/context/Context.ts | 13 ------------- 2 files changed, 2 insertions(+), 14 deletions(-) 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/src/context/Context.ts b/src/context/Context.ts index 3ddea85f..a0504633 100644 --- a/src/context/Context.ts +++ b/src/context/Context.ts @@ -311,19 +311,6 @@ export class Context { isValue: this.isValue(), }; } - - public isInSchemaDefinedObject(): boolean { - // Nested inside Properties check - if ( - this.section === TopLevelSection.Resources && - this.propertyPath.length > 3 && - this.propertyPath[2] === 'Properties' - ) { - return true; - } - - return false; - } } export function logicalIdAndSection(propertyPath: PropertyPath) { From 149b6ac2813393d5a536c5a55efa7aac7096b7f6 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Mon, 13 Oct 2025 15:46:36 -0400 Subject: [PATCH 5/9] Added EventBridgeConfiguration to nested objects e2e test expectation --- tst/e2e/autocomplete/Autocomplete.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index 43df5b20..bf9d7c18 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1475,6 +1475,7 @@ Resources: 'TopicConfigurations', 'QueueConfigurations', 'LambdaConfigurations', + 'EventBridgeConfiguration', ]) .build(), }, From 25406258b7bcc8f280126326d51f92b1658726b4 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Tue, 14 Oct 2025 18:14:28 -0400 Subject: [PATCH 6/9] Added EventBridgeConfig to expectation of json test --- tst/e2e/autocomplete/Autocomplete.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index bf9d7c18..41ee7c26 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1549,6 +1549,7 @@ Resources: 'TopicConfigurations', 'QueueConfigurations', 'LambdaConfigurations', + 'EventBridgeConfiguration', ]) .build(), }, From 8b1f8519bdf12506f03a0743c76f8bf67acf839f Mon Sep 17 00:00:00 2001 From: gemammercado Date: Thu, 16 Oct 2025 11:54:26 -0400 Subject: [PATCH 7/9] Resolve errors after merging --- tst/e2e/autocomplete/Autocomplete.test.ts | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index 41ee7c26..d1bb3d93 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1520,16 +1520,17 @@ Resources: }; 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: `{ + 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": { @@ -1542,21 +1543,20 @@ Resources: } } }`, - verification: { - position: { line: 7, character: 11 }, - expectation: CompletionExpectationBuilder.create() - .expectContainsItems([ - 'TopicConfigurations', - 'QueueConfigurations', - 'LambdaConfigurations', - 'EventBridgeConfiguration', - ]) - .build(), - }, - }, - ], - }; - template.executeScenario(scenario); - }); + verification: { + position: { line: 7, character: 11 }, + expectation: CompletionExpectationBuilder.create() + .expectContainsItems([ + 'TopicConfigurations', + 'QueueConfigurations', + 'LambdaConfigurations', + 'EventBridgeConfiguration', + ]) + .build(), + }, + }, + ], + }; + template.executeScenario(scenario); }); }); From 59cde25a1b6337c4c1cad10562d733853b9557cd Mon Sep 17 00:00:00 2001 From: gemammercado Date: Thu, 16 Oct 2025 12:13:38 -0400 Subject: [PATCH 8/9] Adding test from before merge --- .../ResourceSectionCompletionProvider.test.ts | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts index 9ec25e6b..ae581b95 100644 --- a/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts +++ b/tst/unit/autocomplete/ResourceSectionCompletionProvider.test.ts @@ -194,6 +194,33 @@ describe('ResourceSectionCompletionProvider', () => { spy.mockRestore(); }); + test('should delegate to property provider when entitySection is a resource attribute (CreationPolicy)', async () => { + const mockContext = createResourceContext('MyInstance', { + text: '', + propertyPath: ['Resources', 'MyInstance', 'CreationPolicy', ''], + data: { + Type: 'AWS::EC2::Instance', + CreationPolicy: {}, + }, + }); + + const mockSchemas = createMockResourceSchemas(); + setupMockSchemas(mockSchemas); + + const propertyProvider = resourceProviders.get('Property' as any)!; + const mockCompletions = [ + { label: 'ResourceSignal', kind: CompletionItemKind.Property }, + { label: 'AutoScalingCreationPolicy', 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 delegate to property provider when in nested object within Properties (matchPathWithLogicalIds)', async () => { const mockContext = createResourceContext('MyBucket', { text: 'Topic', From e6a399ea76072c1975f3b51bf61b20058b2c15b2 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Thu, 16 Oct 2025 12:30:19 -0400 Subject: [PATCH 9/9] Move new json test into describe block --- tst/e2e/autocomplete/Autocomplete.test.ts | 50 +++++++++++------------ 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index d1bb3d93..41ee7c26 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1520,17 +1520,16 @@ Resources: }; 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: `{ + 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": { @@ -1543,20 +1542,21 @@ Resources: } } }`, - verification: { - position: { line: 7, character: 11 }, - expectation: CompletionExpectationBuilder.create() - .expectContainsItems([ - 'TopicConfigurations', - 'QueueConfigurations', - 'LambdaConfigurations', - 'EventBridgeConfiguration', - ]) - .build(), - }, - }, - ], - }; - template.executeScenario(scenario); + verification: { + position: { line: 7, character: 11 }, + expectation: CompletionExpectationBuilder.create() + .expectContainsItems([ + 'TopicConfigurations', + 'QueueConfigurations', + 'LambdaConfigurations', + 'EventBridgeConfiguration', + ]) + .build(), + }, + }, + ], + }; + template.executeScenario(scenario); + }); }); });