Skip to content

Commit 802e844

Browse files
authored
implement ref and getAtt for related resources (#484)
* related resources * implement getAtt for related resources * add parent resource selection for related resources
1 parent a1d617e commit 802e844

11 files changed

Lines changed: 1223 additions & 46 deletions

src/handlers/RelatedResourcesHandler.ts

Lines changed: 63 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { TopLevelSection } from '../context/CloudFormationEnums';
33
import { getEntityMap } from '../context/SectionContextBuilder';
44
import { Resource } from '../context/semantic/Entity';
55
import {
6+
AuthoredResource,
67
GetRelatedResourceTypesParams,
78
InsertRelatedResourcesParams,
89
RelatedResourcesCodeAction,
@@ -19,22 +20,25 @@ import {
1920

2021
export function getAuthoredResourceTypesHandler(
2122
components: ServerComponents,
22-
): RequestHandler<TemplateUri, string[], void> {
23+
): RequestHandler<TemplateUri, AuthoredResource[], void> {
2324
return (rawParams) => {
2425
try {
2526
const templateUri = parseWithPrettyError(parseTemplateUriParams, rawParams);
2627
const syntaxTree = components.syntaxTreeManager.getSyntaxTree(templateUri);
2728
if (syntaxTree) {
2829
const resourcesMap = getEntityMap(syntaxTree, TopLevelSection.Resources);
2930
if (resourcesMap) {
30-
const resourceTypes = [...resourcesMap.values()]
31-
.map((context) => {
32-
const resource = context.entity as Resource;
33-
return resource?.Type;
34-
})
35-
.filter((type): type is string => type !== undefined && type !== null);
36-
37-
return [...new Set(resourceTypes)];
31+
const resources: AuthoredResource[] = [];
32+
for (const [logicalId, context] of resourcesMap) {
33+
const resource = context.entity as Resource;
34+
if (resource?.Type) {
35+
resources.push({
36+
logicalId,
37+
type: resource.Type,
38+
});
39+
}
40+
}
41+
return resources;
3842
}
3943
}
4044

@@ -52,26 +56,74 @@ export function getRelatedResourceTypesHandler(
5256
try {
5357
const { parentResourceType } = parseWithPrettyError(parseGetRelatedResourceTypesParams, rawParams);
5458
const relatedTypes = components.relationshipSchemaService.getAllRelatedResourceTypes(parentResourceType);
55-
return [...relatedTypes];
59+
60+
const filtered = [...relatedTypes].filter((relatedType) =>
61+
hasExactlyOnePopulatableRelationship(relatedType, parentResourceType, components),
62+
);
63+
64+
return filtered;
5665
} catch (error) {
5766
handleLspError(error, 'Failed to get related resource types');
5867
}
5968
};
6069
}
6170

71+
/**
72+
* Checks if a related resource type has exactly one top-level property
73+
* that references the parent resource type, and that property is not an array.
74+
*/
75+
function hasExactlyOnePopulatableRelationship(
76+
relatedType: string,
77+
parentResourceType: string,
78+
components: ServerComponents,
79+
): boolean {
80+
const relationships = components.relationshipSchemaService.getRelationshipsForResourceType(relatedType);
81+
if (!relationships) {
82+
return false;
83+
}
84+
85+
const schema = components.schemaRetriever.getDefault().schemas.get(relatedType);
86+
87+
const topLevelParentRefs: { property: string; isArray: boolean }[] = [];
88+
for (const rel of relationships.relationships) {
89+
if (rel.property.includes('/')) {
90+
continue;
91+
}
92+
93+
const referencesParent = rel.relatedResourceTypes.some((rt) => rt.typeName === parentResourceType);
94+
if (!referencesParent) {
95+
continue;
96+
}
97+
98+
const isArray = schema?.properties?.[rel.property]?.type === 'array';
99+
topLevelParentRefs.push({ property: rel.property, isArray });
100+
101+
if (topLevelParentRefs.length > 1) {
102+
break;
103+
}
104+
}
105+
106+
if (topLevelParentRefs.length !== 1) {
107+
return false;
108+
}
109+
110+
return !topLevelParentRefs[0].isArray;
111+
}
112+
62113
export function insertRelatedResourcesHandler(
63114
components: ServerComponents,
64115
): RequestHandler<InsertRelatedResourcesParams, RelatedResourcesCodeAction, void> {
65116
return (rawParams) => {
66117
try {
67-
const { templateUri, relatedResourceTypes, parentResourceType } = parseWithPrettyError(
118+
const { templateUri, relatedResourceTypes, parentResourceType, parentLogicalId } = parseWithPrettyError(
68119
parseInsertRelatedResourcesParams,
69120
rawParams,
70121
);
71122
return components.relatedResourcesSnippetProvider.insertRelatedResources(
72123
templateUri,
73124
relatedResourceTypes,
74125
parentResourceType,
126+
parentLogicalId,
75127
);
76128
} catch (error) {
77129
handleLspError(error, 'Failed to insert related resources');

src/handlers/RelatedResourcesParser.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ const InsertRelatedResourcesParamsSchema = z.object({
1616
templateUri: NonEmptyZodString,
1717
relatedResourceTypes: z.array(NonEmptyZodString).min(1),
1818
parentResourceType: NonEmptyZodString,
19+
parentLogicalId: NonEmptyZodString.optional(),
1920
});
2021

2122
export function parseTemplateUriParams(input: unknown): TemplateUri {

src/protocol/LspRelatedResourcesHandlers.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { Connection, RequestHandler } from 'vscode-languageserver';
22
import {
3+
AuthoredResource,
34
GetAuthoredResourceTypesRequest,
45
GetRelatedResourceTypesParams,
56
GetRelatedResourceTypesRequest,
@@ -12,7 +13,7 @@ import {
1213
export class LspRelatedResourcesHandlers {
1314
constructor(private readonly connection: Connection) {}
1415

15-
onGetAuthoredResourceTypes(handler: RequestHandler<TemplateUri, string[], void>) {
16+
onGetAuthoredResourceTypes(handler: RequestHandler<TemplateUri, AuthoredResource[], void>) {
1617
this.connection.onRequest(GetAuthoredResourceTypesRequest.method, handler);
1718
}
1819

src/protocol/RelatedResourcesProtocol.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,16 @@ export type GetRelatedResourceTypesParams = {
66
parentResourceType: string;
77
};
88

9+
export type AuthoredResource = {
10+
logicalId: string;
11+
type: string;
12+
};
13+
914
export type InsertRelatedResourcesParams = {
1015
templateUri: string;
1116
relatedResourceTypes: string[];
1217
parentResourceType: string;
18+
parentLogicalId?: string;
1319
};
1420

1521
export interface RelatedResourcesCodeAction extends CodeAction {
@@ -19,7 +25,7 @@ export interface RelatedResourcesCodeAction extends CodeAction {
1925
};
2026
}
2127

22-
export const GetAuthoredResourceTypesRequest = new RequestType<TemplateUri, string[], void>(
28+
export const GetAuthoredResourceTypesRequest = new RequestType<TemplateUri, AuthoredResource[], void>(
2329
'aws/cfn/template/resources/authored',
2430
);
2531

0 commit comments

Comments
 (0)