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 {Dictionary} from "./data/dictionary";
|
||||
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";
|
||||
|
||||
const testContext = new Dictionary(
|
||||
@@ -22,11 +23,21 @@ const testContext = new Dictionary(
|
||||
},
|
||||
{
|
||||
key: "github",
|
||||
value: new DescriptionDictionary({
|
||||
key: "actor",
|
||||
value: new StringData(""),
|
||||
description: "The name of the person or app that initiated the workflow. For example, octocat."
|
||||
})
|
||||
value: new DescriptionDictionary(
|
||||
{
|
||||
key: "actor",
|
||||
value: new StringData(""),
|
||||
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",
|
||||
@@ -50,11 +61,11 @@ const testContext = new Dictionary(
|
||||
|
||||
const testFunctions: FunctionInfo[] = [];
|
||||
|
||||
const testComplete = (input: string): CompletionItem[] => {
|
||||
const testComplete = (input: string, functions?: Map<string, FunctionDefinition>): CompletionItem[] => {
|
||||
const pos = input.indexOf("|");
|
||||
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;
|
||||
};
|
||||
@@ -75,6 +86,7 @@ describe("auto-complete", () => {
|
||||
expect(testComplete("to")).toContainEqual(expected);
|
||||
expect(testComplete("toJs")).toContainEqual(expected);
|
||||
expect(testComplete("1 == toJS")).toContainEqual(expected);
|
||||
expect(testComplete("1 == (toJS")).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", () => {
|
||||
it("provides suggestions for env", () => {
|
||||
it("provides suggestions for top-level context", () => {
|
||||
const expected = completionItems("BAR_TEST", "FOO");
|
||||
expect(testComplete("env.X")).toEqual(expected);
|
||||
expect(testComplete("1 == env.F")).toEqual(expected);
|
||||
expect(testComplete("env.")).toEqual(expected);
|
||||
expect(testComplete("env.FOO")).toEqual(expected);
|
||||
expect(testComplete("(env).")).toEqual(expected);
|
||||
});
|
||||
|
||||
it("includes descriptions", () => {
|
||||
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("provides suggestions for nested context", () => {
|
||||
const expected: CompletionItem[] = [
|
||||
{
|
||||
label: "name",
|
||||
function: false,
|
||||
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", () => {
|
||||
@@ -127,6 +169,25 @@ describe("auto-complete", () => {
|
||||
|
||||
it("provides suggestions for contexts in function call", () => {
|
||||
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",
|
||||
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'].",
|
||||
expected: [
|
||||
|
||||
@@ -3,7 +3,7 @@ import {Dictionary, isDictionary} from "./data/dictionary";
|
||||
import {ExpressionData} from "./data/expressiondata";
|
||||
import {Evaluator} from "./evaluator";
|
||||
import {wellKnownFunctions} from "./funcs";
|
||||
import {FunctionInfo} from "./funcs/info";
|
||||
import {FunctionDefinition, FunctionInfo} from "./funcs/info";
|
||||
import {Lexer, Token, TokenType} from "./lexer";
|
||||
import {Parser} from "./parser";
|
||||
|
||||
@@ -13,15 +13,27 @@ export type CompletionItem = {
|
||||
function: boolean;
|
||||
};
|
||||
|
||||
// Complete returns a list of completion items for the given expression.
|
||||
//
|
||||
// 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):
|
||||
// - context.path.inp| or context.path['inp| -- auto-complete context access
|
||||
// - context.path.| or context.path['| -- auto-complete context access
|
||||
// - toJS| -- auto-complete function call or top-level
|
||||
// - | -- auto-complete function call or top-level context access
|
||||
export function complete(input: string, context: Dictionary, extensionFunctions: FunctionInfo[]): CompletionItem[] {
|
||||
/**
|
||||
* Complete returns a list of completion items for the given expression.
|
||||
* 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):
|
||||
* - context.path.inp| or context.path['inp| -- auto-complete context access
|
||||
* - context.path.| or context.path['| -- auto-complete context access
|
||||
* - toJS| -- auto-complete function call or top-level
|
||||
* - | -- auto-complete function call or top-level context access
|
||||
*
|
||||
* @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
|
||||
const lexer = new Lexer(input);
|
||||
const lexResult = lexer.lex();
|
||||
@@ -70,7 +82,7 @@ export function complete(input: string, context: Dictionary, extensionFunctions:
|
||||
);
|
||||
const expr = p.parse();
|
||||
|
||||
const ev = new Evaluator(expr, context);
|
||||
const ev = new Evaluator(expr, context, functions);
|
||||
const result = ev.evaluate();
|
||||
|
||||
return contextKeys(result);
|
||||
@@ -122,9 +134,24 @@ function completionItemFromContext(pair: DescriptionPair): CompletionItem {
|
||||
export function trimTokenVector(tokenVector: Token[]): Token[] {
|
||||
let tokenIdx = tokenVector.length;
|
||||
|
||||
let openParen = 0;
|
||||
|
||||
while (tokenIdx > 0) {
|
||||
const token = tokenVector[tokenIdx - 1];
|
||||
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.DOT:
|
||||
case TokenType.EOF:
|
||||
|
||||
Reference in New Issue
Block a user