Merge branch 'main' into elbrenn/secret-complete

This commit is contained in:
Beth Brennan
2023-03-07 15:23:50 -05:00
committed by GitHub
25 changed files with 388 additions and 119 deletions
+38 -10
View File
@@ -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,43 @@ 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:
jobs:
build:
runs-on: ubuntu-latest
steps:
- 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
+3 -3
View File
@@ -1,6 +1,6 @@
{
"name": "@github/actions-languageservice",
"version": "0.1.153",
"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.153",
"@github/actions-workflow-parser": "^0.1.153",
"@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"
+9
View File
@@ -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;
};
@@ -63,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 || ""}`;
}
+31
View File
@@ -320,6 +320,37 @@ 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("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|
+13 -1
View File
@@ -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 => {
+2 -7
View File
@@ -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<DocumentLin
// Add links to referenced actions
const actionLinks: DocumentLink[] = [];
// TODO: Support base uri for GHES
const gitHubBaseUri = "https://www.github.com/";
for (const job of template?.jobs || []) {
if (!job || !isJob(job)) {
continue;
@@ -40,9 +37,7 @@ export async function documentLinks(document: TextDocument): Promise<DocumentLin
continue;
}
const url = `${gitHubBaseUri}${actionRef.owner}/${actionRef.name}/tree/${actionRef.ref}/${
actionRef.path || ""
}`;
const url = actionUrl(actionRef);
actionLinks.push({
range: mapRange(step.uses.range),
+1 -1
View File
@@ -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."
@@ -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;
}
+13 -1
View File
@@ -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;
}