From b21c0e461db5a3869a30821b843e3c646078d215 Mon Sep 17 00:00:00 2001 From: Laura Yu <60276246+lauraway@users.noreply.github.com> Date: Tue, 28 Feb 2023 10:18:46 -0800 Subject: [PATCH 01/19] Fix Completion bug with null string (#168) --- languageservice/src/complete.test.ts | 13 ++++++++ .../src/utils/expression-detection.ts | 2 +- .../src/templates/template-reader.ts | 31 ++++++++++--------- 3 files changed, 31 insertions(+), 15 deletions(-) diff --git a/languageservice/src/complete.test.ts b/languageservice/src/complete.test.ts index b6c7436..84abd19 100644 --- a/languageservice/src/complete.test.ts +++ b/languageservice/src/complete.test.ts @@ -320,6 +320,19 @@ on: expect(result).toHaveLength(0); }); + it("null strings still give suggestions", async () => { + const input = ` +on: push +jobs: + one: + runs-on: ubuntu-latest + |: + - uses: actions/checkout@v2 +`; + const result = await complete(...getPositionFromCursor(input)); + expect(result).toHaveLength(16); + }); + it("well known mapping keys have descriptions", async () => { const input = ` o| diff --git a/languageservice/src/utils/expression-detection.ts b/languageservice/src/utils/expression-detection.ts index 2242d8a..4406c78 100644 --- a/languageservice/src/utils/expression-detection.ts +++ b/languageservice/src/utils/expression-detection.ts @@ -7,6 +7,6 @@ import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/in export function isPotentiallyExpression(token: TemplateToken): boolean { const isAlwaysExpression = token.definition?.definitionType === DefinitionType.String && (token.definition as StringDefinition).isExpression; - const containsExpression = isString(token) && token.value.indexOf(OPEN_EXPRESSION) >= 0; + const containsExpression = isString(token) && token.value != null && token.value.indexOf(OPEN_EXPRESSION) >= 0; return isAlwaysExpression || containsExpression; } diff --git a/workflow-parser/src/templates/template-reader.ts b/workflow-parser/src/templates/template-reader.ts index 7d5bce0..02df3d7 100644 --- a/workflow-parser/src/templates/template-reader.ts +++ b/workflow-parser/src/templates/template-reader.ts @@ -203,14 +203,15 @@ class TemplateReader { ); // Duplicate - const upperKey = nextKey.value.toUpperCase(); - if (upperKeys[upperKey]) { - this._context.error(nextKey, `'${nextKey.value}' is already defined`); - this.skipValue(); - continue; + if (nextKey.value) { + const upperKey = nextKey.value.toUpperCase(); + if (upperKeys[upperKey]) { + this._context.error(nextKey, `'${nextKey.value}' is already defined`); + this.skipValue(); + continue; + } + upperKeys[upperKey] = true; } - upperKeys[upperKey] = true; - // Well known const nextPropertyDef = this._schema.matchPropertyAndFilter(mappingDefinitions, nextKey.value); if (nextPropertyDef) { @@ -339,13 +340,15 @@ class TemplateReader { ); // Duplicate - const upperKey = nextKey.value.toUpperCase(); - if (upperKeys[upperKey]) { - this._context.error(nextKey, `'${nextKey.value}' is already defined`); - this.skipValue(); - continue; + if (nextKey.value) { + const upperKey = nextKey.value.toUpperCase(); + if (upperKeys[upperKey]) { + this._context.error(nextKey, `'${nextKey.value}' is already defined`); + this.skipValue(); + continue; + } + upperKeys[upperKey] = true; } - upperKeys[upperKey] = true; // Validate this.validate(nextKey, keyDefinition); @@ -443,7 +446,7 @@ class TemplateReader { private parseScalar(token: LiteralToken, definitionInfo: DefinitionInfo): ScalarToken { // Not a string - if (!isString(token)) { + if (!isString(token) || !token.value) { return token; } From 2a420e29f687d5ad4e9ac4c31739da10c42f8c46 Mon Sep 17 00:00:00 2001 From: github-actions Date: Tue, 28 Feb 2023 18:22:11 +0000 Subject: [PATCH 02/19] v0.1.154 --- browser-playground/package.json | 4 ++-- expressions/package.json | 2 +- languageserver/package.json | 6 +++--- languageservice/package.json | 6 +++--- lerna.json | 2 +- package-lock.json | 34 ++++++++++++++++----------------- workflow-parser/package.json | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/browser-playground/package.json b/browser-playground/package.json index d20a4ca..9e99a1a 100644 --- a/browser-playground/package.json +++ b/browser-playground/package.json @@ -1,12 +1,12 @@ { "name": "browser-playground", - "version": "0.1.153", + "version": "0.1.154", "description": "", "private": true, "main": "index.js", "type": "module", "dependencies": { - "@github/actions-languageserver": "^0.1.153", + "@github/actions-languageserver": "^0.1.154", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", diff --git a/expressions/package.json b/expressions/package.json index 4d1f60a..85f2466 100755 --- a/expressions/package.json +++ b/expressions/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-expressions", - "version": "0.1.153", + "version": "0.1.154", "license": "MIT", "type": "module", "source": "./src/index.ts", diff --git a/languageserver/package.json b/languageserver/package.json index e541571..77d4c59 100644 --- a/languageserver/package.json +++ b/languageserver/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageserver", - "version": "0.1.153", + "version": "0.1.154", "description": "Language server for GitHub Actions", "license": "MIT", "type": "module", @@ -38,8 +38,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-languageservice": "^0.1.153", - "@github/actions-workflow-parser": "^0.1.153", + "@github/actions-languageservice": "^0.1.154", + "@github/actions-workflow-parser": "^0.1.154", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", diff --git a/languageservice/package.json b/languageservice/package.json index 203d90e..f84a707 100644 --- a/languageservice/package.json +++ b/languageservice/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageservice", - "version": "0.1.153", + "version": "0.1.154", "description": "Language service for GitHub Actions", "license": "MIT", "type": "module", @@ -39,8 +39,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.153", - "@github/actions-workflow-parser": "^0.1.153", + "@github/actions-expressions": "^0.1.154", + "@github/actions-workflow-parser": "^0.1.154", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" diff --git a/lerna.json b/lerna.json index bbe1c70..0c950c5 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, - "version": "0.1.153" + "version": "0.1.154" } diff --git a/package-lock.json b/package-lock.json index 9e8c02d..1bcf004 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,10 +111,10 @@ } }, "browser-playground": { - "version": "0.1.153", + "version": "0.1.154", "license": "MIT", "dependencies": { - "@github/actions-languageserver": "^0.1.153", + "@github/actions-languageserver": "^0.1.154", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", @@ -135,7 +135,7 @@ }, "expressions": { "name": "@github/actions-expressions", - "version": "0.1.153", + "version": "0.1.154", "license": "MIT", "devDependencies": { "@types/jest": "^29.0.3", @@ -151,11 +151,11 @@ }, "languageserver": { "name": "@github/actions-languageserver", - "version": "0.1.153", + "version": "0.1.154", "license": "MIT", "dependencies": { - "@github/actions-languageservice": "^0.1.153", - "@github/actions-workflow-parser": "^0.1.153", + "@github/actions-languageservice": "^0.1.154", + "@github/actions-workflow-parser": "^0.1.154", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", @@ -176,11 +176,11 @@ }, "languageservice": { "name": "@github/actions-languageservice", - "version": "0.1.153", + "version": "0.1.154", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.153", - "@github/actions-workflow-parser": "^0.1.153", + "@github/actions-expressions": "^0.1.154", + "@github/actions-workflow-parser": "^0.1.154", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" @@ -14265,10 +14265,10 @@ }, "workflow-parser": { "name": "@github/actions-workflow-parser", - "version": "0.1.153", + "version": "0.1.154", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.153", + "@github/actions-expressions": "^0.1.154", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, @@ -14827,8 +14827,8 @@ "@github/actions-languageserver": { "version": "file:languageserver", "requires": { - "@github/actions-languageservice": "^0.1.153", - "@github/actions-workflow-parser": "^0.1.153", + "@github/actions-languageservice": "^0.1.154", + "@github/actions-workflow-parser": "^0.1.154", "@octokit/rest": "^19.0.7", "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", @@ -14845,8 +14845,8 @@ "@github/actions-languageservice": { "version": "file:languageservice", "requires": { - "@github/actions-expressions": "^0.1.153", - "@github/actions-workflow-parser": "^0.1.153", + "@github/actions-expressions": "^0.1.154", + "@github/actions-workflow-parser": "^0.1.154", "@types/jest": "^29.0.3", "jest": "^29.0.3", "prettier": "^2.8.3", @@ -14863,7 +14863,7 @@ "@github/actions-workflow-parser": { "version": "file:workflow-parser", "requires": { - "@github/actions-expressions": "^0.1.153", + "@github/actions-expressions": "^0.1.154", "@types/jest": "^29.0.3", "@typescript-eslint/eslint-plugin": "^5.40.0", "@typescript-eslint/parser": "^5.40.0", @@ -17857,7 +17857,7 @@ "browser-playground": { "version": "file:browser-playground", "requires": { - "@github/actions-languageserver": "^0.1.153", + "@github/actions-languageserver": "^0.1.154", "css-loader": "^6.7.2", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", diff --git a/workflow-parser/package.json b/workflow-parser/package.json index 4bc712e..fb77fa5 100644 --- a/workflow-parser/package.json +++ b/workflow-parser/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-workflow-parser", - "version": "0.1.153", + "version": "0.1.154", "license": "MIT", "type": "module", "source": "./src/index.ts", @@ -40,7 +40,7 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.153", + "@github/actions-expressions": "^0.1.154", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, From 265907bfa09ee4d28bcaf9f442ad44763885a5b9 Mon Sep 17 00:00:00 2001 From: Christopher Schleiden Date: Tue, 28 Feb 2023 15:18:40 -0800 Subject: [PATCH 03/19] Add readme for workflow-parser --- workflow-parser/README.md | 79 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 workflow-parser/README.md diff --git a/workflow-parser/README.md b/workflow-parser/README.md new file mode 100644 index 0000000..5045e00 --- /dev/null +++ b/workflow-parser/README.md @@ -0,0 +1,79 @@ +# actions/workflow-parser + +`@actions/workflow-parser` is a library to parse GitHub Actions [workflows](https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions). + +## Installation + +The [package](https://www.npmjs.com/package/@actions/workflow-parser) contains TypeScript types and compiled ECMAScript modules. + +```bash +npm install @actions/workflow-parser +``` + +## Usage + +The parser is driven by a custom schema defined in [`worflows-v1.0.json`](./src/workflow-v1.0.json). + +### Simple example + +`parseWorkflow` parses the workflow YAML into an intermediate representation and validates that it conforms to the schema. Any parsing errors are returned in the `errors` property of the result context. + +```typescript +var trace: TraceWriter; + +const result = parseWorkflow( + { + name: "test.yaml", + content: `on: push +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo 'hello'` + }, + trace +); +``` + +`convertWorkflowTemplate` then takes that intermediate representation and converts it to a [`WorkflowTemplate`](./src/workflow-template.ts) object, which is a more convenient representation for working with workflows. + +```typescript +const workflowTemplate = await convertWorkflowTemplate(result.context, result.value); + +// workflowTemplate.jobs[0].id === "build" +// workflowTemplate.jobs[0].steps[0].run === "echo 'hello'" +``` + +### Build + +```bash +npm run build +``` + +or to watch for changes + +```bash +npm run watch +``` + +### Test + +```bash +npm test +``` + +or to watch for changes and run tests: + +```bash +npm run test-watch +``` + +### Lint + +```bash +npm run format-check +``` + +## License + +This project is licensed under the terms of the MIT open source license. Please refer to [MIT](../LICENSE) for the full terms. From 5854c87825b2bd768f8ac522cec0e2f3110e3a54 Mon Sep 17 00:00:00 2001 From: Christopher Schleiden Date: Tue, 28 Feb 2023 15:18:55 -0800 Subject: [PATCH 04/19] Adjust package names --- expressions/README.md | 4 ++-- languageserver/README.md | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/expressions/README.md b/expressions/README.md index b2a15ad..2076ada 100644 --- a/expressions/README.md +++ b/expressions/README.md @@ -1,6 +1,6 @@ -# actions-expressions +# actions/expressions -`actions-expressions` is a library to parse and evaluate GitHub Actions [expressions](https://docs.github.com/actions/learn-github-actions/expressions). +`@actions/expressions` is a library to parse and evaluate GitHub Actions [expressions](https://docs.github.com/actions/learn-github-actions/expressions). ## Installation diff --git a/languageserver/README.md b/languageserver/README.md index 9c0cbf3..6698e5c 100644 --- a/languageserver/README.md +++ b/languageserver/README.md @@ -1,4 +1,4 @@ -# actions-languageserver +# actions/languageserver `actions-languageserver` hosts the `actions-languageservice` and makes it available via the [language server protocol](https://microsoft.github.io/language-server-protocol/) (LSP) as a standalone language server. From bb91f52176e170b8c892d28cb6b5bcf947ef399c Mon Sep 17 00:00:00 2001 From: Christopher Schleiden Date: Tue, 28 Feb 2023 15:25:01 -0800 Subject: [PATCH 05/19] Expand language service readme --- languageservice/README.md | 43 ++++++++++++++++++++++++++++++--------- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/languageservice/README.md b/languageservice/README.md index f853365..f10f61c 100644 --- a/languageservice/README.md +++ b/languageservice/README.md @@ -1,4 +1,4 @@ -# actions-languageservice +# actions/languageservice This package contains the logic for the GitHub Actions workflows language server. @@ -22,14 +22,6 @@ The language service features use three sources of information: * _value providers_ which can dynamically add values to the schema, for example, the list of available labels for a repository when validating `runs-on`. * _context providers_ which can dynamically provide available contexts used in [expressions](https://docs.github.com/actions/reference/context-and-expression-syntax-for-github-actions#about-contexts-and-expressions). For example, the contents of the `github.event` context for a given workflow file. -##### Value Providers - -TODO - -##### Context Providers - -TODO - #### Validation Validate a workflow file, returns an array of [`Diagnostic`](https://microsoft.github.io/language-server-protocol/specifications/lsp/3.17/specification/#diagnostic) objects. @@ -60,7 +52,38 @@ const hover = await hover(document, {line: 0, character: 1}); // { contents: { k #### Auto-completion -TODO +```typescript +import {complete} from "@actions/languageservice"; + +const document = { + uri: "file:///path/to/file", + getText: () => "on: \n jobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo hello" +}; + +// Trigger completion for `on: |` +const suggestions = await complete(document, {line: 0, character: 4}); +``` + +will return + +```jsonc +[{ + "documentation": { + "kind": "markdown", + "value": "Runs your workflow when branch protection rules in the workflow repository are changed.", + }, + "label": "branch_protection_rule", + "textEdit": { + "newText": "branch_protection_rule", + "range": { + "end": {"character": 4, "line": 0,}, + "start": {"character": 4, "line": 0}, + }, + }, +}, +//... other events +] +``` ## Contributing From a93db03989c03513125a60ad41d84006c517681d Mon Sep 17 00:00:00 2001 From: Christopher Schleiden Date: Tue, 28 Feb 2023 15:28:13 -0800 Subject: [PATCH 06/19] Add Contribution section for parser readme --- workflow-parser/README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/workflow-parser/README.md b/workflow-parser/README.md index 5045e00..e480668 100644 --- a/workflow-parser/README.md +++ b/workflow-parser/README.md @@ -44,6 +44,14 @@ const workflowTemplate = await convertWorkflowTemplate(result.context, result.va // workflowTemplate.jobs[0].steps[0].run === "echo 'hello'" ``` +## Contributing + +See [CONTRIBUTING.md](../CONTRIBUTING.md) at the root of the repository for general guidelines and recommendations. + +Similar to [`actions/expressions](../expressions/), this project is just one of multiple implementations of the GitHub Actions workflow parser. We therefore cannot accept contributions that significantly modify the schema or the overall behavior of the parser. If you would like to propose changes to Actions itself, please use our [Community Forum](https://github.com/community/community/discussions/categories/actions-and-packages). + +If you do want to contribute, please run [prettier](https://prettier.io/) to format your code and add unit tests as appropriate before submitting your PR. [./testdata](./testdata) contains test cases that all implementations should pass, please also make sure those tests are still passing. + ### Build ```bash From 6bbd3dda95952c0c78aa37045b34fa7b2c0f70a4 Mon Sep 17 00:00:00 2001 From: Christopher Schleiden Date: Wed, 1 Mar 2023 08:19:17 -0800 Subject: [PATCH 07/19] Expand newlines Co-authored-by: Jacob Wallraff --- languageservice/README.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/languageservice/README.md b/languageservice/README.md index f10f61c..76a6072 100644 --- a/languageservice/README.md +++ b/languageservice/README.md @@ -57,7 +57,12 @@ import {complete} from "@actions/languageservice"; const document = { uri: "file:///path/to/file", - getText: () => "on: \n jobs:\n build:\n runs-on: ubuntu-latest\n steps:\n - run: echo hello" + getText: () => `on: +jobs: + build: + runs-on: ubuntu-latest + steps: + - run: echo hello` }; // Trigger completion for `on: |` From 4b8613da02a367070fcc6b6fe275ee42848daca3 Mon Sep 17 00:00:00 2001 From: github-actions Date: Wed, 1 Mar 2023 16:22:25 +0000 Subject: [PATCH 08/19] v0.1.155 --- browser-playground/package.json | 4 ++-- expressions/package.json | 2 +- languageserver/package.json | 6 +++--- languageservice/package.json | 6 +++--- lerna.json | 2 +- package-lock.json | 34 ++++++++++++++++----------------- workflow-parser/package.json | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/browser-playground/package.json b/browser-playground/package.json index 9e99a1a..6d712dc 100644 --- a/browser-playground/package.json +++ b/browser-playground/package.json @@ -1,12 +1,12 @@ { "name": "browser-playground", - "version": "0.1.154", + "version": "0.1.155", "description": "", "private": true, "main": "index.js", "type": "module", "dependencies": { - "@github/actions-languageserver": "^0.1.154", + "@github/actions-languageserver": "^0.1.155", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", diff --git a/expressions/package.json b/expressions/package.json index 85f2466..f323aff 100755 --- a/expressions/package.json +++ b/expressions/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-expressions", - "version": "0.1.154", + "version": "0.1.155", "license": "MIT", "type": "module", "source": "./src/index.ts", diff --git a/languageserver/package.json b/languageserver/package.json index 77d4c59..1c44bec 100644 --- a/languageserver/package.json +++ b/languageserver/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageserver", - "version": "0.1.154", + "version": "0.1.155", "description": "Language server for GitHub Actions", "license": "MIT", "type": "module", @@ -38,8 +38,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-languageservice": "^0.1.154", - "@github/actions-workflow-parser": "^0.1.154", + "@github/actions-languageservice": "^0.1.155", + "@github/actions-workflow-parser": "^0.1.155", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", diff --git a/languageservice/package.json b/languageservice/package.json index f84a707..df13f14 100644 --- a/languageservice/package.json +++ b/languageservice/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageservice", - "version": "0.1.154", + "version": "0.1.155", "description": "Language service for GitHub Actions", "license": "MIT", "type": "module", @@ -39,8 +39,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.154", - "@github/actions-workflow-parser": "^0.1.154", + "@github/actions-expressions": "^0.1.155", + "@github/actions-workflow-parser": "^0.1.155", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" diff --git a/lerna.json b/lerna.json index 0c950c5..4de6c05 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, - "version": "0.1.154" + "version": "0.1.155" } diff --git a/package-lock.json b/package-lock.json index 1bcf004..3ea0e11 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,10 +111,10 @@ } }, "browser-playground": { - "version": "0.1.154", + "version": "0.1.155", "license": "MIT", "dependencies": { - "@github/actions-languageserver": "^0.1.154", + "@github/actions-languageserver": "^0.1.155", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", @@ -135,7 +135,7 @@ }, "expressions": { "name": "@github/actions-expressions", - "version": "0.1.154", + "version": "0.1.155", "license": "MIT", "devDependencies": { "@types/jest": "^29.0.3", @@ -151,11 +151,11 @@ }, "languageserver": { "name": "@github/actions-languageserver", - "version": "0.1.154", + "version": "0.1.155", "license": "MIT", "dependencies": { - "@github/actions-languageservice": "^0.1.154", - "@github/actions-workflow-parser": "^0.1.154", + "@github/actions-languageservice": "^0.1.155", + "@github/actions-workflow-parser": "^0.1.155", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", @@ -176,11 +176,11 @@ }, "languageservice": { "name": "@github/actions-languageservice", - "version": "0.1.154", + "version": "0.1.155", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.154", - "@github/actions-workflow-parser": "^0.1.154", + "@github/actions-expressions": "^0.1.155", + "@github/actions-workflow-parser": "^0.1.155", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" @@ -14265,10 +14265,10 @@ }, "workflow-parser": { "name": "@github/actions-workflow-parser", - "version": "0.1.154", + "version": "0.1.155", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.154", + "@github/actions-expressions": "^0.1.155", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, @@ -14827,8 +14827,8 @@ "@github/actions-languageserver": { "version": "file:languageserver", "requires": { - "@github/actions-languageservice": "^0.1.154", - "@github/actions-workflow-parser": "^0.1.154", + "@github/actions-languageservice": "^0.1.155", + "@github/actions-workflow-parser": "^0.1.155", "@octokit/rest": "^19.0.7", "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", @@ -14845,8 +14845,8 @@ "@github/actions-languageservice": { "version": "file:languageservice", "requires": { - "@github/actions-expressions": "^0.1.154", - "@github/actions-workflow-parser": "^0.1.154", + "@github/actions-expressions": "^0.1.155", + "@github/actions-workflow-parser": "^0.1.155", "@types/jest": "^29.0.3", "jest": "^29.0.3", "prettier": "^2.8.3", @@ -14863,7 +14863,7 @@ "@github/actions-workflow-parser": { "version": "file:workflow-parser", "requires": { - "@github/actions-expressions": "^0.1.154", + "@github/actions-expressions": "^0.1.155", "@types/jest": "^29.0.3", "@typescript-eslint/eslint-plugin": "^5.40.0", "@typescript-eslint/parser": "^5.40.0", @@ -17857,7 +17857,7 @@ "browser-playground": { "version": "file:browser-playground", "requires": { - "@github/actions-languageserver": "^0.1.154", + "@github/actions-languageserver": "^0.1.155", "css-loader": "^6.7.2", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", diff --git a/workflow-parser/package.json b/workflow-parser/package.json index fb77fa5..77282b8 100644 --- a/workflow-parser/package.json +++ b/workflow-parser/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-workflow-parser", - "version": "0.1.154", + "version": "0.1.155", "license": "MIT", "type": "module", "source": "./src/index.ts", @@ -40,7 +40,7 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.154", + "@github/actions-expressions": "^0.1.155", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, From 31badfab56c5b113ec74048acebc4985d61def79 Mon Sep 17 00:00:00 2001 From: Laura Yu <60276246+lauraway@users.noreply.github.com> Date: Fri, 3 Mar 2023 11:02:32 -0800 Subject: [PATCH 09/19] Add trailing colon check for completion (#171) --- languageservice/src/complete.test.ts | 18 ++++++++++++++++++ languageservice/src/complete.ts | 14 +++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/languageservice/src/complete.test.ts b/languageservice/src/complete.test.ts index 84abd19..02fab47 100644 --- a/languageservice/src/complete.test.ts +++ b/languageservice/src/complete.test.ts @@ -333,6 +333,24 @@ jobs: expect(result).toHaveLength(16); }); + it("complete from behind a colon will replace it", async () => { + const input = ` +on: push +jobs: + one: + runs-on: ubuntu-latest + |: + - uses: actions/checkout@v2 +`; + const result = await complete(...getPositionFromCursor(input)); + expect(result).toHaveLength(16); + let textEdit = result[0].textEdit as TextEdit; + expect(textEdit.range).toEqual({ + start: {line: 5, character: 4}, + end: {line: 5, character: 5} + }); + }); + it("well known mapping keys have descriptions", async () => { const input = ` o| diff --git a/languageservice/src/complete.ts b/languageservice/src/complete.ts index 8050570..4d6341b 100644 --- a/languageservice/src/complete.ts +++ b/languageservice/src/complete.ts @@ -108,7 +108,19 @@ export async function complete( // Get the length of the current word const val = line.match(/[\w_-]*$/)?.[0].length || 0; - replaceRange = Range.create({line: position.line, character: position.character - val}, position); + // Check if we need to remove a trailing colon + const charAfterPos = textDocument.getText({ + start: {line: position.line, character: position.character}, + end: {line: position.line, character: position.character + 1} + }); + if (charAfterPos === ":") { + replaceRange = Range.create( + {line: position.line, character: position.character - val}, + {line: position.line, character: position.character + 1} + ); + } else { + replaceRange = Range.create({line: position.line, character: position.character - val}, position); + } } return values.map(value => { From 861cb8aaeb22e15f997d6cdaa5e493d459781834 Mon Sep 17 00:00:00 2001 From: github-actions Date: Fri, 3 Mar 2023 19:05:21 +0000 Subject: [PATCH 10/19] v0.1.156 --- browser-playground/package.json | 4 ++-- expressions/package.json | 2 +- languageserver/package.json | 6 +++--- languageservice/package.json | 6 +++--- lerna.json | 2 +- package-lock.json | 34 ++++++++++++++++----------------- workflow-parser/package.json | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/browser-playground/package.json b/browser-playground/package.json index 6d712dc..12b6322 100644 --- a/browser-playground/package.json +++ b/browser-playground/package.json @@ -1,12 +1,12 @@ { "name": "browser-playground", - "version": "0.1.155", + "version": "0.1.156", "description": "", "private": true, "main": "index.js", "type": "module", "dependencies": { - "@github/actions-languageserver": "^0.1.155", + "@github/actions-languageserver": "^0.1.156", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", diff --git a/expressions/package.json b/expressions/package.json index f323aff..fc94081 100755 --- a/expressions/package.json +++ b/expressions/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-expressions", - "version": "0.1.155", + "version": "0.1.156", "license": "MIT", "type": "module", "source": "./src/index.ts", diff --git a/languageserver/package.json b/languageserver/package.json index 1c44bec..89be61a 100644 --- a/languageserver/package.json +++ b/languageserver/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageserver", - "version": "0.1.155", + "version": "0.1.156", "description": "Language server for GitHub Actions", "license": "MIT", "type": "module", @@ -38,8 +38,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-languageservice": "^0.1.155", - "@github/actions-workflow-parser": "^0.1.155", + "@github/actions-languageservice": "^0.1.156", + "@github/actions-workflow-parser": "^0.1.156", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", diff --git a/languageservice/package.json b/languageservice/package.json index df13f14..7ac0d7c 100644 --- a/languageservice/package.json +++ b/languageservice/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageservice", - "version": "0.1.155", + "version": "0.1.156", "description": "Language service for GitHub Actions", "license": "MIT", "type": "module", @@ -39,8 +39,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.155", - "@github/actions-workflow-parser": "^0.1.155", + "@github/actions-expressions": "^0.1.156", + "@github/actions-workflow-parser": "^0.1.156", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" diff --git a/lerna.json b/lerna.json index 4de6c05..93eb637 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, - "version": "0.1.155" + "version": "0.1.156" } diff --git a/package-lock.json b/package-lock.json index 3ea0e11..1e769be 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,10 +111,10 @@ } }, "browser-playground": { - "version": "0.1.155", + "version": "0.1.156", "license": "MIT", "dependencies": { - "@github/actions-languageserver": "^0.1.155", + "@github/actions-languageserver": "^0.1.156", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", @@ -135,7 +135,7 @@ }, "expressions": { "name": "@github/actions-expressions", - "version": "0.1.155", + "version": "0.1.156", "license": "MIT", "devDependencies": { "@types/jest": "^29.0.3", @@ -151,11 +151,11 @@ }, "languageserver": { "name": "@github/actions-languageserver", - "version": "0.1.155", + "version": "0.1.156", "license": "MIT", "dependencies": { - "@github/actions-languageservice": "^0.1.155", - "@github/actions-workflow-parser": "^0.1.155", + "@github/actions-languageservice": "^0.1.156", + "@github/actions-workflow-parser": "^0.1.156", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", @@ -176,11 +176,11 @@ }, "languageservice": { "name": "@github/actions-languageservice", - "version": "0.1.155", + "version": "0.1.156", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.155", - "@github/actions-workflow-parser": "^0.1.155", + "@github/actions-expressions": "^0.1.156", + "@github/actions-workflow-parser": "^0.1.156", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" @@ -14265,10 +14265,10 @@ }, "workflow-parser": { "name": "@github/actions-workflow-parser", - "version": "0.1.155", + "version": "0.1.156", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.155", + "@github/actions-expressions": "^0.1.156", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, @@ -14827,8 +14827,8 @@ "@github/actions-languageserver": { "version": "file:languageserver", "requires": { - "@github/actions-languageservice": "^0.1.155", - "@github/actions-workflow-parser": "^0.1.155", + "@github/actions-languageservice": "^0.1.156", + "@github/actions-workflow-parser": "^0.1.156", "@octokit/rest": "^19.0.7", "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", @@ -14845,8 +14845,8 @@ "@github/actions-languageservice": { "version": "file:languageservice", "requires": { - "@github/actions-expressions": "^0.1.155", - "@github/actions-workflow-parser": "^0.1.155", + "@github/actions-expressions": "^0.1.156", + "@github/actions-workflow-parser": "^0.1.156", "@types/jest": "^29.0.3", "jest": "^29.0.3", "prettier": "^2.8.3", @@ -14863,7 +14863,7 @@ "@github/actions-workflow-parser": { "version": "file:workflow-parser", "requires": { - "@github/actions-expressions": "^0.1.155", + "@github/actions-expressions": "^0.1.156", "@types/jest": "^29.0.3", "@typescript-eslint/eslint-plugin": "^5.40.0", "@typescript-eslint/parser": "^5.40.0", @@ -17857,7 +17857,7 @@ "browser-playground": { "version": "file:browser-playground", "requires": { - "@github/actions-languageserver": "^0.1.155", + "@github/actions-languageserver": "^0.1.156", "css-loader": "^6.7.2", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", diff --git a/workflow-parser/package.json b/workflow-parser/package.json index 77282b8..948c042 100644 --- a/workflow-parser/package.json +++ b/workflow-parser/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-workflow-parser", - "version": "0.1.155", + "version": "0.1.156", "license": "MIT", "type": "module", "source": "./src/index.ts", @@ -40,7 +40,7 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.155", + "@github/actions-expressions": "^0.1.156", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, From 6dde0b554d613f60196c8ab237d880d7869cddad Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 6 Mar 2023 14:44:18 -0500 Subject: [PATCH 11/19] Show action description on hover --- languageserver/src/description-provider.ts | 26 ++++++++- .../action-description.test.ts | 57 +++++++++++++++++++ .../action-description.ts | 23 ++++++++ .../action-input.test.ts | 54 ++---------------- .../src/test-utils/action-metadata.ts | 42 ++++++++++++++ languageservice/src/action.ts | 2 + 6 files changed, 153 insertions(+), 51 deletions(-) create mode 100644 languageserver/src/description-providers/action-description.test.ts create mode 100644 languageserver/src/description-providers/action-description.ts create mode 100644 languageserver/src/test-utils/action-metadata.ts diff --git a/languageserver/src/description-provider.ts b/languageserver/src/description-provider.ts index 128a1ee..58163ad 100644 --- a/languageserver/src/description-provider.ts +++ b/languageserver/src/description-provider.ts @@ -2,20 +2,40 @@ import {DescriptionProvider} from "@github/actions-languageservice/hover"; import {Octokit} from "@octokit/rest"; import {getActionInputDescription} from "./description-providers/action-input"; import {TTLCache} from "./utils/cache"; +import {isString} from "@github/actions-workflow-parser/templates/tokens/type-guards"; +import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token"; +import {getActionDescription} from "./description-providers/action-description"; export function descriptionProvider(client: Octokit | undefined, cache: TTLCache): DescriptionProvider { const getDescription: DescriptionProvider["getDescription"] = async (context, token, path) => { - if (!client) { + if (!client || !context.step) { return undefined; } - const parent = path[path.length - 1]; - if (context.step && parent.definition?.key === "step-with") { + if (isStepInput(path)) { return await getActionInputDescription(client, cache, context.step, token); } + + if (isStepUses(path)) { + return await getActionDescription(client, cache, context.step); + } }; return { getDescription }; } + +function isStepInput(path: TemplateToken[]): boolean { + const parent = path[path.length - 1]; + return parent.definition?.key === "step-with"; +} + +function isStepUses(path: TemplateToken[]): boolean { + if (path.length < 2) { + return false; + } + const parent = path[path.length - 1]; + const grandparent = path[path.length - 2]; + return isString(parent) && parent.value === "uses" && grandparent.definition?.key === "regular-step"; +} diff --git a/languageserver/src/description-providers/action-description.test.ts b/languageserver/src/description-providers/action-description.test.ts new file mode 100644 index 0000000..91fc5df --- /dev/null +++ b/languageserver/src/description-providers/action-description.test.ts @@ -0,0 +1,57 @@ +import {Octokit} from "@octokit/rest"; +import fetchMock from "fetch-mock"; +import {createWorkflowContext} from "../test-utils/workflow-context"; +import {TTLCache} from "../utils/cache"; +import {getActionDescription} from "./action-description"; +import {actionsCheckoutMetadata} from "../test-utils/action-metadata"; + +const workflow = ` +name: Hello World +on: workflow_dispatch +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 +`; + +async function getDescription(mock: fetchMock.FetchMockSandbox) { + const workflowContext = await createWorkflowContext(workflow, "build", 0); + + return await getActionDescription( + new Octokit({ + request: { + fetch: mock + } + }), + new TTLCache(), + workflowContext.step! + ); +} + +describe("action descriptions", () => { + it("actions/checkout description", async () => { + const mock = fetchMock + .sandbox() + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata); + + expect(await getDescription(mock)).toEqual("**Checkout**\n\nCheckout a Git repository at a particular version"); + }); + + it("action does not exist", async () => { + const mock = fetchMock + .sandbox() + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", 404) + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yaml?ref=v3", 404); + + expect(await getDescription(mock)).toBeUndefined(); + }); + + it("invalid metadata", async () => { + const mock = fetchMock + .sandbox() + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", {}); + + expect(await getDescription(mock)).toBeUndefined(); + }); +}); diff --git a/languageserver/src/description-providers/action-description.ts b/languageserver/src/description-providers/action-description.ts new file mode 100644 index 0000000..8ca6d91 --- /dev/null +++ b/languageserver/src/description-providers/action-description.ts @@ -0,0 +1,23 @@ +import {parseActionReference} from "@github/actions-languageservice/action"; +import {isActionStep} from "@github/actions-workflow-parser/model/type-guards"; +import {Step} from "@github/actions-workflow-parser/model/workflow-template"; +import {Octokit} from "@octokit/rest"; +import {fetchActionMetadata} from "../utils/action-metadata"; +import {TTLCache} from "../utils/cache"; + +export async function getActionDescription(client: Octokit, cache: TTLCache, step: Step): Promise { + if (!isActionStep(step)) { + return undefined; + } + const action = parseActionReference(step.uses.value); + if (!action) { + return undefined; + } + + const metadata = await fetchActionMetadata(client, cache, action); + if (!metadata?.name || !metadata?.description) { + return undefined; + } + + return `**${metadata.name}**\n\n${metadata.description}`; +} diff --git a/languageserver/src/description-providers/action-input.test.ts b/languageserver/src/description-providers/action-input.test.ts index 9912982..0e2ce7d 100644 --- a/languageserver/src/description-providers/action-input.test.ts +++ b/languageserver/src/description-providers/action-input.test.ts @@ -5,6 +5,7 @@ import fetchMock from "fetch-mock"; import {createWorkflowContext} from "../test-utils/workflow-context"; import {TTLCache} from "../utils/cache"; import {getActionInputDescription} from "./action-input"; +import {actionsCheckoutMetadata} from "../test-utils/action-metadata"; const workflow = ` name: Hello World @@ -16,49 +17,6 @@ jobs: - uses: actions/checkout@v3 `; -// A simplified version of the action.yml file from actions/checkout -const actionMetadataContent = ` -name: 'Checkout' -description: 'Checkout a Git repository at a particular version' -inputs: - repository: - description: Repository name with owner. For example, actions/checkout - default: \${{ github.repository }} - ref: - description: The branch, tag or SHA to checkout. - required: true - token: - description: Personal access token (PAT) used to fetch the repository. - default: \${{ github.token }} - repo: - description: 'Repository name with owner. For example, actions/checkout' - deprecationMessage: 'Use repository instead' -runs: - using: node16 - main: dist/index.js - post: dist/index.js -`; - -// Based on https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3 -const actionMetadata = { - name: "action.yml", - path: "action.yml", - sha: "cab09ebd3a964aba67b57f9727f5f6fff1372b04", - size: 3649, - url: "https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", - html_url: "https://github.com/actions/checkout/blob/v3/action.yml", - git_url: "https://api.github.com/repos/actions/checkout/git/blobs/cab09ebd3a964aba67b57f9727f5f6fff1372b04", - download_url: "https://raw.githubusercontent.com/actions/checkout/v3/action.yml", - type: "file", - content: Buffer.from(actionMetadataContent).toString("base64"), - encoding: "base64", - _links: { - self: "https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", - git: "https://api.github.com/repos/actions/checkout/git/blobs/cab09ebd3a964aba67b57f9727f5f6fff1372b04", - html: "https://github.com/actions/checkout/blob/v3/action.yml" - } -}; - async function getDescription(input: string, mock: fetchMock.FetchMockSandbox) { const workflowContext = await createWorkflowContext(workflow, "build", 0); @@ -74,11 +32,11 @@ async function getDescription(input: string, mock: fetchMock.FetchMockSandbox) { ); } -describe("action descriptions", () => { +describe("action input descriptions", () => { it("optional input", async () => { const mock = fetchMock .sandbox() - .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata); + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata); expect(await getDescription("repository", mock)).toEqual( "Repository name with owner. For example, actions/checkout" @@ -88,7 +46,7 @@ describe("action descriptions", () => { it("required input", async () => { const mock = fetchMock .sandbox() - .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata); + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata); expect(await getDescription("ref", mock)).toEqual("The branch, tag or SHA to checkout.\n\n**Required**"); }); @@ -96,7 +54,7 @@ describe("action descriptions", () => { it("deprecated input", async () => { const mock = fetchMock .sandbox() - .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata); + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata); expect(await getDescription("repo", mock)).toEqual( "Repository name with owner. For example, actions/checkout\n\n**Deprecated**" @@ -106,7 +64,7 @@ describe("action descriptions", () => { it("invalid input", async () => { const mock = fetchMock .sandbox() - .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionMetadata); + .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata); expect(await getDescription("typo", mock)).toBeUndefined(); }); diff --git a/languageserver/src/test-utils/action-metadata.ts b/languageserver/src/test-utils/action-metadata.ts new file mode 100644 index 0000000..9be3868 --- /dev/null +++ b/languageserver/src/test-utils/action-metadata.ts @@ -0,0 +1,42 @@ +// A simplified version of the action.yml file from actions/checkout +const checkoutMetadataContent = ` +name: 'Checkout' +description: 'Checkout a Git repository at a particular version' +inputs: + repository: + description: Repository name with owner. For example, actions/checkout + default: \${{ github.repository }} + ref: + description: The branch, tag or SHA to checkout. + required: true + token: + description: Personal access token (PAT) used to fetch the repository. + default: \${{ github.token }} + repo: + description: 'Repository name with owner. For example, actions/checkout' + deprecationMessage: 'Use repository instead' +runs: + using: node16 + main: dist/index.js + post: dist/index.js +`; + +// Based on https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3 +export const actionsCheckoutMetadata = { + name: "action.yml", + path: "action.yml", + sha: "cab09ebd3a964aba67b57f9727f5f6fff1372b04", + size: 3649, + url: "https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", + html_url: "https://github.com/actions/checkout/blob/v3/action.yml", + git_url: "https://api.github.com/repos/actions/checkout/git/blobs/cab09ebd3a964aba67b57f9727f5f6fff1372b04", + download_url: "https://raw.githubusercontent.com/actions/checkout/v3/action.yml", + type: "file", + content: Buffer.from(checkoutMetadataContent).toString("base64"), + encoding: "base64", + _links: { + self: "https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", + git: "https://api.github.com/repos/actions/checkout/git/blobs/cab09ebd3a964aba67b57f9727f5f6fff1372b04", + html: "https://github.com/actions/checkout/blob/v3/action.yml" + } +}; diff --git a/languageservice/src/action.ts b/languageservice/src/action.ts index b10a166..2512e69 100644 --- a/languageservice/src/action.ts +++ b/languageservice/src/action.ts @@ -1,5 +1,7 @@ // https://docs.github.com/en/actions/creating-actions/metadata-syntax-for-github-actions export type ActionMetadata = { + name: string; + description: string; inputs?: ActionInputs; outputs?: ActionOutputs; }; From b516b65d3e0277e8b9e3dd8d7999cf77399b93f7 Mon Sep 17 00:00:00 2001 From: Christopher Schleiden Date: Mon, 6 Mar 2023 11:38:44 -0800 Subject: [PATCH 12/19] Move action url generation to shared function --- languageservice/src/action.ts | 7 +++++++ languageservice/src/document-links.ts | 9 ++------- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/languageservice/src/action.ts b/languageservice/src/action.ts index 2512e69..8aa6e8b 100644 --- a/languageservice/src/action.ts +++ b/languageservice/src/action.ts @@ -65,3 +65,10 @@ export function actionIdentifier(ref: ActionReference): string { } return `${ref.owner}/${ref.name}/${ref.ref}`; } + +export function actionUrl(actionRef: ActionReference): string { + // TODO: Support base uri for GHES + const gitHubBaseUri = "https://www.github.com/"; + + return `${gitHubBaseUri}${actionRef.owner}/${actionRef.name}/tree/${actionRef.ref}/${actionRef.path || ""}`; +} diff --git a/languageservice/src/document-links.ts b/languageservice/src/document-links.ts index 42ec015..e995bee 100644 --- a/languageservice/src/document-links.ts +++ b/languageservice/src/document-links.ts @@ -4,7 +4,7 @@ import {isJob} from "@github/actions-workflow-parser/model/type-guards"; import {File} from "@github/actions-workflow-parser/workflows/file"; import {TextDocument} from "vscode-languageserver-textdocument"; import {DocumentLink} from "vscode-languageserver-types"; -import {parseActionReference} from "./action"; +import {actionUrl, parseActionReference} from "./action"; import {nullTrace} from "./nulltrace"; import {mapRange} from "./utils/range"; @@ -26,9 +26,6 @@ export async function documentLinks(document: TextDocument): Promise Date: Mon, 6 Mar 2023 14:54:43 -0500 Subject: [PATCH 13/19] Include link in action description --- .../src/description-providers/action-description.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/languageserver/src/description-providers/action-description.ts b/languageserver/src/description-providers/action-description.ts index 8ca6d91..cd0866b 100644 --- a/languageserver/src/description-providers/action-description.ts +++ b/languageserver/src/description-providers/action-description.ts @@ -1,4 +1,4 @@ -import {parseActionReference} from "@github/actions-languageservice/action"; +import {actionUrl, parseActionReference} from "@github/actions-languageservice/action"; import {isActionStep} from "@github/actions-workflow-parser/model/type-guards"; import {Step} from "@github/actions-workflow-parser/model/workflow-template"; import {Octokit} from "@octokit/rest"; @@ -19,5 +19,5 @@ export async function getActionDescription(client: Octokit, cache: TTLCache, ste return undefined; } - return `**${metadata.name}**\n\n${metadata.description}`; + return `[**${metadata.name}**](${actionUrl(action)})\n\n${metadata.description}`; } From 44948c7a92ea88f38e1924817e5c2e3c03e8ab2a Mon Sep 17 00:00:00 2001 From: Christopher Schleiden Date: Mon, 6 Mar 2023 11:38:25 -0800 Subject: [PATCH 14/19] Extract `step-uses` into its own definition --- workflow-parser/src/workflow-v1.0.json | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/workflow-parser/src/workflow-v1.0.json b/workflow-parser/src/workflow-v1.0.json index ec233b9..b3ce7ec 100644 --- a/workflow-parser/src/workflow-v1.0.json +++ b/workflow-parser/src/workflow-v1.0.json @@ -2044,8 +2044,7 @@ "continue-on-error": "step-continue-on-error", "timeout-minutes": "step-timeout-minutes", "uses": { - "description": "Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image.", - "type": "non-empty-string", + "type": "step-uses", "required": true }, "with": "step-with", @@ -2053,6 +2052,12 @@ } } }, + "step-uses": { + "description": "Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image.", + "string": { + "require-non-empty": true + } + }, "step-continue-on-error": { "context": [ "github", From 2e4143c8f6f6f42d18100a60c4ef0067a8a7b4b9 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 6 Mar 2023 15:06:38 -0500 Subject: [PATCH 15/19] Use `step-uses` definition key --- languageserver/src/description-provider.ts | 21 +++------------------ 1 file changed, 3 insertions(+), 18 deletions(-) diff --git a/languageserver/src/description-provider.ts b/languageserver/src/description-provider.ts index 58163ad..3a8403a 100644 --- a/languageserver/src/description-provider.ts +++ b/languageserver/src/description-provider.ts @@ -2,8 +2,6 @@ import {DescriptionProvider} from "@github/actions-languageservice/hover"; import {Octokit} from "@octokit/rest"; import {getActionInputDescription} from "./description-providers/action-input"; import {TTLCache} from "./utils/cache"; -import {isString} from "@github/actions-workflow-parser/templates/tokens/type-guards"; -import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token"; import {getActionDescription} from "./description-providers/action-description"; export function descriptionProvider(client: Octokit | undefined, cache: TTLCache): DescriptionProvider { @@ -12,11 +10,12 @@ export function descriptionProvider(client: Octokit | undefined, cache: TTLCache return undefined; } - if (isStepInput(path)) { + const parent = path[path.length - 1]; + if (parent.definition?.key === "step-with") { return await getActionInputDescription(client, cache, context.step, token); } - if (isStepUses(path)) { + if (parent.definition?.key === "step-uses") { return await getActionDescription(client, cache, context.step); } }; @@ -25,17 +24,3 @@ export function descriptionProvider(client: Octokit | undefined, cache: TTLCache getDescription }; } - -function isStepInput(path: TemplateToken[]): boolean { - const parent = path[path.length - 1]; - return parent.definition?.key === "step-with"; -} - -function isStepUses(path: TemplateToken[]): boolean { - if (path.length < 2) { - return false; - } - const parent = path[path.length - 1]; - const grandparent = path[path.length - 2]; - return isString(parent) && parent.value === "uses" && grandparent.definition?.key === "regular-step"; -} From c817c9b3218a7292b4a21c18fe76b0d93b0df522 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 6 Mar 2023 15:23:26 -0500 Subject: [PATCH 16/19] Update description test with action link --- .../src/description-providers/action-description.test.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/languageserver/src/description-providers/action-description.test.ts b/languageserver/src/description-providers/action-description.test.ts index 91fc5df..72a0bd8 100644 --- a/languageserver/src/description-providers/action-description.test.ts +++ b/languageserver/src/description-providers/action-description.test.ts @@ -35,7 +35,9 @@ describe("action descriptions", () => { .sandbox() .getOnce("https://api.github.com/repos/actions/checkout/contents/action.yml?ref=v3", actionsCheckoutMetadata); - expect(await getDescription(mock)).toEqual("**Checkout**\n\nCheckout a Git repository at a particular version"); + expect(await getDescription(mock)).toEqual( + "[**Checkout**](https://www.github.com/actions/checkout/tree/v3/)\n\nCheckout a Git repository at a particular version" + ); }); it("action does not exist", async () => { From 0d5fb0e27ff920ab4941d399141c7362538260f2 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 6 Mar 2023 16:40:54 -0500 Subject: [PATCH 17/19] Fix tests referencing `step-uses` --- languageservice/src/hover.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/languageservice/src/hover.test.ts b/languageservice/src/hover.test.ts index a7c4064..0648070 100644 --- a/languageservice/src/hover.test.ts +++ b/languageservice/src/hover.test.ts @@ -189,7 +189,7 @@ jobs: - uses|: actions/checkout@v2 `; - const result = await hover(...getPositionFromCursor(input), testHoverConfig("uses", "non-empty-string", undefined)); + const result = await hover(...getPositionFromCursor(input), testHoverConfig("uses", "step-uses", undefined)); expect(result).not.toBeUndefined(); expect(result?.contents).toEqual( "Selects an action to run as part of a step in your job. An action is a reusable unit of code. You can use an action defined in the same repository as the workflow, a public repository, or in a published Docker container image." From 69408df701798b036070f71fd31baeefd5511d84 Mon Sep 17 00:00:00 2001 From: Josh Gross Date: Mon, 6 Mar 2023 16:41:06 -0500 Subject: [PATCH 18/19] Include name and description in test actions --- languageservice/src/validate.actions.test.ts | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/languageservice/src/validate.actions.test.ts b/languageservice/src/validate.actions.test.ts index fa00c5e..0f4bcd8 100644 --- a/languageservice/src/validate.actions.test.ts +++ b/languageservice/src/validate.actions.test.ts @@ -14,6 +14,8 @@ const validationConfig: ValidationConfig = { switch (ref.owner + "/" + ref.name + "@" + ref.ref) { case "actions/checkout@v3": metadata = { + name: "Checkout", + description: "Checkout a Git repository at a particular version", inputs: { repository: { description: "Repository name with owner", @@ -24,6 +26,9 @@ const validationConfig: ValidationConfig = { break; case "actions/setup-node@v1": metadata = { + name: "Setup Node.js environment", + description: + "Setup a Node.js environment by adding problem matchers and optionally downloading and adding it to the PATH.", inputs: { version: { description: "Deprecated. Use node-version instead. Will not be supported after October 1, 2019", @@ -35,6 +40,8 @@ const validationConfig: ValidationConfig = { break; case "actions/deploy-pages@main": metadata = { + name: "Deploy GitHub Pages site", + description: "A GitHub Action to deploy an artifact as a GitHub Pages site", inputs: { token: { required: true, @@ -46,6 +53,8 @@ const validationConfig: ValidationConfig = { break; case "actions/cache@v1": metadata = { + name: "Cache", + description: "Cache artifacts like dependencies and build outputs to improve workflow execution time", inputs: { path: { description: "A directory to store and save the cache", @@ -63,7 +72,10 @@ const validationConfig: ValidationConfig = { }; break; case "actions/action-no-input@v1": - metadata = {}; + metadata = { + name: "Action with no inputs", + description: "An action with no inputs" + }; } return metadata; } From 94e537e07272f4a0c19ec2f27fb81d8b963cc8b6 Mon Sep 17 00:00:00 2001 From: github-actions Date: Mon, 6 Mar 2023 22:31:53 +0000 Subject: [PATCH 19/19] v0.1.157 --- browser-playground/package.json | 4 ++-- expressions/package.json | 2 +- languageserver/package.json | 6 +++--- languageservice/package.json | 6 +++--- lerna.json | 2 +- package-lock.json | 34 ++++++++++++++++----------------- workflow-parser/package.json | 4 ++-- 7 files changed, 29 insertions(+), 29 deletions(-) diff --git a/browser-playground/package.json b/browser-playground/package.json index 12b6322..067a069 100644 --- a/browser-playground/package.json +++ b/browser-playground/package.json @@ -1,12 +1,12 @@ { "name": "browser-playground", - "version": "0.1.156", + "version": "0.1.157", "description": "", "private": true, "main": "index.js", "type": "module", "dependencies": { - "@github/actions-languageserver": "^0.1.156", + "@github/actions-languageserver": "^0.1.157", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", diff --git a/expressions/package.json b/expressions/package.json index fc94081..8f1613b 100755 --- a/expressions/package.json +++ b/expressions/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-expressions", - "version": "0.1.156", + "version": "0.1.157", "license": "MIT", "type": "module", "source": "./src/index.ts", diff --git a/languageserver/package.json b/languageserver/package.json index 89be61a..cfb19f8 100644 --- a/languageserver/package.json +++ b/languageserver/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageserver", - "version": "0.1.156", + "version": "0.1.157", "description": "Language server for GitHub Actions", "license": "MIT", "type": "module", @@ -38,8 +38,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-languageservice": "^0.1.156", - "@github/actions-workflow-parser": "^0.1.156", + "@github/actions-languageservice": "^0.1.157", + "@github/actions-workflow-parser": "^0.1.157", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", diff --git a/languageservice/package.json b/languageservice/package.json index 7ac0d7c..9aa131b 100644 --- a/languageservice/package.json +++ b/languageservice/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-languageservice", - "version": "0.1.156", + "version": "0.1.157", "description": "Language service for GitHub Actions", "license": "MIT", "type": "module", @@ -39,8 +39,8 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.156", - "@github/actions-workflow-parser": "^0.1.156", + "@github/actions-expressions": "^0.1.157", + "@github/actions-workflow-parser": "^0.1.157", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" diff --git a/lerna.json b/lerna.json index 93eb637..0135705 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { "$schema": "node_modules/lerna/schemas/lerna-schema.json", "useWorkspaces": true, - "version": "0.1.156" + "version": "0.1.157" } diff --git a/package-lock.json b/package-lock.json index 1e769be..36b8fee 100644 --- a/package-lock.json +++ b/package-lock.json @@ -111,10 +111,10 @@ } }, "browser-playground": { - "version": "0.1.156", + "version": "0.1.157", "license": "MIT", "dependencies": { - "@github/actions-languageserver": "^0.1.156", + "@github/actions-languageserver": "^0.1.157", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", "monaco-languageclient": "^4.0.3", @@ -135,7 +135,7 @@ }, "expressions": { "name": "@github/actions-expressions", - "version": "0.1.156", + "version": "0.1.157", "license": "MIT", "devDependencies": { "@types/jest": "^29.0.3", @@ -151,11 +151,11 @@ }, "languageserver": { "name": "@github/actions-languageserver", - "version": "0.1.156", + "version": "0.1.157", "license": "MIT", "dependencies": { - "@github/actions-languageservice": "^0.1.156", - "@github/actions-workflow-parser": "^0.1.156", + "@github/actions-languageservice": "^0.1.157", + "@github/actions-workflow-parser": "^0.1.157", "@octokit/rest": "^19.0.7", "vscode-languageserver": "^8.0.2", "vscode-languageserver-textdocument": "^1.0.7", @@ -176,11 +176,11 @@ }, "languageservice": { "name": "@github/actions-languageservice", - "version": "0.1.156", + "version": "0.1.157", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.156", - "@github/actions-workflow-parser": "^0.1.156", + "@github/actions-expressions": "^0.1.157", + "@github/actions-workflow-parser": "^0.1.157", "vscode-languageserver-textdocument": "^1.0.7", "vscode-languageserver-types": "^3.17.2", "yaml": "^2.1.1" @@ -14265,10 +14265,10 @@ }, "workflow-parser": { "name": "@github/actions-workflow-parser", - "version": "0.1.156", + "version": "0.1.157", "license": "MIT", "dependencies": { - "@github/actions-expressions": "^0.1.156", + "@github/actions-expressions": "^0.1.157", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" }, @@ -14827,8 +14827,8 @@ "@github/actions-languageserver": { "version": "file:languageserver", "requires": { - "@github/actions-languageservice": "^0.1.156", - "@github/actions-workflow-parser": "^0.1.156", + "@github/actions-languageservice": "^0.1.157", + "@github/actions-workflow-parser": "^0.1.157", "@octokit/rest": "^19.0.7", "@types/jest": "^29.0.3", "fetch-mock": "^9.11.0", @@ -14845,8 +14845,8 @@ "@github/actions-languageservice": { "version": "file:languageservice", "requires": { - "@github/actions-expressions": "^0.1.156", - "@github/actions-workflow-parser": "^0.1.156", + "@github/actions-expressions": "^0.1.157", + "@github/actions-workflow-parser": "^0.1.157", "@types/jest": "^29.0.3", "jest": "^29.0.3", "prettier": "^2.8.3", @@ -14863,7 +14863,7 @@ "@github/actions-workflow-parser": { "version": "file:workflow-parser", "requires": { - "@github/actions-expressions": "^0.1.156", + "@github/actions-expressions": "^0.1.157", "@types/jest": "^29.0.3", "@typescript-eslint/eslint-plugin": "^5.40.0", "@typescript-eslint/parser": "^5.40.0", @@ -17857,7 +17857,7 @@ "browser-playground": { "version": "file:browser-playground", "requires": { - "@github/actions-languageserver": "^0.1.156", + "@github/actions-languageserver": "^0.1.157", "css-loader": "^6.7.2", "monaco-editor-webpack-plugin": "^7.0.1", "monaco-editor-workers": "^0.34.2", diff --git a/workflow-parser/package.json b/workflow-parser/package.json index 948c042..d4b0e26 100644 --- a/workflow-parser/package.json +++ b/workflow-parser/package.json @@ -1,6 +1,6 @@ { "name": "@github/actions-workflow-parser", - "version": "0.1.156", + "version": "0.1.157", "license": "MIT", "type": "module", "source": "./src/index.ts", @@ -40,7 +40,7 @@ "watch": "tsc --build tsconfig.build.json --watch" }, "dependencies": { - "@github/actions-expressions": "^0.1.156", + "@github/actions-expressions": "^0.1.157", "cronstrue": "^2.21.0", "yaml": "^2.0.0-8" },