Fix a bunch of auto-complete issues
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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:");
|
||||
});
|
||||
});
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user