Skip to content

Commit 6bcac48

Browse files
author
Deep Furiya
committed
Constants autocomplete support with intrinsic functions
1 parent 056d922 commit 6bcac48

11 files changed

+354
-8
lines changed

src/autocomplete/CompletionFormatter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ export class CompletionFormatter {
9595
if (
9696
item?.kind === CompletionItemKind.EnumMember ||
9797
item?.kind === CompletionItemKind.Reference ||
98+
item?.kind === CompletionItemKind.Constant ||
9899
item?.kind === CompletionItemKind.Event
99100
) {
100101
return label;

src/autocomplete/CompletionRouter.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ export function createCompletionProviders(
303303
core.syntaxTreeManager,
304304
external.schemaRetriever,
305305
core.documentManager,
306+
external.featureFlags.get('Constants'),
306307
),
307308
);
308309
completionProviderMap.set('ParameterTypeValue', new ParameterTypeValueCompletionProvider());

src/autocomplete/IntrinsicFunctionArgumentCompletionProvider.ts

Lines changed: 61 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,12 @@ import { pseudoParameterDocsMap } from '../artifacts/PseudoParameterDocs';
33
import { Context } from '../context/Context';
44
import { IntrinsicFunction, PseudoParameter, PseudoParametersSet, TopLevelSection } from '../context/ContextType';
55
import { getEntityMap } from '../context/SectionContextBuilder';
6-
import { Mapping, Parameter, Resource } from '../context/semantic/Entity';
6+
import { Constant, Mapping, Parameter, Resource } from '../context/semantic/Entity';
77
import { EntityType } from '../context/semantic/SemanticTypes';
88
import { SyntaxTree } from '../context/syntaxtree/SyntaxTree';
99
import { SyntaxTreeManager } from '../context/syntaxtree/SyntaxTreeManager';
1010
import { DocumentManager } from '../document/DocumentManager';
11+
import { FeatureFlag } from '../featureFlag/FeatureFlagI';
1112
import { SchemaRetriever } from '../schema/SchemaRetriever';
1213
import { LoggerFactory } from '../telemetry/LoggerFactory';
1314
import { Measure } from '../telemetry/TelemetryDecorator';
@@ -54,6 +55,7 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
5455
private readonly syntaxTreeManager: SyntaxTreeManager,
5556
private readonly schemaRetriever: SchemaRetriever,
5657
private readonly documentManager: DocumentManager,
58+
private readonly constantsFeatureFlag: FeatureFlag,
5759
) {}
5860

5961
@Measure({ name: 'getCompletions' })
@@ -104,14 +106,19 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
104106
syntaxTree,
105107
);
106108

107-
if (!parametersAndResourcesCompletions || parametersAndResourcesCompletions.length === 0) {
109+
const constantsCompletions = this.getConstantsCompletions(syntaxTree);
110+
111+
const allCompletions = [
112+
...this.pseudoParameterCompletionItems,
113+
...(parametersAndResourcesCompletions ?? []),
114+
...constantsCompletions,
115+
];
116+
117+
if (allCompletions.length === this.pseudoParameterCompletionItems.length) {
108118
return this.applyFuzzySearch(this.pseudoParameterCompletionItems, context.text);
109119
}
110120

111-
return this.applyFuzzySearch(
112-
[...this.pseudoParameterCompletionItems, ...parametersAndResourcesCompletions],
113-
context.text,
114-
);
121+
return this.applyFuzzySearch(allCompletions, context.text);
115122
}
116123

117124
private handleSubArguments(
@@ -125,6 +132,7 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
125132
syntaxTree,
126133
);
127134
const getAttCompletions = this.getGetAttCompletions(syntaxTree, context.logicalId);
135+
const constantsCompletions = this.getConstantsCompletions(syntaxTree, true);
128136

129137
const baseItems = [...this.pseudoParameterCompletionItems];
130138
if (parametersAndResourcesCompletions && parametersAndResourcesCompletions.length > 0) {
@@ -133,6 +141,9 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
133141
if (getAttCompletions.length > 0) {
134142
baseItems.push(...getAttCompletions);
135143
}
144+
if (constantsCompletions.length > 0) {
145+
baseItems.push(...constantsCompletions);
146+
}
136147

137148
// Handle ${} parameter substitution context detection
138149
const subText = this.getTextForSub(params.textDocument.uri, params.position, context);
@@ -246,6 +257,50 @@ export class IntrinsicFunctionArgumentCompletionProvider implements CompletionPr
246257
return completionItems;
247258
}
248259

