From 3c0a5f79fcff5a952be50a9bf0fc9777164eb30b Mon Sep 17 00:00:00 2001 From: Allan Guigou <34221163+AllanGuigou@users.noreply.github.com> Date: Thu, 29 Jan 2026 14:34:51 +0000 Subject: [PATCH 1/5] Remove experimental flag for `case` function --- expressions/src/completion.ts | 14 +++-------- expressions/src/features.test.ts | 3 +-- expressions/src/features.ts | 9 +------- .../src/complete.expressions.test.ts | 23 +++++++------------ languageservice/src/complete.test.ts | 20 ++-------------- 5 files changed, 15 insertions(+), 54 deletions(-) diff --git a/expressions/src/completion.ts b/expressions/src/completion.ts index 846ebe6..4ebafc3 100644 --- a/expressions/src/completion.ts +++ b/expressions/src/completion.ts @@ -2,7 +2,6 @@ import {DescriptionPair} from "./completion/descriptionDictionary.js"; import {Dictionary, isDictionary} from "./data/dictionary.js"; import {ExpressionData} from "./data/expressiondata.js"; import {Evaluator} from "./evaluator.js"; -import {FeatureFlags} from "./features.js"; import {wellKnownFunctions} from "./funcs.js"; import {FunctionDefinition, FunctionInfo} from "./funcs/info.js"; import {Lexer, Token, TokenType} from "./lexer.js"; @@ -27,15 +26,13 @@ export type CompletionItem = { * @param context Context available for the expression * @param extensionFunctions List of functions available * @param functions Optional map of functions to use during evaluation - * @param featureFlags Optional feature flags to control which features are enabled * @returns Array of completion items */ export function complete( input: string, context: Dictionary, extensionFunctions: FunctionInfo[], - functions?: Map, - featureFlags?: FeatureFlags + functions?: Map ): CompletionItem[] { // Lex const lexer = new Lexer(input); @@ -66,7 +63,7 @@ export function complete( const result = contextKeys(context); // Merge with functions - result.push(...functionItems(extensionFunctions, featureFlags)); + result.push(...functionItems(extensionFunctions)); return result; } @@ -91,15 +88,10 @@ export function complete( return contextKeys(result); } -function functionItems(extensionFunctions: FunctionInfo[], featureFlags?: FeatureFlags): CompletionItem[] { +function functionItems(extensionFunctions: FunctionInfo[]): CompletionItem[] { const result: CompletionItem[] = []; - const flags = featureFlags ?? new FeatureFlags(); for (const fdef of [...Object.values(wellKnownFunctions), ...extensionFunctions]) { - // Filter out case function if feature is disabled - if (fdef.name === "case" && !flags.isEnabled("allowCaseFunction")) { - continue; - } result.push({ label: fdef.name, description: fdef.description, diff --git a/expressions/src/features.test.ts b/expressions/src/features.test.ts index f280c14..307616a 100644 --- a/expressions/src/features.test.ts +++ b/expressions/src/features.test.ts @@ -53,8 +53,7 @@ describe("FeatureFlags", () => { const flags = new FeatureFlags({all: true}); expect(flags.getEnabledFeatures()).toEqual([ "missingInputsQuickfix", - "blockScalarChompingWarning", - "allowCaseFunction" + "blockScalarChompingWarning" ]); }); }); diff --git a/expressions/src/features.ts b/expressions/src/features.ts index e142aac..7bd5481 100644 --- a/expressions/src/features.ts +++ b/expressions/src/features.ts @@ -28,12 +28,6 @@ export interface ExperimentalFeatures { * @default false */ blockScalarChompingWarning?: boolean; - - /** - * Enable the case() function in expressions. - * @default false - */ - allowCaseFunction?: boolean; } /** @@ -47,8 +41,7 @@ export type ExperimentalFeatureKey = Exclude; */ const allFeatureKeys: ExperimentalFeatureKey[] = [ "missingInputsQuickfix", - "blockScalarChompingWarning", - "allowCaseFunction" + "blockScalarChompingWarning" ]; export class FeatureFlags { diff --git a/languageservice/src/complete.expressions.test.ts b/languageservice/src/complete.expressions.test.ts index 0a2f276..e20b4cf 100644 --- a/languageservice/src/complete.expressions.test.ts +++ b/languageservice/src/complete.expressions.test.ts @@ -1,5 +1,5 @@ /* eslint-disable @typescript-eslint/no-non-null-assertion */ -import {data, DescriptionDictionary, FeatureFlags} from "@actions/expressions"; +import {data, DescriptionDictionary} from "@actions/expressions"; import {CompletionItem, CompletionItemKind, MarkupContent} from "vscode-languageserver-types"; import {complete, getExpressionInput} from "./complete.js"; import {ContextProviderConfig} from "./context-providers/config.js"; @@ -69,8 +69,7 @@ describe("expressions", () => { it("single region", async () => { const input = "run-name: ${{ | }}"; const result = await complete(...getPositionFromCursor(input), { - contextProviderConfig, - featureFlags: new FeatureFlags({allowCaseFunction: true}) + contextProviderConfig }); expect(result.map(x => x.label)).toEqual([ @@ -113,8 +112,7 @@ describe("expressions", () => { it("single region with existing input", async () => { const input = "run-name: ${{ g| }}"; const result = await complete(...getPositionFromCursor(input), { - contextProviderConfig, - featureFlags: new FeatureFlags({allowCaseFunction: true}) + contextProviderConfig }); expect(result.map(x => x.label)).toEqual([ @@ -135,8 +133,7 @@ describe("expressions", () => { it("single region with existing condition", async () => { const input = "run-name: ${{ g| == 'test' }}"; const result = await complete(...getPositionFromCursor(input), { - contextProviderConfig, - featureFlags: new FeatureFlags({allowCaseFunction: true}) + contextProviderConfig }); expect(result.map(x => x.label)).toEqual([ @@ -157,8 +154,7 @@ describe("expressions", () => { it("multiple regions with partial function", async () => { const input = "run-name: Run a ${{ inputs.test }} one-line script ${{ from|('test') == inputs.name }}"; const result = await complete(...getPositionFromCursor(input), { - contextProviderConfig, - featureFlags: new FeatureFlags({allowCaseFunction: true}) + contextProviderConfig }); expect(result.map(x => x.label)).toEqual([ @@ -179,8 +175,7 @@ describe("expressions", () => { it("multiple regions - first region", async () => { const input = "run-name: test-${{ git| == 1 }}-${{ github.event }}"; const result = await complete(...getPositionFromCursor(input), { - contextProviderConfig, - featureFlags: new FeatureFlags({allowCaseFunction: true}) + contextProviderConfig }); expect(result.map(x => x.label)).toEqual([ @@ -201,8 +196,7 @@ describe("expressions", () => { it("multiple regions", async () => { const input = "run-name: test-${{ github }}-${{ | }}"; const result = await complete(...getPositionFromCursor(input), { - contextProviderConfig, - featureFlags: new FeatureFlags({allowCaseFunction: true}) + contextProviderConfig }); expect(result.map(x => x.label)).toEqual([ @@ -1181,8 +1175,7 @@ jobs: `; const result = await complete(...getPositionFromCursor(input), { - contextProviderConfig, - featureFlags: new FeatureFlags({allowCaseFunction: true}) + contextProviderConfig }); expect(result.map(x => x.label)).toEqual([ "env", diff --git a/languageservice/src/complete.test.ts b/languageservice/src/complete.test.ts index 5026283..c661080 100644 --- a/languageservice/src/complete.test.ts +++ b/languageservice/src/complete.test.ts @@ -6,7 +6,6 @@ import {getPositionFromCursor} from "./test-utils/cursor-position.js"; import {TestLogger} from "./test-utils/logger.js"; import {clearCache} from "./utils/workflow-cache.js"; import {ValueProviderConfig, ValueProviderKind} from "./value-providers/config.js"; -import {FeatureFlags} from "@actions/expressions/features"; registerLogger(new TestLogger()); @@ -898,11 +897,9 @@ jobs: }); describe("expression completions", () => { - it("include case function when enabled", async () => { + it("includes case function", async () => { const input = "on: push\njobs:\n build:\n runs-on: ${{ c|"; - const result = await complete(...getPositionFromCursor(input), { - featureFlags: new FeatureFlags({allowCaseFunction: true}) - }); + const result = await complete(...getPositionFromCursor(input)); expect(result).not.toBeUndefined(); // Expression completions starting with 'c': case, contains @@ -910,18 +907,5 @@ jobs: expect(labels).toContain("case"); expect(labels).toContain("contains"); }); - - it("exclude case function when disabled", async () => { - const input = "on: push\njobs:\n build:\n runs-on: ${{ c|"; - const result = await complete(...getPositionFromCursor(input), { - featureFlags: new FeatureFlags({allowCaseFunction: false}) - }); - - expect(result).not.toBeUndefined(); - // Expression completions starting with 'c': contains - const labels = result.map(x => x.label); - expect(labels).not.toContain("case"); - expect(labels).toContain("contains"); - }); }); }); From fea8440c1d5476d3117f7231804818a7d19fae9f Mon Sep 17 00:00:00 2001 From: Allan Guigou <34221163+AllanGuigou@users.noreply.github.com> Date: Thu, 29 Jan 2026 15:56:43 +0000 Subject: [PATCH 2/5] Fix lint --- expressions/src/features.test.ts | 5 +---- expressions/src/features.ts | 5 +---- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/expressions/src/features.test.ts b/expressions/src/features.test.ts index 307616a..6694141 100644 --- a/expressions/src/features.test.ts +++ b/expressions/src/features.test.ts @@ -51,10 +51,7 @@ describe("FeatureFlags", () => { it("returns all features when all is enabled", () => { const flags = new FeatureFlags({all: true}); - expect(flags.getEnabledFeatures()).toEqual([ - "missingInputsQuickfix", - "blockScalarChompingWarning" - ]); + expect(flags.getEnabledFeatures()).toEqual(["missingInputsQuickfix", "blockScalarChompingWarning"]); }); }); }); diff --git a/expressions/src/features.ts b/expressions/src/features.ts index 7bd5481..b0a0770 100644 --- a/expressions/src/features.ts +++ b/expressions/src/features.ts @@ -39,10 +39,7 @@ export type ExperimentalFeatureKey = Exclude; * All known experimental feature keys. * This list must be kept in sync with the ExperimentalFeatures interface. */ -const allFeatureKeys: ExperimentalFeatureKey[] = [ - "missingInputsQuickfix", - "blockScalarChompingWarning" -]; +const allFeatureKeys: ExperimentalFeatureKey[] = ["missingInputsQuickfix", "blockScalarChompingWarning"]; export class FeatureFlags { private readonly features: ExperimentalFeatures; From ef457b29fa6e9148f1cfef539664873df1d7684d Mon Sep 17 00:00:00 2001 From: Allan Guigou <34221163+AllanGuigou@users.noreply.github.com> Date: Thu, 29 Jan 2026 16:05:18 +0000 Subject: [PATCH 3/5] Remove unused feature flag param --- languageservice/src/complete.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index 32c2e5e..055ebbd 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -532,7 +532,7 @@ function getExpressionCompletionItems( context: DescriptionDictionary, extensionFunctions: FunctionInfo[], pos: Position, - featureFlags?: FeatureFlags + _featureFlags?: FeatureFlags ): CompletionItem[] { if (!token.range) { return []; @@ -551,8 +551,8 @@ function getExpressionCompletionItems( const expressionInput = (getExpressionInput(currentInput, cursorOffset) || "").trim(); try { - return completeExpression(expressionInput, context, extensionFunctions, validatorFunctions, featureFlags).map( - item => mapExpressionCompletionItem(item, currentInput[cursorOffset]) + return completeExpression(expressionInput, context, extensionFunctions, validatorFunctions).map(item => + mapExpressionCompletionItem(item, currentInput[cursorOffset]) ); } catch (e) { error(`Error while completing expression: '${(e as Error)?.message || ""}'`); From 7ac83f43a68544603c89d92f190822d967c583f3 Mon Sep 17 00:00:00 2001 From: Allan Guigou <34221163+AllanGuigou@users.noreply.github.com> Date: Thu, 29 Jan 2026 11:09:12 -0500 Subject: [PATCH 4/5] Fix unused param --- languageservice/src/complete.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index 055ebbd..f051e5d 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -141,7 +141,7 @@ export async function complete( func.description = getFunctionDescription(func.name); } - return getExpressionCompletionItems(token, context, extensionFunctions, newPos, config?.featureFlags); + return getExpressionCompletionItems(token, context, extensionFunctions, newPos); } const indentation = guessIndentation(newDoc, 2, true); // Use 2 spaces as default and most common for YAML @@ -531,8 +531,7 @@ function getExpressionCompletionItems( token: TemplateToken, context: DescriptionDictionary, extensionFunctions: FunctionInfo[], - pos: Position, - _featureFlags?: FeatureFlags + pos: Position ): CompletionItem[] { if (!token.range) { return []; From 0410ab83026c7fb8da89bf1d50bdc1ac50ad41b8 Mon Sep 17 00:00:00 2001 From: Allan Guigou <34221163+AllanGuigou@users.noreply.github.com> Date: Thu, 29 Jan 2026 17:23:01 +0000 Subject: [PATCH 5/5] Add featureFlags param with lint ignore --- expressions/src/completion.ts | 6 +++++- languageservice/src/complete.ts | 9 +++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/expressions/src/completion.ts b/expressions/src/completion.ts index 4ebafc3..b7cb252 100644 --- a/expressions/src/completion.ts +++ b/expressions/src/completion.ts @@ -2,6 +2,7 @@ import {DescriptionPair} from "./completion/descriptionDictionary.js"; import {Dictionary, isDictionary} from "./data/dictionary.js"; import {ExpressionData} from "./data/expressiondata.js"; import {Evaluator} from "./evaluator.js"; +import {FeatureFlags} from "./features.js"; import {wellKnownFunctions} from "./funcs.js"; import {FunctionDefinition, FunctionInfo} from "./funcs/info.js"; import {Lexer, Token, TokenType} from "./lexer.js"; @@ -26,13 +27,16 @@ export type CompletionItem = { * @param context Context available for the expression * @param extensionFunctions List of functions available * @param functions Optional map of functions to use during evaluation + * @param featureFlags Optional feature flags to control which features are enabled * @returns Array of completion items */ export function complete( input: string, context: Dictionary, extensionFunctions: FunctionInfo[], - functions?: Map + functions?: Map, + // eslint-disable-next-line @typescript-eslint/no-unused-vars + featureFlags?: FeatureFlags ): CompletionItem[] { // Lex const lexer = new Lexer(input); diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index f051e5d..32c2e5e 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -141,7 +141,7 @@ export async function complete( func.description = getFunctionDescription(func.name); } - return getExpressionCompletionItems(token, context, extensionFunctions, newPos); + return getExpressionCompletionItems(token, context, extensionFunctions, newPos, config?.featureFlags); } const indentation = guessIndentation(newDoc, 2, true); // Use 2 spaces as default and most common for YAML @@ -531,7 +531,8 @@ function getExpressionCompletionItems( token: TemplateToken, context: DescriptionDictionary, extensionFunctions: FunctionInfo[], - pos: Position + pos: Position, + featureFlags?: FeatureFlags ): CompletionItem[] { if (!token.range) { return []; @@ -550,8 +551,8 @@ function getExpressionCompletionItems( const expressionInput = (getExpressionInput(currentInput, cursorOffset) || "").trim(); try { - return completeExpression(expressionInput, context, extensionFunctions, validatorFunctions).map(item => - mapExpressionCompletionItem(item, currentInput[cursorOffset]) + return completeExpression(expressionInput, context, extensionFunctions, validatorFunctions, featureFlags).map( + item => mapExpressionCompletionItem(item, currentInput[cursorOffset]) ); } catch (e) { error(`Error while completing expression: '${(e as Error)?.message || ""}'`);