Skip to content

Commit 7cca2f2

Browse files
Deep Furiyadeepfuriya
authored andcommitted
Exclude Fn::ForEach resources from Ref and GetAtt argument completions
1 parent 9f484ce commit 7cca2f2

File tree

4 files changed

+110
-1
lines changed

4 files changed

+110
-1
lines changed

src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,11 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
341341
continue;
342342
}
343343

344+
// Skip Fn::ForEach resources
345+
if (resourceName.startsWith(IntrinsicFunction.ForEach)) {
346+
continue;
347+
}
348+
344349
const resource = resourceContext.entity;
345350

346351
completionItems.push(
@@ -690,7 +695,7 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
690695
}
691696

692697
const items = [...resourceEntities.keys()]
693-
.filter((logicalId) => logicalId !== context.logicalId)
698+
.filter((logicalId) => logicalId !== context.logicalId && !logicalId.startsWith(IntrinsicFunction.ForEach))
694699
.map((logicalId) => createCompletionItem(logicalId, CompletionItemKind.Reference, { context }));
695700

696701
return context.text.length > 0 ? this.fuzzySearch(items, context.text) : items;

tst/e2e/Completion.test.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1921,6 +1921,47 @@ Resources:
19211921

19221922
await client.closeDocument({ textDocument: { uri } });
19231923
});
1924+
1925+
it('should exclude Fn::ForEach resources from !Ref completions', async () => {
1926+
const template = getSimpleYamlTemplateText();
1927+
const updatedTemplate = `AWSTemplateFormatVersion: '2010-09-09'
1928+
Transform: AWS::LanguageExtensions
1929+
Resources:
1930+
FirstBucket:
1931+
Type: AWS::S3::Bucket
1932+
Fn::ForEach::LoopBuckets:
1933+
- BucketName
1934+
- - Alpha
1935+
- Beta
1936+
- Bucket\${BucketName}:
1937+
Type: AWS::S3::Bucket
1938+
AnotherResource:
1939+
Type: AWS::EC2::VPC
1940+
Properties:
1941+
CidrBlock: !Ref F`;
1942+
const uri = await client.openYamlTemplate(template);
1943+
1944+
await client.changeDocument({
1945+
textDocument: { uri, version: 2 },
1946+
contentChanges: [{ text: updatedTemplate }],
1947+
});
1948+
1949+
const completions: any = await client.completion({
1950+
textDocument: { uri },
1951+
position: { line: 14, character: 23 },
1952+
});
1953+
1954+
expect(completions).toBeDefined();
1955+
expect(completions?.items).toBeDefined();
1956+
1957+
const labels = completions.items.map((item: any) => item.label);
1958+
// Should include regular resources
1959+
expect(labels).toContain('FirstBucket');
1960+
// Should NOT include Fn::ForEach resources
1961+
expect(labels).not.toContain('Fn::ForEach::LoopBuckets');
1962+
1963+
await client.closeDocument({ textDocument: { uri } });
1964+
});
19241965
});
19251966

19261967
describe('Value Completions', () => {

tst/unit/autocomplete/IntrinsicFunctionArgumentCompletionProvider.GetAtt.test.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -288,6 +288,30 @@ describe('IntrinsicFunctionArgumentCompletionProvider - GetAtt Function', () =>
288288
expect(labels).toContain('LambdaRole');
289289
expect(labels).toContain('DatabaseInstance');
290290
});
291+
292+
it('should exclude Fn::ForEach resources from completions', () => {
293+
const resourceDataWithForEach = {
294+
...mockResourceData,
295+
'Fn::ForEach::Buckets': { Type: 'AWS::S3::Bucket' },
296+
'Fn::ForEach::Instances': { Type: 'AWS::EC2::Instance' },
297+
};
298+
setupResourceEntities(resourceDataWithForEach);
299+
300+
const mockContext = createMockGetAttContext('', []);
301+
302+
const result = provider.getCompletions(mockContext, createTestParams());
303+
304+
expect(result).toBeDefined();
305+
expect(result!.length).toBe(4); // Should only include the 4 regular resources
306+
307+
const labels = result!.map((item) => item.label);
308+
expect(labels).toContain('MyVPC');
309+
expect(labels).toContain('MyS3Bucket');
310+
expect(labels).toContain('LambdaRole');
311+
expect(labels).toContain('DatabaseInstance');
312+
expect(labels).not.toContain('Fn::ForEach::Buckets');
313+
expect(labels).not.toContain('Fn::ForEach::Instances');
314+
});
291315
});
292316

293317
describe('Invalid Arguments', () => {

tst/unit/autocomplete/IntrinsicFunctionArgumentCompletionProvider.Ref.test.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,45 @@ describe('IntrinsicFunctionArgumentCompletionProvider - Ref Function', () => {
238238
const resourceCompletions = result!.filter((item) => item.detail?.includes('Resource ('));
239239
expect(resourceCompletions.length).toBe(2); // Should include all resources in Outputs section
240240
});
241+
242+
it('should exclude Fn::ForEach resources from completions', () => {
243+
const context = createMockContext(TopLevelSection.Resources, 'MyResource', { text: '' });
244+
Object.defineProperty(context, 'intrinsicContext', {
245+
value: createMockIntrinsicContext(IntrinsicFunction.Ref, ''),
246+
});
247+
248+
const mockResourcesMap = new Map([
249+
['MyResource', { entity: { Type: 'AWS::S3::Bucket' } }],
250+
['OtherResource', { entity: { Type: 'AWS::EC2::Instance' } }],
251+
['Fn::ForEach::Buckets', { entity: { Type: 'AWS::S3::Bucket' } }],
252+
['Fn::ForEach::Instances', { entity: { Type: 'AWS::EC2::Instance' } }],
253+
]);
254+
255+
const mockSyntaxTree = stubInterface<SyntaxTree>();
256+
mockSyntaxTree.findTopLevelSections.returns(new Map([[TopLevelSection.Resources, {} as SyntaxNode]]));
257+
(mockSyntaxTree as any).type = DocumentType.YAML;
258+
mockSyntaxTreeManager.getSyntaxTree.returns(mockSyntaxTree);
259+
260+
(getEntityMap as any).mockImplementation((syntaxTree: any, section: TopLevelSection) => {
261+
if (section === TopLevelSection.Resources) {
262+
return mockResourcesMap;
263+
}
264+
return new Map();
265+
});
266+
267+
const result = provider.getCompletions(context, createTestParams());
268+
269+
expect(result).toBeDefined();
270+
const resourceCompletions = result!.filter((item) => item.detail?.includes('Resource ('));
271+
// Should only include OtherResource (MyResource is excluded as current, Fn::ForEach:: resources are filtered)
272+
expect(resourceCompletions.length).toBe(1);
273+
expect(resourceCompletions[0].label).toBe('OtherResource');
274+
275+
// Verify Fn::ForEach resources are not in the results
276+
const labels = result!.map((item) => item.label);
277+
expect(labels).not.toContain('Fn::ForEach::Buckets');
278+
expect(labels).not.toContain('Fn::ForEach::Instances');
279+
});
241280
});
242281

243282
describe('filtering and fuzzy search', () => {

0 commit comments

Comments
 (0)