From eafbcde86a29cba88e731bd46cf81c062ca23560 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Wed, 14 Dec 2022 16:10:31 -0500 Subject: [PATCH 1/3] Improve auto-completion for expression functions --- actions-languageservice/package.json | 3 +- .../src/complete.expressions.test.ts | 34 +++++++++++++++++++ actions-languageservice/src/complete.ts | 10 ++++-- 3 files changed, 44 insertions(+), 3 deletions(-) diff --git a/actions-languageservice/package.json b/actions-languageservice/package.json index e441757..d7ec8ad 100644 --- a/actions-languageservice/package.json +++ b/actions-languageservice/package.json @@ -39,6 +39,7 @@ }, "dependencies": { "@github/actions-workflow-parser": "*", + "@github/actions-expressions": "*", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" @@ -58,4 +59,4 @@ "ts-jest": "^29.0.3", "typescript": "^4.8.4" } -} +} \ No newline at end of file diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index 6af1866..be97f15 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -1,4 +1,5 @@ import {data} from "@github/actions-expressions"; +import {CompletionItemKind} from "vscode-languageserver-types"; import {complete, getExpressionInput} from "./complete"; import {ContextProviderConfig} from "./context-providers/config"; import {registerLogger} from "./log"; @@ -632,4 +633,37 @@ jobs: expect(result.map(x => x.label)).toEqual(["color"]); }); }); + + it("context completion items include kind and insert text", async () => { + const input = ` + on: push + + jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: echo \${{ | }}.txt + `; + + const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); + + // Built-in function + const toJSON = result.find(x => x.label === "toJson"); + expect(toJSON).toBeDefined(); + expect(toJSON!.kind).toBe(CompletionItemKind.Function); + expect(toJSON!.insertText).toBe("toJson()"); + + // Function from context + const hashFiles = result.find(x => x.label === "hashFiles"); + expect(hashFiles).toBeDefined(); + expect(hashFiles!.kind).toBe(CompletionItemKind.Function); + expect(hashFiles!.insertText).toBe("hashFiles()"); + + // Not a function + const github = result.find(x => x.label === "github"); + expect(github).toBeDefined(); + expect(github!.kind).toBe(CompletionItemKind.Variable); + expect(github!.insertText).toBeUndefined(); + }); }); diff --git a/actions-languageservice/src/complete.ts b/actions-languageservice/src/complete.ts index cc714ab..b675281 100644 --- a/actions-languageservice/src/complete.ts +++ b/actions-languageservice/src/complete.ts @@ -9,7 +9,7 @@ import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/map import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types"; import {File} from "@github/actions-workflow-parser/workflows/file"; import {Position, TextDocument} from "vscode-languageserver-textdocument"; -import {CompletionItem, CompletionItemTag, Range, TextEdit} from "vscode-languageserver-types"; +import {CompletionItem, CompletionItemKind, CompletionItemTag, Range, TextEdit} from "vscode-languageserver-types"; import {ContextProviderConfig} from "./context-providers/config"; import {getContext, Mode} from "./context-providers/default"; import {getWorkflowContext, WorkflowContext} from "./context/workflow-context"; @@ -95,7 +95,13 @@ export async function complete( const allowedContext = getAllowedContext(token, parent); const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion); - return completeExpression(expressionInput, context, []); + return completeExpression(expressionInput, context, []).map(item => { + return { + label: item.label, + insertText: item.function ? item.label + "()" : undefined, + kind: item.function ? CompletionItemKind.Function : CompletionItemKind.Variable + }; + }); } } From b3c97c4c0ff6277e70417e13164aac836e95320f Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Thu, 15 Dec 2022 10:33:57 -0500 Subject: [PATCH 2/3] Bump expressions package version --- package-lock.json | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 3553df5..5afb05d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -43,6 +43,7 @@ "version": "0.1.55", "license": "MIT", "dependencies": { + "@github/actions-expressions": "*", "@github/actions-workflow-parser": "*", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", @@ -683,9 +684,9 @@ "dev": true }, "node_modules/@github/actions-expressions": { - "version": "0.0.7", - "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.7/deed2be11b9f74791730303b6e33f9aca701d6a7", - "integrity": "sha512-PG8dQUafOckk/ny5O1wcUcfml5IZM8REoi7WBBY1HfpnRR7mL7gqTczQEmQWNTpoHb9f1VQK47y2hBZTOg9Y7g==", + "version": "0.0.8", + "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.8/055f455fcb0ed907f52c349b9385728aeed38f9a", + "integrity": "sha512-SPuGfnjgKAbMzJNCk4sZPAK2bxHlRXDJCk6vrwIi9VJL16Eh1YyiyM4fU9kV5EXEE5AV9aPFlZAPXu6t0mmBNw==", "license": "MIT", "engines": { "node": ">= 16" @@ -13500,9 +13501,9 @@ "dev": true }, "@github/actions-expressions": { - "version": "0.0.7", - "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.7/deed2be11b9f74791730303b6e33f9aca701d6a7", - "integrity": "sha512-PG8dQUafOckk/ny5O1wcUcfml5IZM8REoi7WBBY1HfpnRR7mL7gqTczQEmQWNTpoHb9f1VQK47y2hBZTOg9Y7g==" + "version": "0.0.8", + "resolved": "https://npm.pkg.github.com/download/@github/actions-expressions/0.0.8/055f455fcb0ed907f52c349b9385728aeed38f9a", + "integrity": "sha512-SPuGfnjgKAbMzJNCk4sZPAK2bxHlRXDJCk6vrwIi9VJL16Eh1YyiyM4fU9kV5EXEE5AV9aPFlZAPXu6t0mmBNw==" }, "@github/actions-languageserver": { "version": "file:actions-languageserver", @@ -13524,6 +13525,7 @@ "@github/actions-languageservice": { "version": "file:actions-languageservice", "requires": { + "@github/actions-expressions": "*", "@github/actions-workflow-parser": "*", "@types/jest": "^29.0.3", "jest": "^29.0.3", From 9130ef0c6510e8fa68687b446df8019d9d81bb29 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Thu, 15 Dec 2022 10:48:28 -0500 Subject: [PATCH 3/3] Avoid inserting unnecessary parentheses after functions --- .../src/complete.expressions.test.ts | 16 ++++++++++++ actions-languageservice/src/complete.ts | 25 +++++++++++++------ 2 files changed, 34 insertions(+), 7 deletions(-) diff --git a/actions-languageservice/src/complete.expressions.test.ts b/actions-languageservice/src/complete.expressions.test.ts index a098a9d..6b5e41b 100644 --- a/actions-languageservice/src/complete.expressions.test.ts +++ b/actions-languageservice/src/complete.expressions.test.ts @@ -706,4 +706,20 @@ jobs: expect(github!.kind).toBe(CompletionItemKind.Variable); expect(github!.insertText).toBeUndefined(); }); + + it("function parentheses are not inserted when parentheses already exist", async () => { + const input = ` + on: push + + jobs: + test: + runs-on: ubuntu-latest + steps: + - run: echo \${{ toJS|(github.event) }} + `; + + const result = await complete(...getPositionFromCursor(input), undefined, contextProviderConfig); + + expect(result.find(x => x.label === "toJson")!.insertText).toBe("toJson"); + }); }); diff --git a/actions-languageservice/src/complete.ts b/actions-languageservice/src/complete.ts index cecfb5b..0477b19 100644 --- a/actions-languageservice/src/complete.ts +++ b/actions-languageservice/src/complete.ts @@ -1,4 +1,5 @@ import {complete as completeExpression} from "@github/actions-expressions"; +import {CompletionItem as ExpressionCompletionItem} from "@github/actions-expressions/completion"; import {convertWorkflowTemplate, isSequence, isString, parseWorkflow} from "@github/actions-workflow-parser"; import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert"; import {DefinitionType} from "@github/actions-workflow-parser/templates/schema/definition-type"; @@ -87,13 +88,9 @@ export async function complete( const allowedContext = token.definitionInfo?.allowedContext || []; const context = await getContext(allowedContext, contextProviderConfig, workflowContext, Mode.Completion); - return completeExpression(expressionInput, context, []).map(item => { - return { - label: item.label, - insertText: item.function ? item.label + "()" : undefined, - kind: item.function ? CompletionItemKind.Function : CompletionItemKind.Variable - }; - }); + return completeExpression(expressionInput, context, []).map(item => + mapExpressionCompletionItem(item, currentInput[relCharPos]) + ); } } @@ -198,3 +195,17 @@ function filterAndSortCompletionOptions(options: Value[], existingValues?: Set a.label.localeCompare(b.label)); return options; } + +function mapExpressionCompletionItem(item: ExpressionCompletionItem, charAfterPos: string): CompletionItem { + let insertText: string | undefined; + // Insert parentheses if the cursor is after a function + // and the function does not have any parantheses already + if (item.function) { + insertText = charAfterPos === "(" ? item.label : item.label + "()"; + } + return { + label: item.label, + insertText: insertText, + kind: item.function ? CompletionItemKind.Function : CompletionItemKind.Variable + }; +}