Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 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
14 changes: 3 additions & 11 deletions expressions/src/completion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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";
Expand All @@ -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<string, FunctionDefinition>,
featureFlags?: FeatureFlags
functions?: Map<string, FunctionDefinition>
): CompletionItem[] {
// Lex
const lexer = new Lexer(input);
Expand Down Expand Up @@ -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;
}
Expand All @@ -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,
Expand Down
6 changes: 1 addition & 5 deletions expressions/src/features.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,11 +51,7 @@ describe("FeatureFlags", () => {

it("returns all features when all is enabled", () => {
const flags = new FeatureFlags({all: true});
expect(flags.getEnabledFeatures()).toEqual([
"missingInputsQuickfix",
"blockScalarChompingWarning",
"allowCaseFunction"
]);
expect(flags.getEnabledFeatures()).toEqual(["missingInputsQuickfix", "blockScalarChompingWarning"]);
});
});
});
12 changes: 1 addition & 11 deletions expressions/src/features.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,6 @@ export interface ExperimentalFeatures {
* @default false
*/
blockScalarChompingWarning?: boolean;

/**
* Enable the case() function in expressions.
* @default false
*/
allowCaseFunction?: boolean;
}

/**
Expand All @@ -45,11 +39,7 @@ export type ExperimentalFeatureKey = Exclude<keyof ExperimentalFeatures, "all">;
* All known experimental feature keys.
* This list must be kept in sync with the ExperimentalFeatures interface.
*/
const allFeatureKeys: ExperimentalFeatureKey[] = [
"missingInputsQuickfix",
"blockScalarChompingWarning",
"allowCaseFunction"
];
const allFeatureKeys: ExperimentalFeatureKey[] = ["missingInputsQuickfix", "blockScalarChompingWarning"];

export class FeatureFlags {
private readonly features: ExperimentalFeatures;
Expand Down
23 changes: 8 additions & 15 deletions languageservice/src/complete.expressions.test.ts
Original file line number Diff line number Diff line change
@@ -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";
Expand Down Expand Up @@ -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([
Expand Down Expand Up @@ -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([
Expand All @@ -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([
Expand All @@ -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([
Expand All @@ -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([
Expand All @@ -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([
Expand Down Expand Up @@ -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",
Expand Down
20 changes: 2 additions & 18 deletions languageservice/src/complete.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand Down Expand Up @@ -898,30 +897,15 @@ 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
const labels = result.map(x => x.label);
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");
});
});
});
9 changes: 4 additions & 5 deletions languageservice/src/complete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -531,8 +531,7 @@ function getExpressionCompletionItems(
token: TemplateToken,
context: DescriptionDictionary,
extensionFunctions: FunctionInfo[],
pos: Position,
featureFlags?: FeatureFlags
pos: Position
): CompletionItem[] {
if (!token.range) {
return [];
Expand All @@ -551,8 +550,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 || "<no details>"}'`);
Expand Down
Loading