From b9152d87d0cc871dd475b5edec5b4aa2e6097ed0 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Fri, 10 Oct 2025 17:30:56 -0400 Subject: [PATCH 1/7] Adding description to autocomplete suggestion --- src/autocomplete/CompletionUtils.ts | 37 ++++++- ...insicFunctionArgumentCompletionProvider.ts | 35 +++++- .../ResourcePropertyCompletionProvider.ts | 16 ++- tst/e2e/autocomplete/Autocomplete.test.ts | 101 ++++++++++++++++++ 4 files changed, 181 insertions(+), 8 deletions(-) diff --git a/src/autocomplete/CompletionUtils.ts b/src/autocomplete/CompletionUtils.ts index 8a0f4356..effaaf78 100644 --- a/src/autocomplete/CompletionUtils.ts +++ b/src/autocomplete/CompletionUtils.ts @@ -4,6 +4,8 @@ import { CompletionParams, InsertTextFormat, InsertTextMode, + MarkupContent, + MarkupKind, Position, Range, TextEdit, @@ -33,6 +35,20 @@ export function createReplacementRange(context: Context, includeQuotes?: boolean } } +/** + * Creates a MarkupContent object from a markdown string. + * This ensures consistent formatting for completion documentation. + * + * @param markdown The markdown content string + * @returns A MarkupContent object with markdown formatting + */ +export function createMarkupContent(markdown: string): MarkupContent { + return { + kind: MarkupKind.Markdown, + value: markdown, + }; +} + /** * Creates a base completion item with common properties. * This reduces duplication across different completion providers. @@ -51,7 +67,7 @@ export function createCompletionItem( insertTextFormat?: InsertTextFormat; insertTextMode?: InsertTextMode; sortText?: string; - documentation?: string; + documentation?: string | MarkupContent; data?: Record; context?: Context; }, @@ -70,6 +86,23 @@ export function createCompletionItem( } } + // Handle documentation - support both string and MarkupContent + let documentation: string | MarkupContent | undefined; + if (options?.documentation) { + if (typeof options.documentation === 'string') { + // For string documentation, add the source attribution + documentation = `${options.documentation}\n\nSource: ${ExtensionName}`; + } else { + // For MarkupContent, add source attribution to the markdown value + documentation = { + kind: options.documentation.kind, + value: `${options.documentation.value}\n\n**Source:** ${ExtensionName}`, + }; + } + } else { + documentation = `Source: ${ExtensionName}`; + } + return { label, kind, @@ -80,7 +113,7 @@ export function createCompletionItem( textEdit: textEdit, filterText: filterText, sortText: options?.sortText, - documentation: `${options?.documentation ? `${options?.documentation}\n` : ''}Source: ${ExtensionName}`, + documentation: documentation, data: options?.data, }; } diff --git a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts index b74001c6..45caf07e 100644 --- a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts +++ b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts @@ -3,6 +3,7 @@ import { pseudoParameterDocsMap } from '../artifacts/PseudoParameterDocs'; import { Context } from '../context/Context'; import { IntrinsicFunction, PseudoParameter, TopLevelSection } from '../context/ContextType'; import { getEntityMap } from '../context/SectionContextBuilder'; +import { propertyTypesToMarkdown } from '../hover/HoverFormatter'; import { Mapping, Parameter, Resource } from '../context/semantic/Entity'; import { EntityType } from '../context/semantic/SemanticTypes'; import { SyntaxTree } from '../context/syntaxtree/SyntaxTree'; @@ -12,7 +13,7 @@ import { SchemaRetriever } from '../schema/SchemaRetriever'; import { LoggerFactory } from '../telemetry/LoggerFactory'; import { getFuzzySearchFunction } from '../utils/FuzzySearchUtil'; import { CompletionProvider } from './CompletionProvider'; -import { createCompletionItem, createReplacementRange } from './CompletionUtils'; +import { createCompletionItem, createMarkupContent, createReplacementRange } from './CompletionUtils'; interface IntrinsicFunctionInfo { type: IntrinsicFunction; @@ -344,10 +345,25 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr const attributes = this.getResourceAttributes(resource.Type); for (const attributeName of attributes) { + const schema = this.schemaRetriever.getDefault().schemas.get(resource.Type); + let attributeDescription = `GetAtt attribute for ${resource.Type}`; + + if (schema) { + const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/properties/')}`; + + try { + const resolvedSchemas = schema.resolveJsonPointerPath(jsonPointerPath); + if (resolvedSchemas.length > 0 && resolvedSchemas[0].description) { + attributeDescription = resolvedSchemas[0].description; + } + } catch { + attributeDescription = `${attributeName} attribute of ${resource.Type}`; + } + } completionItems.push( createCompletionItem(`${resourceName}.${attributeName}`, CompletionItemKind.Property, { detail: `GetAtt (${resource.Type})`, - documentation: `Get attribute ${attributeName} from resource ${resourceName}`, + documentation: attributeDescription, data: { isIntrinsicFunction: true, }, @@ -648,7 +664,8 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr } const resource = resourceContext.entity as Resource; - if (!resource.Type || typeof resource.Type !== 'string') { + const resourceType = resource.Type; + if (!resourceType || typeof resourceType !== 'string') { return undefined; } @@ -658,8 +675,18 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr } const completionItems = attributes.map((attributeName) => { + const schema = this.schemaRetriever.getDefault().schemas.get(resourceType); + let documentation; + + if (schema) { + documentation = createMarkupContent(schema.description); + } else { + documentation = `GettAtt for resource ${resource.Type}`; + } + const item = createCompletionItem(attributeName, CompletionItemKind.Property, { - data: { isIntrinsicFunction: true }, + documentation: documentation, + detail: `GetAtt attribute for ${resource.Type}`, }); if (context.text.length > 0) { diff --git a/src/autocomplete/ResourcePropertyCompletionProvider.ts b/src/autocomplete/ResourcePropertyCompletionProvider.ts index 93004c15..d7049558 100644 --- a/src/autocomplete/ResourcePropertyCompletionProvider.ts +++ b/src/autocomplete/ResourcePropertyCompletionProvider.ts @@ -4,13 +4,14 @@ import { Resource } from '../context/semantic/Entity'; import { CfnValue } from '../context/semantic/SemanticTypes'; import { NodeType } from '../context/syntaxtree/utils/NodeType'; import { CommonNodeTypes } from '../context/syntaxtree/utils/TreeSitterTypes'; +import { propertyTypesToMarkdown } from '../hover/HoverFormatter'; import { PropertyType, ResourceSchema } from '../schema/ResourceSchema'; import { SchemaRetriever } from '../schema/SchemaRetriever'; import { getFuzzySearchFunction } from '../utils/FuzzySearchUtil'; import { templatePathToJsonPointerPath } from '../utils/PathUtils'; import { CompletionItemData, ExtendedCompletionItem } from './CompletionFormatter'; import { CompletionProvider } from './CompletionProvider'; -import { createCompletionItem } from './CompletionUtils'; +import { createCompletionItem, createMarkupContent } from './CompletionUtils'; export class ResourcePropertyCompletionProvider implements CompletionProvider { private readonly fuzzySearch = getFuzzySearchFunction(); @@ -237,7 +238,6 @@ export class ResourcePropertyCompletionProvider implements CompletionProvider { context: Context, ): CompletionItem[] { const result: CompletionItem[] = []; - const availableRequiredProperties = [...requiredProperties].filter( (propName) => allProperties.has(propName) && !existingProperties.has(propName), ); @@ -255,10 +255,22 @@ export class ResourcePropertyCompletionProvider implements CompletionProvider { const itemData = this.getPropertyType(schema, propertyDef); + // Generate rich markdown documentation for the property + let documentation; + if (propertyDef.description || propertyDef.properties || propertyDef.type) { + // Use the rich markdown formatter from hover system + const markdownDoc = propertyTypesToMarkdown(propertyName, [propertyDef]); + documentation = createMarkupContent(markdownDoc); + } else { + // Fallback to simple description for properties without schema details + documentation = `${propertyName} property of ${schema.typeName}`; + } + const completionItem: ExtendedCompletionItem = createCompletionItem( propertyName, CompletionItemKind.Property, { + documentation: documentation, data: itemData, context: context, }, diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index 672d02f8..3ee8d7b7 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1419,6 +1419,107 @@ O`, }; template.executeScenario(scenario); }); + + it('YAML GetAtt autocomplete should include documentation', () => { + const template = new TemplateBuilder(DocumentType.YAML); + const scenario: TemplateScenario = { + name: 'GetAtt documentation test', + steps: [ + { + action: 'type', + content: `AWSTemplateFormatVersion: '2010-09-09' +Description: 'Test template for GetAtt autocomplete bug in YAML' + +Parameters: + BucketPrefix: + Type: String + Default: 'test-bucket' + Description: 'Prefix for the S3 bucket name' + +Resources: + MyS3Bucket: + Type: AWS::S3::Bucket + Properties: + BucketName: !Sub '\${BucketPrefix}-\${AWS::StackName}' + VersioningConfiguration: + Status: Enabled + PublicAccessBlockConfiguration: + BlockPublicAcls: true + BlockPublicPolicy: true + IgnorePublicAcls: true + RestrictPublicBuckets: true + + MyLambdaFunction: + Type: AWS::Lambda::Function + Properties: + FunctionName: !Sub '\${AWS::StackName}-TestFunction' + Runtime: python3.9 + Handler: index.handler + Role: !GetAtt LambdaExecutionRole.Arn + Code: + ZipFile: | + def handler(event, context): + return {'statusCode': 200, 'body': 'Hello World'} + Environment: + Variables: + BUCKET_NAME: !Ref MyS3Bucket + + LambdaExecutionRole: + Type: AWS::IAM::Role + Properties: + AssumeRolePolicyDocument: + Version: '2012-10-17' + Statement: + - Effect: Allow + Principal: + Service: lambda.amazonaws.com + Action: sts:AssumeRole + ManagedPolicyArns: + - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole + + MyDynamoDBTable: + Type: AWS::DynamoDB::Table + Properties: + TableName: !Sub '\${AWS::StackName}-TestTable' + BillingMode: PAY_PER_REQUEST + AttributeDefinitions: + - AttributeName: id + AttributeType: S + KeySchema: + - AttributeName: id + KeyType: HASH + + MyEC2Instance: + Type: AWS::EC2::Instance + Properties: + ImageId: ami-0abcdef1234567890 + InstanceType: t3.micro + Tags: + - Key: Name + Value: !Sub '\${AWS::StackName}-TestInstance' + +Outputs: + S3BucketArn: + Description: 'S3 Bucket ARN - should show: Arn, DomainName, RegionalDomainName, WebsiteURL'`, + position: { line: 0, character: 0 }, + }, + { + action: 'type', + content: ` + Value: !GetAtt MyS3Bucket.`, + position: { line: 73, character: 95 }, + description: 'GetAtt autocomplete should include documentation for S3 attributes', + verification: { + position: { line: 74, character: 30 }, + expectation: CompletionExpectationBuilder.create() + .expectContainsItems(['Arn', 'DomainName', 'RegionalDomainName', 'WebsiteURL']) + .build(), + }, + }, + ], + }; + template.executeScenario(scenario); + }); }); describe('JSON', () => { From b75a1c9f8870403f00049e700211fdb9c8c5772c Mon Sep 17 00:00:00 2001 From: gemammercado Date: Sun, 12 Oct 2025 18:22:16 -0400 Subject: [PATCH 2/7] Add description markdowns on autocomplete for property types and getAtt attributes --- ...insicFunctionArgumentCompletionProvider.ts | 26 ++++++++++++++----- 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts index 45caf07e..44b73461 100644 --- a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts +++ b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts @@ -3,7 +3,6 @@ import { pseudoParameterDocsMap } from '../artifacts/PseudoParameterDocs'; import { Context } from '../context/Context'; import { IntrinsicFunction, PseudoParameter, TopLevelSection } from '../context/ContextType'; import { getEntityMap } from '../context/SectionContextBuilder'; -import { propertyTypesToMarkdown } from '../hover/HoverFormatter'; import { Mapping, Parameter, Resource } from '../context/semantic/Entity'; import { EntityType } from '../context/semantic/SemanticTypes'; import { SyntaxTree } from '../context/syntaxtree/SyntaxTree'; @@ -363,7 +362,7 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr completionItems.push( createCompletionItem(`${resourceName}.${attributeName}`, CompletionItemKind.Property, { detail: `GetAtt (${resource.Type})`, - documentation: attributeDescription, + documentation: createMarkupContent(attributeDescription), data: { isIntrinsicFunction: true, }, @@ -678,10 +677,25 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr const schema = this.schemaRetriever.getDefault().schemas.get(resourceType); let documentation; - if (schema) { - documentation = createMarkupContent(schema.description); - } else { - documentation = `GettAtt for resource ${resource.Type}`; + if (schema) { + const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/properties/')}`; + documentation = createMarkupContent( + `**${attributeName}** attribute of **${resource.Type}**\n\nReturns the value of this attribute when used with the GetAtt intrinsic function.`, + ); + + try { + const resolvedSchemas = schema.resolveJsonPointerPath(jsonPointerPath); + + if (resolvedSchemas.length > 0) { + const firstSchema = resolvedSchemas[0]; + + if (firstSchema.description) { + documentation = createMarkupContent(firstSchema.description); + } + } + } catch (error) { + log.debug(error); + } } const item = createCompletionItem(attributeName, CompletionItemKind.Property, { From d094e1364dda115c1f9c3c183302a123a3ece8f0 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Sun, 12 Oct 2025 18:31:28 -0400 Subject: [PATCH 3/7] Removing extra test in Autocomplete.test.ts --- tst/e2e/autocomplete/Autocomplete.test.ts | 101 ---------------------- 1 file changed, 101 deletions(-) diff --git a/tst/e2e/autocomplete/Autocomplete.test.ts b/tst/e2e/autocomplete/Autocomplete.test.ts index 3ee8d7b7..672d02f8 100644 --- a/tst/e2e/autocomplete/Autocomplete.test.ts +++ b/tst/e2e/autocomplete/Autocomplete.test.ts @@ -1419,107 +1419,6 @@ O`, }; template.executeScenario(scenario); }); - - it('YAML GetAtt autocomplete should include documentation', () => { - const template = new TemplateBuilder(DocumentType.YAML); - const scenario: TemplateScenario = { - name: 'GetAtt documentation test', - steps: [ - { - action: 'type', - content: `AWSTemplateFormatVersion: '2010-09-09' -Description: 'Test template for GetAtt autocomplete bug in YAML' - -Parameters: - BucketPrefix: - Type: String - Default: 'test-bucket' - Description: 'Prefix for the S3 bucket name' - -Resources: - MyS3Bucket: - Type: AWS::S3::Bucket - Properties: - BucketName: !Sub '\${BucketPrefix}-\${AWS::StackName}' - VersioningConfiguration: - Status: Enabled - PublicAccessBlockConfiguration: - BlockPublicAcls: true - BlockPublicPolicy: true - IgnorePublicAcls: true - RestrictPublicBuckets: true - - MyLambdaFunction: - Type: AWS::Lambda::Function - Properties: - FunctionName: !Sub '\${AWS::StackName}-TestFunction' - Runtime: python3.9 - Handler: index.handler - Role: !GetAtt LambdaExecutionRole.Arn - Code: - ZipFile: | - def handler(event, context): - return {'statusCode': 200, 'body': 'Hello World'} - Environment: - Variables: - BUCKET_NAME: !Ref MyS3Bucket - - LambdaExecutionRole: - Type: AWS::IAM::Role - Properties: - AssumeRolePolicyDocument: - Version: '2012-10-17' - Statement: - - Effect: Allow - Principal: - Service: lambda.amazonaws.com - Action: sts:AssumeRole - ManagedPolicyArns: - - arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole - - MyDynamoDBTable: - Type: AWS::DynamoDB::Table - Properties: - TableName: !Sub '\${AWS::StackName}-TestTable' - BillingMode: PAY_PER_REQUEST - AttributeDefinitions: - - AttributeName: id - AttributeType: S - KeySchema: - - AttributeName: id - KeyType: HASH - - MyEC2Instance: - Type: AWS::EC2::Instance - Properties: - ImageId: ami-0abcdef1234567890 - InstanceType: t3.micro - Tags: - - Key: Name - Value: !Sub '\${AWS::StackName}-TestInstance' - -Outputs: - S3BucketArn: - Description: 'S3 Bucket ARN - should show: Arn, DomainName, RegionalDomainName, WebsiteURL'`, - position: { line: 0, character: 0 }, - }, - { - action: 'type', - content: ` - Value: !GetAtt MyS3Bucket.`, - position: { line: 73, character: 95 }, - description: 'GetAtt autocomplete should include documentation for S3 attributes', - verification: { - position: { line: 74, character: 30 }, - expectation: CompletionExpectationBuilder.create() - .expectContainsItems(['Arn', 'DomainName', 'RegionalDomainName', 'WebsiteURL']) - .build(), - }, - }, - ], - }; - template.executeScenario(scenario); - }); }); describe('JSON', () => { From 32c684f0b43c4ba634ec8e09b73041a0080c2bc3 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Mon, 13 Oct 2025 15:28:43 -0400 Subject: [PATCH 4/7] Fixed return value for getGetAttCompletions, added unit test for CompletionUtils.ts --- ...insicFunctionArgumentCompletionProvider.ts | 3 +- tst/unit/autocomplete/CompletionUtils.test.ts | 97 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 tst/unit/autocomplete/CompletionUtils.test.ts diff --git a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts index 44b73461..d2b91443 100644 --- a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts +++ b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts @@ -345,7 +345,7 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr const attributes = this.getResourceAttributes(resource.Type); for (const attributeName of attributes) { const schema = this.schemaRetriever.getDefault().schemas.get(resource.Type); - let attributeDescription = `GetAtt attribute for ${resource.Type}`; + let attributeDescription = `${attributeName} attribute of ${resource.Type}`; if (schema) { const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/properties/')}`; @@ -356,7 +356,6 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr attributeDescription = resolvedSchemas[0].description; } } catch { - attributeDescription = `${attributeName} attribute of ${resource.Type}`; } } completionItems.push( diff --git a/tst/unit/autocomplete/CompletionUtils.test.ts b/tst/unit/autocomplete/CompletionUtils.test.ts new file mode 100644 index 00000000..a90385f5 --- /dev/null +++ b/tst/unit/autocomplete/CompletionUtils.test.ts @@ -0,0 +1,97 @@ +import { describe, expect, test } from 'vitest'; +import { CompletionItemKind, MarkupContent, MarkupKind } from 'vscode-languageserver'; +import { + createCompletionItem, +} from '../../../src/autocomplete/CompletionUtils'; + +describe('CompletionUtils', () => { + describe('createCompletionItem', () => { + describe('documentation handling', () => { + const stringDoc = "This is a test documentation"; + const expectedStringResult = "This is a test documentation\n\nSource: AWS CloudFormation"; + + const markupDoc = { kind: MarkupKind.Markdown, value: "**Bold** documentation" }; + const expectedMarkupResult = { + kind: MarkupKind.Markdown, + value: "**Bold** documentation\n\n**Source:** AWS CloudFormation" + }; + + const emptyDoc = ""; + const defaultSource = "Source: AWS CloudFormation"; + + const markdownDoc = { kind: MarkupKind.Markdown, value: "**Bold** text" }; + const plainTextDoc = { kind: MarkupKind.PlainText, value: "Plain text" }; + + const complexMarkdown = { + kind: MarkupKind.Markdown, + value: `# S3 Bucket + + Creates an **Amazon S3** bucket with: + - Versioning enabled + - Public access *blocked* + - [Documentation](https://docs.aws.amazon.com)` + }; + + const expectedComplexMarkdown = { + kind: MarkupKind.Markdown, + value: `# S3 Bucket + + Creates an **Amazon S3** bucket with: + - Versioning enabled + - Public access *blocked* + - [Documentation](https://docs.aws.amazon.com) + +**Source:** AWS CloudFormation`, + }; + + test('should handle string documentation with source attribution', () => { + const result = createCompletionItem("Test", CompletionItemKind.Keyword, { + documentation: stringDoc, + }); + expect(result.documentation).toEqual(expectedStringResult); + }); + + test('should handle MarkupContent documentation with source attribution', () => { + const result = createCompletionItem("Test", CompletionItemKind.Keyword, { + documentation: markupDoc, + }); + expect(result.documentation).toEqual(expectedMarkupResult); + }); + + test('should handle undefined documentation with default source', () => { + const result = createCompletionItem("Test", CompletionItemKind.Keyword); + expect(result.documentation).toEqual(defaultSource); + }); + + test('should handle empty string documentation', () => { + const result = createCompletionItem("Test", CompletionItemKind.Keyword, { + documentation: emptyDoc, + }); + expect(result.documentation).toEqual(defaultSource); + }); + + test('should preserve MarkupContent kind when adding source attribution', () => { + const markdownResult = createCompletionItem("Test", CompletionItemKind.Keyword, { + documentation: markdownDoc, + }); + const markdownAsMarkup = markdownResult.documentation as MarkupContent; + expect(markdownAsMarkup.kind).toEqual(MarkupKind.Markdown); + + const plainTextResult = createCompletionItem("Test", CompletionItemKind.Keyword, { + documentation: plainTextDoc, + }); + const plainTextAsMarkup = plainTextResult.documentation as MarkupContent; + expect(plainTextAsMarkup.kind).toEqual(MarkupKind.PlainText); + }); + + + test('should handle MarkupContent with existing markdown formatting', () => { + const result = createCompletionItem("Test", CompletionItemKind.Keyword, { + documentation: complexMarkdown, + }); + + expect(result.documentation).toEqual(expectedComplexMarkdown); + }); + }); + }); +}); From ed9ca86e89e443b94ff94223c9ddc3b98e2f8ccf Mon Sep 17 00:00:00 2001 From: gemammercado Date: Mon, 13 Oct 2025 15:37:55 -0400 Subject: [PATCH 5/7] fix lint errors, add error logging to empty catch block --- ...insicFunctionArgumentCompletionProvider.ts | 3 +- tst/unit/autocomplete/CompletionUtils.test.ts | 49 +++++++++---------- 2 files changed, 25 insertions(+), 27 deletions(-) diff --git a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts index d2b91443..d14b473c 100644 --- a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts +++ b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts @@ -355,7 +355,8 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr if (resolvedSchemas.length > 0 && resolvedSchemas[0].description) { attributeDescription = resolvedSchemas[0].description; } - } catch { + } catch (error) { + log.error({ error }, 'Error resolving JSON Pointer path'); } } completionItems.push( diff --git a/tst/unit/autocomplete/CompletionUtils.test.ts b/tst/unit/autocomplete/CompletionUtils.test.ts index a90385f5..1599149a 100644 --- a/tst/unit/autocomplete/CompletionUtils.test.ts +++ b/tst/unit/autocomplete/CompletionUtils.test.ts @@ -1,26 +1,24 @@ import { describe, expect, test } from 'vitest'; import { CompletionItemKind, MarkupContent, MarkupKind } from 'vscode-languageserver'; -import { - createCompletionItem, -} from '../../../src/autocomplete/CompletionUtils'; +import { createCompletionItem } from '../../../src/autocomplete/CompletionUtils'; describe('CompletionUtils', () => { describe('createCompletionItem', () => { describe('documentation handling', () => { - const stringDoc = "This is a test documentation"; - const expectedStringResult = "This is a test documentation\n\nSource: AWS CloudFormation"; + const stringDoc = 'This is a test documentation'; + const expectedStringResult = 'This is a test documentation\n\nSource: AWS CloudFormation'; - const markupDoc = { kind: MarkupKind.Markdown, value: "**Bold** documentation" }; - const expectedMarkupResult = { - kind: MarkupKind.Markdown, - value: "**Bold** documentation\n\n**Source:** AWS CloudFormation" + const markupDoc = { kind: MarkupKind.Markdown, value: '**Bold** documentation' }; + const expectedMarkupResult = { + kind: MarkupKind.Markdown, + value: '**Bold** documentation\n\n**Source:** AWS CloudFormation', }; - const emptyDoc = ""; - const defaultSource = "Source: AWS CloudFormation"; + const emptyDoc = ''; + const defaultSource = 'Source: AWS CloudFormation'; - const markdownDoc = { kind: MarkupKind.Markdown, value: "**Bold** text" }; - const plainTextDoc = { kind: MarkupKind.PlainText, value: "Plain text" }; + const markdownDoc = { kind: MarkupKind.Markdown, value: '**Bold** text' }; + const plainTextDoc = { kind: MarkupKind.PlainText, value: 'Plain text' }; const complexMarkdown = { kind: MarkupKind.Markdown, @@ -29,7 +27,7 @@ describe('CompletionUtils', () => { Creates an **Amazon S3** bucket with: - Versioning enabled - Public access *blocked* - - [Documentation](https://docs.aws.amazon.com)` + - [Documentation](https://docs.aws.amazon.com)`, }; const expectedComplexMarkdown = { @@ -45,52 +43,51 @@ describe('CompletionUtils', () => { }; test('should handle string documentation with source attribution', () => { - const result = createCompletionItem("Test", CompletionItemKind.Keyword, { + const result = createCompletionItem('Test', CompletionItemKind.Keyword, { documentation: stringDoc, }); expect(result.documentation).toEqual(expectedStringResult); }); test('should handle MarkupContent documentation with source attribution', () => { - const result = createCompletionItem("Test", CompletionItemKind.Keyword, { + const result = createCompletionItem('Test', CompletionItemKind.Keyword, { documentation: markupDoc, }); expect(result.documentation).toEqual(expectedMarkupResult); }); test('should handle undefined documentation with default source', () => { - const result = createCompletionItem("Test", CompletionItemKind.Keyword); + const result = createCompletionItem('Test', CompletionItemKind.Keyword); expect(result.documentation).toEqual(defaultSource); }); test('should handle empty string documentation', () => { - const result = createCompletionItem("Test", CompletionItemKind.Keyword, { + const result = createCompletionItem('Test', CompletionItemKind.Keyword, { documentation: emptyDoc, }); expect(result.documentation).toEqual(defaultSource); }); test('should preserve MarkupContent kind when adding source attribution', () => { - const markdownResult = createCompletionItem("Test", CompletionItemKind.Keyword, { + const markdownResult = createCompletionItem('Test', CompletionItemKind.Keyword, { documentation: markdownDoc, }); const markdownAsMarkup = markdownResult.documentation as MarkupContent; expect(markdownAsMarkup.kind).toEqual(MarkupKind.Markdown); - - const plainTextResult = createCompletionItem("Test", CompletionItemKind.Keyword, { + + const plainTextResult = createCompletionItem('Test', CompletionItemKind.Keyword, { documentation: plainTextDoc, }); const plainTextAsMarkup = plainTextResult.documentation as MarkupContent; expect(plainTextAsMarkup.kind).toEqual(MarkupKind.PlainText); }); - test('should handle MarkupContent with existing markdown formatting', () => { - const result = createCompletionItem("Test", CompletionItemKind.Keyword, { - documentation: complexMarkdown, - }); + const result = createCompletionItem('Test', CompletionItemKind.Keyword, { + documentation: complexMarkdown, + }); - expect(result.documentation).toEqual(expectedComplexMarkdown); + expect(result.documentation).toEqual(expectedComplexMarkdown); }); }); }); From 2803eb1f4bb7c4b020b17144b447f1ebda060b87 Mon Sep 17 00:00:00 2001 From: gemammercado Date: Mon, 13 Oct 2025 16:40:32 -0400 Subject: [PATCH 6/7] Changed jsonPointerPath replaceAll to /. Now descriptions are found for nested attributes --- src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts index d14b473c..44d4c65c 100644 --- a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts +++ b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts @@ -348,7 +348,7 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr let attributeDescription = `${attributeName} attribute of ${resource.Type}`; if (schema) { - const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/properties/')}`; + const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/')}`; try { const resolvedSchemas = schema.resolveJsonPointerPath(jsonPointerPath); From 02beae15de0f6276f40a428fe31cc0d76598ee9e Mon Sep 17 00:00:00 2001 From: gemammercado Date: Mon, 13 Oct 2025 18:11:59 -0400 Subject: [PATCH 7/7] Add data parameter to createCompletionItem --- .../IntrinsicFunctionArgumentCompletionProvider.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts index 44d4c65c..e0255457 100644 --- a/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts +++ b/src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts @@ -701,6 +701,9 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr const item = createCompletionItem(attributeName, CompletionItemKind.Property, { documentation: documentation, detail: `GetAtt attribute for ${resource.Type}`, + data: { + isIntrinsicFunction: true, + }, }); if (context.text.length > 0) {