260+
private getConstantsAsCompletionItems(
261+
constantsMap: ReadonlyMap<string, Context>,
262+
stringOnly: boolean = false,
263+
): CompletionItem[] {
264+
const completionItems: CompletionItem[] = [];
265+
for (const [constantName, context] of constantsMap) {
266+
const constant = context.entity as Constant;
267+
268+
if (stringOnly && typeof constant.value !== 'string') {
269+
continue;
270+
}
271+
272+
const valuePreview =
273+
typeof constant.value === 'string'
274+
? constant.value
275+
: typeof constant.value === 'object'
276+
? '[Object]'
277+
: String(constant.value);
278+
279+
completionItems.push(
280+
createCompletionItem(`Const::${constantName}`, CompletionItemKind.Constant, {
281+
detail: `Constant`,
282+
documentation: `Value: ${valuePreview}`,
283+
insertText: `Const::${constantName}`,
284+
}),
285+
);
286+
}
287+
288+
return completionItems;
289+
}
290+
291+
private getConstantsCompletions(syntaxTree: SyntaxTree, stringOnly: boolean = false): CompletionItem[] {
292+
if (!this.constantsFeatureFlag.isEnabled()) {
293+
return [];
294+
}
295+
296+
const constantsMap = getEntityMap(syntaxTree, TopLevelSection.Constants);
297+
if (!constantsMap || constantsMap.size === 0) {
298+
return [];
299+
}
300+
301+
return this.getConstantsAsCompletionItems(constantsMap, stringOnly);
302+
}
303+
249304
private shouldIncludeResourceCompletions(context: Context): boolean {
250305
// Only provide resource completions in Resources and Outputs sections
251306
return context.section === TopLevelSection.Resources || context.section === TopLevelSection.Outputs;

tst/unit/autocomplete/CompletionRouter.test.ts

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import { docPosition, Templates } from '../../utils/TemplateUtils';
2424
describe('CompletionRouter', () => {
2525
const mockComponents = createMockComponents();
2626

27-
mockComponents.external.featureFlags.get.returns({ isEnabled: () => false, describe: () => 'mock' });
27+
mockComponents.external.featureFlags.get.returns({ isEnabled: () => true, describe: () => 'mock feature flags' });
2828

2929
const completionRouter = CompletionRouter.create(
3030
mockComponents.core,
@@ -103,7 +103,40 @@ describe('CompletionRouter', () => {
103103
const regularSections = result!.items.filter((item) => item.kind === CompletionItemKind.Class);
104104

105105
// Check that we have the expected number of regular sections
106-
expect(regularSections.length).toBe(10); // Should suggest all template sections for missing document
106+
expect(regularSections.length).toBe(11); // Should suggest all template sections for missing document
107+
});
108+
109+
test('should return less top-level sections when feature flag is disabled', async () => {
110+
const mockComponentsWithDisabledFlag = createMockComponents();
111+
mockComponentsWithDisabledFlag.external.featureFlags.get.returns({
112+
isEnabled: () => false,
113+
describe: () => 'Constants feature flag',
114+
});
115+
116+
const completionRouterWithDisabledFlag = CompletionRouter.create(
117+
mockComponentsWithDisabledFlag.core,
118+
mockComponentsWithDisabledFlag.external,
119+
mockComponentsWithDisabledFlag.providers,
120+
);
121+
122+
const mockContext = createTopLevelContext('Unknown', { text: '' });
123+
124+
mockComponentsWithDisabledFlag.contextManager.getContext.returns(mockContext);
125+
126+
const result = await completionRouterWithDisabledFlag.getCompletions(mockParams);
127+
128+
expect(result).toBeDefined();
129+
expect(result?.isIncomplete).toBe(false);
130+
131+
// Filter to get only regular sections (not snippets)
132+
const regularSections = result!.items.filter((item) => item.kind === CompletionItemKind.Class);
133+
134+
// Check that we have 10 sections (without Constants)
135+
expect(regularSections.length).toBe(10);
136+
137+
// Verify Constants is not in the results
138+
const constantsItem = regularSections.find((item) => item.label === 'Constants');
139+
expect(constantsItem).toBeUndefined();
107140
});
108141

109142
test('should return resource section provider given context entity type of Resource', () => {
@@ -201,6 +234,10 @@ describe('CompletionRouter', () => {
201234
schemaRetriever: mockComponents.schemaRetriever,
202235
settingsManager: mockSettingsManager,
203236
});
237+
mockTestComponents.external.featureFlags.get.returns({
238+
isEnabled: () => true,
239+
describe: () => 'mock feature flags',
240+
});
204241
const completionProviderMap = createCompletionProviders(
205242
mockTestComponents.core,
206243
mockTestComponents.external,

0 commit comments

Comments
 (0)