Fix a bunch of auto-complete issues

This commit is contained in:
Christopher Schleiden
2023-01-18 16:54:53 -08:00
parent a7fd04c47d
commit e6a2e3eb43
10 changed files with 214 additions and 73 deletions
@@ -37,6 +37,10 @@ describe("expressions", () => {
expect(test("${{ github.| == 'test' }}")).toBe(" github.");
expect(test("test ${{ github.| == 'test' }}")).toBe(" github.");
expect(test("${{ vars }} ${{ gh |}}")).toBe(" gh ");
expect(test("${{ test.|")).toBe(" test.");
expect(test("${{ test.| }}")).toBe(" test.");
expect(test("${{ 1 == (test.|)")).toBe(" 1 == (test.");
});
describe("top-level auto-complete", () => {
@@ -58,6 +62,16 @@ describe("expressions", () => {
]);
});
it("within parentheses", async () => {
const result = await complete(
...getPositionFromCursor("run-name: ${{ 1 == (github.|) }}"),
undefined,
contextProviderConfig
);
expect(result.map(x => x.label)).toEqual(["event"]);
});
it("contains description", async () => {
const input = "run-name: ${{ github.| }}";
const result = await complete(...getPositionFromCursor(input), undefined, undefined);
@@ -202,6 +216,25 @@ jobs:
});
});
it("nested with parentheses", async () => {
const input = `on:
workflow_dispatch:
inputs:
test:
type: string
jobs:
build:
runs-on: ubuntu-latest
env:
foo: '{}'
steps:
- name: "\${{ fromJSON('test') == (inputs.|) }}"`;
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
expect(result.map(x => x.label)).toEqual(["test"]);
});
it("nested auto-complete", async () => {
const input = "run-name: ${{ github.| }}";
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
+15 -7
View File
@@ -14,6 +14,8 @@ import {CompletionItem, CompletionItemKind, CompletionItemTag, Range, TextEdit}
import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {validatorFunctions} from "./expression-validation/functions";
import {error} from "./log";
import {nullTrace} from "./nulltrace";
import {findToken} from "./utils/find-token";
import {guessIndentation} from "./utils/indentation-guesser";
@@ -75,13 +77,14 @@ export async function complete(
// Transform the overall position into a node relative position
let relCharPos: number = 0;
const lineDiff = newPos.line - token.range!.start[0];
if (token.range!.start[0] !== token.range!.end[0]) {
const range = mapRange(token.range!);
if (range.start.line !== range.end.line) {
const lines = currentInput.split("\n");
const lineDiff = newPos.line - range.start.line - 1;
const linesBeforeCusor = lines.slice(0, lineDiff);
relCharPos = linesBeforeCusor.join("\n").length + 1 + newPos.character;
relCharPos = linesBeforeCusor.join("\n").length + newPos.character + 1;
} else {
relCharPos = newPos.character - token.range!.start[1] + 1;
relCharPos = newPos.character - range.start.character;
}
const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim();
@@ -89,9 +92,14 @@ export async function complete(
const allowedContext = token.definitionInfo?.allowedContext || [];
const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion);
return completeExpression(expressionInput, context, []).map(item =>
mapExpressionCompletionItem(item, currentInput[relCharPos])
);
try {
return completeExpression(expressionInput, context, [], validatorFunctions).map(item =>
mapExpressionCompletionItem(item, currentInput[relCharPos])
);
} catch (e: any) {
error(`Error while completing expression: '${e?.message || "<no details>"}'`);
return [];
}
}
}
@@ -0,0 +1,13 @@
import {data, wellKnownFunctions} from "@github/actions-expressions";
// Custom implementations for standard actions-expression functions used during validation and auto-completion.
// For example, for fromJson we'll most likely not have a valid input. In order to not throw, we'll always
// return an empty dictionary.
export const validatorFunctions = new Map(
Object.entries({
fromjson: {
...wellKnownFunctions.fromjson,
call: () => new data.Dictionary()
}
})
);
@@ -21,4 +21,12 @@ describe("getPositionFromCursor", () => {
expect(position).toEqual({line: 0, character: 0});
expect(newDoc.getText()).toEqual("on: push\njobs:");
});
it("handles a cursor in the middle of the document", () => {
const input = "on: push\n jobs|:\n build:";
const [newDoc, position] = getPositionFromCursor(input);
expect(position).toEqual({line: 1, character: 6});
expect(newDoc.getText()).toEqual("on: push\n jobs:\n build:");
});
});
+11 -10
View File
@@ -1,5 +1,5 @@
import {TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range";
import {Range} from "vscode-languageserver-types";
import {Position as TokenPosition, TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range";
import {Position, Range} from "vscode-languageserver-types";
export function mapRange(range: TokenRange | undefined): Range {
if (!range) {
@@ -16,13 +16,14 @@ export function mapRange(range: TokenRange | undefined): Range {
}
return {
start: {
line: range.start[0] - 1,
character: range.start[1] - 1
},
end: {
line: range.end[0] - 1,
character: range.end[1] - 1
}
start: mapPosition(range.start),
end: mapPosition(range.end)
};
}
export function mapPosition(position: TokenPosition): Position {
return {
line: position[0] - 1,
character: position[1] - 1
};
}
+2 -20
View File
@@ -1,11 +1,4 @@
import {
data,
Evaluator,
ExpressionEvaluationError,
Lexer,
Parser,
wellKnownFunctions
} from "@github/actions-expressions";
import {Evaluator, ExpressionEvaluationError, Lexer, Parser} from "@github/actions-expressions";
import {Expr} from "@github/actions-expressions/ast";
import {
convertWorkflowTemplate,
@@ -29,6 +22,7 @@ import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {AccessError, wrapDictionary} from "./expression-validation/error-dictionary";
import {validatorFunctions} from "./expression-validation/functions";
import {error} from "./log";
import {nullTrace} from "./nulltrace";
import {findToken} from "./utils/find-token";
@@ -231,15 +225,3 @@ async function validateExpression(
}
}
}
// Custom implementations for standard actions-expression functions used during validation. For example,
// for fromJson we'll most likely not have a valid input. In order to not throw, we'll always return an
// empty dictionary.
const validatorFunctions = new Map(
Object.entries({
fromjson: {
...wellKnownFunctions.fromjson,
call: () => new data.Dictionary()
}
})
);