Fix a bunch of auto-complete issues
This commit is contained in:
@@ -3,7 +3,8 @@ import {DescriptionDictionary} from "./completion/descriptionDictionary";
|
|||||||
import {BooleanData} from "./data/boolean";
|
import {BooleanData} from "./data/boolean";
|
||||||
import {Dictionary} from "./data/dictionary";
|
import {Dictionary} from "./data/dictionary";
|
||||||
import {StringData} from "./data/string";
|
import {StringData} from "./data/string";
|
||||||
import {FunctionInfo} from "./funcs/info";
|
import {wellKnownFunctions} from "./funcs";
|
||||||
|
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
|
||||||
import {Lexer, TokenType} from "./lexer";
|
import {Lexer, TokenType} from "./lexer";
|
||||||
|
|
||||||
const testContext = new Dictionary(
|
const testContext = new Dictionary(
|
||||||
@@ -22,11 +23,21 @@ const testContext = new Dictionary(
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "github",
|
key: "github",
|
||||||
value: new DescriptionDictionary({
|
value: new DescriptionDictionary(
|
||||||
|
{
|
||||||
key: "actor",
|
key: "actor",
|
||||||
value: new StringData(""),
|
value: new StringData(""),
|
||||||
description: "The name of the person or app that initiated the workflow. For example, octocat."
|
description: "The name of the person or app that initiated the workflow. For example, octocat."
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: "inputs",
|
||||||
|
value: new DescriptionDictionary({
|
||||||
|
key: "name",
|
||||||
|
value: new StringData("monalisa"),
|
||||||
|
description: "The name of a person"
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: "secrets",
|
key: "secrets",
|
||||||
@@ -50,11 +61,11 @@ const testContext = new Dictionary(
|
|||||||
|
|
||||||
const testFunctions: FunctionInfo[] = [];
|
const testFunctions: FunctionInfo[] = [];
|
||||||
|
|
||||||
const testComplete = (input: string): CompletionItem[] => {
|
const testComplete = (input: string, functions?: Map<string, FunctionDefinition>): CompletionItem[] => {
|
||||||
const pos = input.indexOf("|");
|
const pos = input.indexOf("|");
|
||||||
input = input.replace("|", "");
|
input = input.replace("|", "");
|
||||||
|
|
||||||
const results = complete(input.slice(0, pos >= 0 ? pos : input.length), testContext, testFunctions);
|
const results = complete(input.slice(0, pos >= 0 ? pos : input.length), testContext, testFunctions, functions);
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
@@ -75,6 +86,7 @@ describe("auto-complete", () => {
|
|||||||
expect(testComplete("to")).toContainEqual(expected);
|
expect(testComplete("to")).toContainEqual(expected);
|
||||||
expect(testComplete("toJs")).toContainEqual(expected);
|
expect(testComplete("toJs")).toContainEqual(expected);
|
||||||
expect(testComplete("1 == toJS")).toContainEqual(expected);
|
expect(testComplete("1 == toJS")).toContainEqual(expected);
|
||||||
|
expect(testComplete("1 == (toJS")).toContainEqual(expected);
|
||||||
expect(testComplete("toJS| == 1")).toContainEqual(expected);
|
expect(testComplete("toJS| == 1")).toContainEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -92,21 +104,51 @@ describe("auto-complete", () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe("functions", () => {
|
||||||
|
it("uses provided function definitions", () => {
|
||||||
|
expect(
|
||||||
|
testComplete(
|
||||||
|
"fromJson('invalid').|",
|
||||||
|
new Map(
|
||||||
|
Object.entries({
|
||||||
|
fromjson: {
|
||||||
|
...wellKnownFunctions.fromjson,
|
||||||
|
call: () =>
|
||||||
|
new Dictionary({
|
||||||
|
key: "foo",
|
||||||
|
value: new StringData("bar")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
)
|
||||||
|
)
|
||||||
|
).toEqual<CompletionItem[]>([{label: "foo", function: false}]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe("for contexts", () => {
|
describe("for contexts", () => {
|
||||||
it("provides suggestions for env", () => {
|
it("provides suggestions for top-level context", () => {
|
||||||
const expected = completionItems("BAR_TEST", "FOO");
|
const expected = completionItems("BAR_TEST", "FOO");
|
||||||
expect(testComplete("env.X")).toEqual(expected);
|
expect(testComplete("env.X")).toEqual(expected);
|
||||||
expect(testComplete("1 == env.F")).toEqual(expected);
|
expect(testComplete("1 == env.F")).toEqual(expected);
|
||||||
expect(testComplete("env.")).toEqual(expected);
|
expect(testComplete("env.")).toEqual(expected);
|
||||||
expect(testComplete("env.FOO")).toEqual(expected);
|
expect(testComplete("env.FOO")).toEqual(expected);
|
||||||
|
expect(testComplete("(env).")).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("includes descriptions", () => {
|
it("provides suggestions for nested context", () => {
|
||||||
expect(testComplete("github.")).toContainEqual<CompletionItem>({
|
const expected: CompletionItem[] = [
|
||||||
label: "actor",
|
{
|
||||||
|
label: "name",
|
||||||
function: false,
|
function: false,
|
||||||
description: "The name of the person or app that initiated the workflow. For example, octocat."
|
description: "The name of a person"
|
||||||
});
|
}
|
||||||
|
];
|
||||||
|
expect(testComplete("github.inputs.|")).toEqual(expected);
|
||||||
|
expect(testComplete("(github).inputs.|")).toEqual(expected);
|
||||||
|
expect(testComplete("(github.inputs).|")).toEqual(expected);
|
||||||
|
expect(testComplete("'test' == github.inputs.|")).toEqual(expected);
|
||||||
|
expect(testComplete("github.inputs.| == 'monalisa'")).toEqual(expected);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("provides suggestions for secrets", () => {
|
it("provides suggestions for secrets", () => {
|
||||||
@@ -127,6 +169,25 @@ describe("auto-complete", () => {
|
|||||||
|
|
||||||
it("provides suggestions for contexts in function call", () => {
|
it("provides suggestions for contexts in function call", () => {
|
||||||
expect(testComplete("toJSON(env.|)")).toEqual(completionItems("BAR_TEST", "FOO"));
|
expect(testComplete("toJSON(env.|)")).toEqual(completionItems("BAR_TEST", "FOO"));
|
||||||
|
expect(testComplete("toJSON(secrets.")).toEqual(completionItems("AWS_TOKEN"));
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with descriptions", () => {
|
||||||
|
it("top-level", () => {
|
||||||
|
expect(testComplete("github.")).toContainEqual<CompletionItem>({
|
||||||
|
label: "actor",
|
||||||
|
function: false,
|
||||||
|
description: "The name of the person or app that initiated the workflow. For example, octocat."
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("nested", () => {
|
||||||
|
expect(testComplete("github.inputs.")).toContainEqual<CompletionItem>({
|
||||||
|
label: "name",
|
||||||
|
function: false,
|
||||||
|
description: "The name of a person"
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -152,6 +213,21 @@ describe("trimTokenVector", () => {
|
|||||||
input: "github.mona == github.act",
|
input: "github.mona == github.act",
|
||||||
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF]
|
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.IDENTIFIER, TokenType.EOF]
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
input: "github.mona == (github).act",
|
||||||
|
expected: [
|
||||||
|
TokenType.LEFT_PAREN,
|
||||||
|
TokenType.IDENTIFIER,
|
||||||
|
TokenType.RIGHT_PAREN,
|
||||||
|
TokenType.DOT,
|
||||||
|
TokenType.IDENTIFIER,
|
||||||
|
TokenType.EOF
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: "github.mona == (github.",
|
||||||
|
expected: [TokenType.IDENTIFIER, TokenType.DOT, TokenType.EOF]
|
||||||
|
},
|
||||||
{
|
{
|
||||||
input: "github['test'].",
|
input: "github['test'].",
|
||||||
expected: [
|
expected: [
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ import {Dictionary, isDictionary} from "./data/dictionary";
|
|||||||
import {ExpressionData} from "./data/expressiondata";
|
import {ExpressionData} from "./data/expressiondata";
|
||||||
import {Evaluator} from "./evaluator";
|
import {Evaluator} from "./evaluator";
|
||||||
import {wellKnownFunctions} from "./funcs";
|
import {wellKnownFunctions} from "./funcs";
|
||||||
import {FunctionInfo} from "./funcs/info";
|
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
|
||||||
import {Lexer, Token, TokenType} from "./lexer";
|
import {Lexer, Token, TokenType} from "./lexer";
|
||||||
import {Parser} from "./parser";
|
import {Parser} from "./parser";
|
||||||
|
|
||||||
@@ -13,15 +13,27 @@ export type CompletionItem = {
|
|||||||
function: boolean;
|
function: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Complete returns a list of completion items for the given expression.
|
/**
|
||||||
//
|
* Complete returns a list of completion items for the given expression.
|
||||||
// The main functionality is auto-completing functions and context access:
|
* The main functionality is auto-completing functions and context access:
|
||||||
// We can only provide assistance if the input is in one of the following forms (with | denoting the cursor position):
|
* We can only provide assistance if the input is in one of the following forms (with | denoting the cursor position):
|
||||||
// - context.path.inp| or context.path['inp| -- auto-complete context access
|
* - context.path.inp| or context.path['inp| -- auto-complete context access
|
||||||
// - context.path.| or context.path['| -- auto-complete context access
|
* - context.path.| or context.path['| -- auto-complete context access
|
||||||
// - toJS| -- auto-complete function call or top-level
|
* - toJS| -- auto-complete function call or top-level
|
||||||
// - | -- auto-complete function call or top-level context access
|
* - | -- auto-complete function call or top-level context access
|
||||||
export function complete(input: string, context: Dictionary, extensionFunctions: FunctionInfo[]): CompletionItem[] {
|
*
|
||||||
|
* @param input Input expression
|
||||||
|
* @param context Context available for the expression
|
||||||
|
* @param extensionFunctions List of functions available
|
||||||
|
* @param functions Optional map of functions to use during evaluation
|
||||||
|
* @returns Array of completion items
|
||||||
|
*/
|
||||||
|
export function complete(
|
||||||
|
input: string,
|
||||||
|
context: Dictionary,
|
||||||
|
extensionFunctions: FunctionInfo[],
|
||||||
|
functions?: Map<string, FunctionDefinition>
|
||||||
|
): CompletionItem[] {
|
||||||
// Lex
|
// Lex
|
||||||
const lexer = new Lexer(input);
|
const lexer = new Lexer(input);
|
||||||
const lexResult = lexer.lex();
|
const lexResult = lexer.lex();
|
||||||
@@ -70,7 +82,7 @@ export function complete(input: string, context: Dictionary, extensionFunctions:
|
|||||||
);
|
);
|
||||||
const expr = p.parse();
|
const expr = p.parse();
|
||||||
|
|
||||||
const ev = new Evaluator(expr, context);
|
const ev = new Evaluator(expr, context, functions);
|
||||||
const result = ev.evaluate();
|
const result = ev.evaluate();
|
||||||
|
|
||||||
return contextKeys(result);
|
return contextKeys(result);
|
||||||
@@ -122,9 +134,24 @@ function completionItemFromContext(pair: DescriptionPair): CompletionItem {
|
|||||||
export function trimTokenVector(tokenVector: Token[]): Token[] {
|
export function trimTokenVector(tokenVector: Token[]): Token[] {
|
||||||
let tokenIdx = tokenVector.length;
|
let tokenIdx = tokenVector.length;
|
||||||
|
|
||||||
|
let openParen = 0;
|
||||||
|
|
||||||
while (tokenIdx > 0) {
|
while (tokenIdx > 0) {
|
||||||
const token = tokenVector[tokenIdx - 1];
|
const token = tokenVector[tokenIdx - 1];
|
||||||
switch (token.type) {
|
switch (token.type) {
|
||||||
|
case TokenType.LEFT_PAREN:
|
||||||
|
if (openParen == 0) {
|
||||||
|
// Encounterend an open parenthesis witout a closing first, stop here
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
tokenIdx--;
|
||||||
|
continue;
|
||||||
|
|
||||||
|
case TokenType.RIGHT_PAREN:
|
||||||
|
openParen++;
|
||||||
|
tokenIdx--;
|
||||||
|
continue;
|
||||||
|
|
||||||
case TokenType.IDENTIFIER:
|
case TokenType.IDENTIFIER:
|
||||||
case TokenType.DOT:
|
case TokenType.DOT:
|
||||||
case TokenType.EOF:
|
case TokenType.EOF:
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ describe("expressions", () => {
|
|||||||
expect(test("${{ github.| == 'test' }}")).toBe(" github.");
|
expect(test("${{ github.| == 'test' }}")).toBe(" github.");
|
||||||
expect(test("test ${{ github.| == 'test' }}")).toBe(" github.");
|
expect(test("test ${{ github.| == 'test' }}")).toBe(" github.");
|
||||||
expect(test("${{ vars }} ${{ gh |}}")).toBe(" gh ");
|
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", () => {
|
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 () => {
|
it("contains description", async () => {
|
||||||
const input = "run-name: ${{ github.| }}";
|
const input = "run-name: ${{ github.| }}";
|
||||||
const result = await complete(...getPositionFromCursor(input), undefined, undefined);
|
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 () => {
|
it("nested auto-complete", async () => {
|
||||||
const input = "run-name: ${{ github.| }}";
|
const input = "run-name: ${{ github.| }}";
|
||||||
const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig);
|
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 {ContextProviderConfig} from "./context-providers/config";
|
||||||
import {getContext, Mode} from "./context-providers/default";
|
import {getContext, Mode} from "./context-providers/default";
|
||||||
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
|
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
|
||||||
|
import {validatorFunctions} from "./expression-validation/functions";
|
||||||
|
import {error} from "./log";
|
||||||
import {nullTrace} from "./nulltrace";
|
import {nullTrace} from "./nulltrace";
|
||||||
import {findToken} from "./utils/find-token";
|
import {findToken} from "./utils/find-token";
|
||||||
import {guessIndentation} from "./utils/indentation-guesser";
|
import {guessIndentation} from "./utils/indentation-guesser";
|
||||||
@@ -75,13 +77,14 @@ export async function complete(
|
|||||||
|
|
||||||
// Transform the overall position into a node relative position
|
// Transform the overall position into a node relative position
|
||||||
let relCharPos: number = 0;
|
let relCharPos: number = 0;
|
||||||
const lineDiff = newPos.line - token.range!.start[0];
|
const range = mapRange(token.range!);
|
||||||
if (token.range!.start[0] !== token.range!.end[0]) {
|
if (range.start.line !== range.end.line) {
|
||||||
const lines = currentInput.split("\n");
|
const lines = currentInput.split("\n");
|
||||||
|
const lineDiff = newPos.line - range.start.line - 1;
|
||||||
const linesBeforeCusor = lines.slice(0, lineDiff);
|
const linesBeforeCusor = lines.slice(0, lineDiff);
|
||||||
relCharPos = linesBeforeCusor.join("\n").length + 1 + newPos.character;
|
relCharPos = linesBeforeCusor.join("\n").length + newPos.character + 1;
|
||||||
} else {
|
} else {
|
||||||
relCharPos = newPos.character - token.range!.start[1] + 1;
|
relCharPos = newPos.character - range.start.character;
|
||||||
}
|
}
|
||||||
|
|
||||||
const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim();
|
const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim();
|
||||||
@@ -89,9 +92,14 @@ export async function complete(
|
|||||||
const allowedContext = token.definitionInfo?.allowedContext || [];
|
const allowedContext = token.definitionInfo?.allowedContext || [];
|
||||||
const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion);
|
const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion);
|
||||||
|
|
||||||
return completeExpression(expressionInput, context, []).map(item =>
|
try {
|
||||||
|
return completeExpression(expressionInput, context, [], validatorFunctions).map(item =>
|
||||||
mapExpressionCompletionItem(item, currentInput[relCharPos])
|
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(position).toEqual({line: 0, character: 0});
|
||||||
expect(newDoc.getText()).toEqual("on: push\njobs:");
|
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 {Position as TokenPosition, TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range";
|
||||||
import {Range} from "vscode-languageserver-types";
|
import {Position, Range} from "vscode-languageserver-types";
|
||||||
|
|
||||||
export function mapRange(range: TokenRange | undefined): Range {
|
export function mapRange(range: TokenRange | undefined): Range {
|
||||||
if (!range) {
|
if (!range) {
|
||||||
@@ -16,13 +16,14 @@ export function mapRange(range: TokenRange | undefined): Range {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
start: {
|
start: mapPosition(range.start),
|
||||||
line: range.start[0] - 1,
|
end: mapPosition(range.end)
|
||||||
character: range.start[1] - 1
|
};
|
||||||
},
|
|
||||||
end: {
|
|
||||||
line: range.end[0] - 1,
|
|
||||||
character: range.end[1] - 1
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function mapPosition(position: TokenPosition): Position {
|
||||||
|
return {
|
||||||
|
line: position[0] - 1,
|
||||||
|
character: position[1] - 1
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,4 @@
|
|||||||
import {
|
import {Evaluator, ExpressionEvaluationError, Lexer, Parser} from "@github/actions-expressions";
|
||||||
data,
|
|
||||||
Evaluator,
|
|
||||||
ExpressionEvaluationError,
|
|
||||||
Lexer,
|
|
||||||
Parser,
|
|
||||||
wellKnownFunctions
|
|
||||||
} from "@github/actions-expressions";
|
|
||||||
import {Expr} from "@github/actions-expressions/ast";
|
import {Expr} from "@github/actions-expressions/ast";
|
||||||
import {
|
import {
|
||||||
convertWorkflowTemplate,
|
convertWorkflowTemplate,
|
||||||
@@ -29,6 +22,7 @@ import {ContextProviderConfig} from "./context-providers/config";
|
|||||||
import {getContext, Mode} from "./context-providers/default";
|
import {getContext, Mode} from "./context-providers/default";
|
||||||
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
|
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
|
||||||
import {AccessError, wrapDictionary} from "./expression-validation/error-dictionary";
|
import {AccessError, wrapDictionary} from "./expression-validation/error-dictionary";
|
||||||
|
import {validatorFunctions} from "./expression-validation/functions";
|
||||||
import {error} from "./log";
|
import {error} from "./log";
|
||||||
import {nullTrace} from "./nulltrace";
|
import {nullTrace} from "./nulltrace";
|
||||||
import {findToken} from "./utils/find-token";
|
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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -451,7 +451,6 @@ class TemplateReader {
|
|||||||
const allowedContext = definitionInfo.allowedContext;
|
const allowedContext = definitionInfo.allowedContext;
|
||||||
const raw = token.source || token.value;
|
const raw = token.source || token.value;
|
||||||
|
|
||||||
// Check if the value is definitely a literal
|
|
||||||
let startExpression: number = raw.indexOf(OPEN_EXPRESSION);
|
let startExpression: number = raw.indexOf(OPEN_EXPRESSION);
|
||||||
if (startExpression < 0) {
|
if (startExpression < 0) {
|
||||||
// Doesn't contain "${{"
|
// Doesn't contain "${{"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import {isCollection, isDocument, isMap, isPair, isScalar, isSeq, LineCounter, parseDocument, Scalar} from "yaml";
|
import {isCollection, isDocument, isMap, isPair, isScalar, isSeq, LineCounter, parseDocument, Scalar} from "yaml";
|
||||||
import {LinePos} from "yaml/dist/errors";
|
import type {LinePos} from "yaml/dist/errors";
|
||||||
import {NodeBase} from "yaml/dist/nodes/Node";
|
import type {NodeBase} from "yaml/dist/nodes/Node";
|
||||||
import {ObjectReader} from "../templates/object-reader";
|
import {ObjectReader} from "../templates/object-reader";
|
||||||
import {EventType, ParseEvent} from "../templates/parse-event";
|
import {EventType, ParseEvent} from "../templates/parse-event";
|
||||||
import {TemplateContext} from "../templates/template-context";
|
import {TemplateContext} from "../templates/template-context";
|
||||||
@@ -110,14 +110,8 @@ export class YamlObjectReader implements ObjectReader {
|
|||||||
case "boolean":
|
case "boolean":
|
||||||
return new BooleanToken(fileId, range, value, undefined);
|
return new BooleanToken(fileId, range, value, undefined);
|
||||||
case "string": {
|
case "string": {
|
||||||
// If the string is a YAML block string, include the original source
|
|
||||||
let source: string | undefined;
|
let source: string | undefined;
|
||||||
if (
|
if (token.srcToken && "source" in token.srcToken) {
|
||||||
(token.type === "BLOCK_LITERAL" || // | multi-line strings
|
|
||||||
token.type === "BLOCK_FOLDED") && // > multi-line strings
|
|
||||||
token.srcToken &&
|
|
||||||
token.srcToken.type === "block-scalar"
|
|
||||||
) {
|
|
||||||
source = token.srcToken.source;
|
source = token.srcToken.source;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user