Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 8 additions & 24 deletions src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,11 @@ import { DocumentManager } from '../document/DocumentManager';
import { SchemaRetriever } from '../schema/SchemaRetriever';
import { LoggerFactory } from '../telemetry/LoggerFactory';
import { getFuzzySearchFunction } from '../utils/FuzzySearchUtil';
import { determineGetAttPosition, extractGetAttResourceLogicalId } from '../utils/GetAttUtils';
import {
determineGetAttPosition,
extractGetAttResourceLogicalId,
getAttributeDocumentationFromSchema,
} from '../utils/GetAttUtils';
import { CompletionProvider } from './CompletionProvider';
import { createCompletionItem, createMarkupContent, createReplacementRange } from './CompletionUtils';

Expand Down Expand Up @@ -708,29 +712,9 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
}

const completionItems = attributes.map((attributeName) => {
const schema = this.schemaRetriever.getDefault().schemas.get(resourceType);
let documentation;

if (schema) {
const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/')}`;
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 documentation = createMarkupContent(
getAttributeDocumentationFromSchema(this.schemaRetriever, resourceType, attributeName),
);

const item = createCompletionItem(attributeName, CompletionItemKind.Property, {
documentation: documentation,
Expand Down
32 changes: 7 additions & 25 deletions src/hover/IntrinsicFunctionArgumentHoverProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@ import { ContextWithRelatedEntities } from '../context/ContextWithRelatedEntitie
import { Resource } from '../context/semantic/Entity';
import { EntityType } from '../context/semantic/SemanticTypes';
import { SchemaRetriever } from '../schema/SchemaRetriever';
import { LoggerFactory } from '../telemetry/LoggerFactory';
import { determineGetAttPosition, extractAttributeName, extractGetAttResourceLogicalId } from '../utils/GetAttUtils';
import {
determineGetAttPosition,
extractAttributeName,
extractGetAttResourceLogicalId,
getAttributeDocumentationFromSchema,
} from '../utils/GetAttUtils';
import { formatIntrinsicArgumentHover, getResourceAttributeValueDoc } from './HoverFormatter';
import { HoverProvider } from './HoverProvider';

const log = LoggerFactory.getLogger('IntrinsicFunctionArgumentHoverProvider');

export class IntrinsicFunctionArgumentHoverProvider implements HoverProvider {
constructor(private readonly schemaRetriever: SchemaRetriever) {}

Expand Down Expand Up @@ -146,27 +148,7 @@ export class IntrinsicFunctionArgumentHoverProvider implements HoverProvider {
* Gets documentation for a specific resource attribute from the schema
*/
private getAttributeDocumentation(resourceType: string, attributeName: string): string | undefined {
const schema = this.schemaRetriever.getDefault().schemas.get(resourceType);

// Provide fallback description even when schema is not available
let description = `**${attributeName}** attribute of **${resourceType}**\n\nReturns the value of this attribute when used with the GetAtt intrinsic function.`;

if (schema) {
const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/')}`;

try {
const resolvedSchemas = schema.resolveJsonPointerPath(jsonPointerPath);
if (resolvedSchemas.length === 1) {
const firstSchema = resolvedSchemas[0];
if (firstSchema.description) {
description = firstSchema.description;
}
}
} catch (error) {
log.debug({ error, resourceType, attributeName }, 'Error resolving attribute documentation');
}
}

const description = getAttributeDocumentationFromSchema(this.schemaRetriever, resourceType, attributeName);
return this.formatAttributeHover(resourceType, description);
}

Expand Down
41 changes: 17 additions & 24 deletions src/services/cfnLint/pyodide-worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,21 @@ async function initializePyodide(): Promise<InitializeResult> {
}
}

function convertPythonResultToDiagnostics(result: unknown): PublishDiagnosticsParams[] {
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
if (!result || typeof (result as any).toJs !== 'function') {
throw new Error('Invalid result from Python linting');
}

// Type assertion for the conversion result
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const diagnostics = (result as any).toJs({
dict_converter: Object.fromEntries,
});

return (Array.isArray(diagnostics) ? diagnostics : []) as PublishDiagnosticsParams[];
}

// Lint template content as string
async function lintTemplate(
content: string,
Expand All @@ -251,18 +266,7 @@ async function lintTemplate(
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const result = await pyodide.runPythonAsync(pythonCode);

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!result || typeof result.toJs !== 'function') {
throw new Error('Invalid result from Python linting');
}

// Type assertion for the conversion result
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const diagnostics = result.toJs({
dict_converter: Object.fromEntries,
});

return (Array.isArray(diagnostics) ? diagnostics : []) as PublishDiagnosticsParams[];
return convertPythonResultToDiagnostics(result);
}

// Lint file using path
Expand All @@ -280,18 +284,7 @@ async function lintFile(
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
const result = await pyodide.runPythonAsync(pythonCode);

// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (!result || typeof result.toJs !== 'function') {
throw new Error('Invalid result from Python linting');
}

// Type assertion for the conversion result
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unsafe-call, @typescript-eslint/no-unsafe-member-access
const diagnostics = result.toJs({
dict_converter: Object.fromEntries,
});

return (Array.isArray(diagnostics) ? diagnostics : []) as PublishDiagnosticsParams[];
return convertPythonResultToDiagnostics(result);
}

// Mount folder to Pyodide filesystem
Expand Down
39 changes: 39 additions & 0 deletions src/utils/GetAttUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import { Position } from 'vscode-languageserver-protocol';
import { Context } from '../context/Context';
import { SchemaRetriever } from '../schema/SchemaRetriever';
import { LoggerFactory } from '../telemetry/LoggerFactory';

const log = LoggerFactory.getLogger('GetAttUtils');

/**
* Determines the position in GetAtt arguments (1 for resource, 2 for attribute)
Expand Down Expand Up @@ -98,3 +102,38 @@ export function extractAttributeName(args: unknown, context: Context): string |

return context.text;
}

/**
* Gets documentation for a resource attribute from the schema.
* Returns the attribute description if found in the schema, otherwise returns a default description.
*/
export function getAttributeDocumentationFromSchema(
schemaRetriever: SchemaRetriever,
resourceType: string,
attributeName: string,
): string {
const schema = schemaRetriever.getDefault().schemas.get(resourceType);

// Provide fallback description even when schema is not available
let description = `**${attributeName}** attribute of **${resourceType}**\n\nReturns the value of this attribute when used with the GetAtt intrinsic function.`;

if (schema) {
const jsonPointerPath = `/properties/${attributeName.replaceAll('.', '/')}`;

try {
const resolvedSchemas = schema.resolveJsonPointerPath(jsonPointerPath);

if (resolvedSchemas.length > 0) {
const firstSchema = resolvedSchemas[0];

if (firstSchema.description) {
description = firstSchema.description;
}
}
} catch (error) {
log.debug({ error, resourceType, attributeName }, 'Error resolving attribute documentation');
}
}

return description;
}
Loading