Merge branch 'main' into elbrenn/secret-complete
This commit is contained in:
+38
-10
@@ -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
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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 || ""}`;
|
||||
}
|
||||
|
||||
@@ -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|
|
||||
|
||||
@@ -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 => {
|
||||
|
||||
@@ -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),
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user