Add descriptions for schema-defined functions

This commit is contained in:
Josh Gross
2023-03-20 17:06:25 -04:00
parent 0c8752af17
commit 98e56cb774
4 changed files with 100 additions and 4 deletions
@@ -1,4 +1,3 @@
{
"$schema": "./descriptionsSchema.json",
"root": {
@@ -39,6 +38,20 @@
"description": "Contains the inputs of a reusable or manually triggered workflow. For more information, see [`inputs` context](https://docs.github.com/actions/learn-github-actions/contexts#inputs-context)."
}
},
"functions": {
"always": {
"description": "Causes the step to always execute, and returns `true`, even when canceled. The `always` expression is best used at the step level or on tasks that you expect to run even when a job is canceled. For example, you can use `always` to send logs even when a job is canceled."
},
"cancelled": {
"description": "Returns `true` if the workflow was canceled."
},
"failure": {
"description": "Returns `true` when any previous step of a job fails. If you have a chain of dependent jobs, `failure()` returns `true` if any ancestor job fails."
},
"hashFiles": {
"description": "Returns a single hash for the set of files that matches the `path` pattern. You can provide a single `path` pattern or multiple `path` patterns separated by commas. The `path` is relative to the `GITHUB_WORKSPACE` directory and can only include files inside of the `GITHUB_WORKSPACE`."
}
},
"github": {
"action": {
"description": "The name of the action currently running, or the [`id`](https://docs.github.com/actions/learn-github-actions/workflow-syntax-for-github-actions#jobsjob_idstepsid) of a step. GitHub Actions removes special characters, and uses the name `__run` when the current step runs a script without an `id`. If you use the same action more than once in the same job, the name will include a suffix with the sequence number with underscore before it. For example, the first script you run will have the name `__run`, and the second script will be named `__run_2`. Similarly, the second invocation of `actions/checkout` will be `actionscheckout2`."
@@ -1,6 +1,7 @@
import descriptions from "./descriptions.json" assert {type: "json"};
export const RootContext = "root";
const FunctionContext = "functions";
/**
* Get a description for a built-in context
@@ -13,3 +14,12 @@ export function getDescription(context: string, key: string): string | undefined
// eslint-disable-next-line @typescript-eslint/no-unsafe-return, @typescript-eslint/no-unsafe-member-access, @typescript-eslint/no-explicit-any
return (descriptions as any)[context]?.[key]?.description;
}
/**
* Get a description for a context function
* This will not include functions defined by the expressions library (e.g. `contains`, `fromJSON`, etc.)
* @param name Name of the function, for example `hashFiles`
*/
export function getFunctionDescription(name: string): string | undefined {
return getDescription(FunctionContext, name);
}
@@ -1,4 +1,5 @@
import {data, DescriptionDictionary} from "@github/actions-expressions";
import {format} from "@github/actions-expressions/funcs/format";
import {Hover} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config";
import {hover} from "./hover";
@@ -118,4 +119,64 @@ jobs:
}
});
});
it("built-in function", async () => {
const input = `on: push
run-name: \${{ form|at('Run {0}', github.run_id) }}
jobs:
build:
runs-on: [self-hosted]`;
const result = await hover(...getPositionFromCursor(input), {
contextProviderConfig
});
expect(result).toEqual<Hover>({
contents: format.description || "",
range: {
start: {line: 1, character: 14},
end: {line: 1, character: 20}
}
});
});
it("schema-defined function", async () => {
const input = `on: push
jobs:
build:
if: alwa|ys()
runs-on: [self-hosted]`;
const result = await hover(...getPositionFromCursor(input), {
contextProviderConfig
});
expect(result).toEqual<Hover>({
contents:
"Causes the step to always execute, and returns `true`, even when canceled. The `always` expression is best used at the step level or on tasks that you expect to run even when a job is canceled. For example, you can use `always` to send logs even when a job is canceled.",
range: {
start: {line: 3, character: 11},
end: {line: 3, character: 17}
}
});
});
it("schema-defined function with different casing", async () => {
const input = `on: push
jobs:
build:
runs-on: [self-hosted]
steps:
- run: echo \${{ hash|Files('test.txt') }}`;
const result = await hover(...getPositionFromCursor(input), {
contextProviderConfig
});
expect(result).toEqual<Hover>({
contents:
"Returns a single hash for the set of files that matches the `path` pattern. You can provide a single `path` pattern or multiple `path` patterns separated by commas. The `path` is relative to the `GITHUB_WORKSPACE` directory and can only include files inside of the `GITHUB_WORKSPACE`.",
range: {
start: {line: 5, character: 22},
end: {line: 5, character: 31}
}
});
});
});
+15 -3
View File
@@ -1,5 +1,5 @@
import {DescriptionDictionary, Parser} from "@github/actions-expressions";
import {FunctionInfo} from "@github/actions-expressions/funcs/info";
import {data, DescriptionDictionary, Parser} from "@github/actions-expressions";
import {FunctionDefinition, FunctionInfo} from "@github/actions-expressions/funcs/info";
import {Lexer} from "@github/actions-expressions/lexer";
import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert";
import {getCronDescription} from "@github/actions-workflow-parser/model/converter/cron";
@@ -13,6 +13,7 @@ import {Position, TextDocument} from "vscode-languageserver-textdocument";
import {Hover} from "vscode-languageserver-types";
import {ContextProviderConfig} from "./context-providers/config";
import {getContext, Mode} from "./context-providers/default";
import {getFunctionDescription} from "./context-providers/descriptions";
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
import {
isReusableWorkflowJobInput,
@@ -72,6 +73,10 @@ export async function hover(document: TextDocument, position: Position, config?:
const {namedContexts, functions} = splitAllowedContext(allowedContext);
const context = await getContext(namedContexts, config?.contextProviderConfig, workflowContext, Mode.Completion);
for (const func of functions) {
func.description = getFunctionDescription(func.name);
}
const exprPos = mapToExpressionPos(token, position);
if (exprPos) {
return expressionHover(exprPos, context, namedContexts, functions);
@@ -175,7 +180,14 @@ function expressionHover(
const p = new Parser(lr.tokens, namedContexts, functions);
const expr = p.parse();
const hv = new HoverVisitor(position, context, validatorFunctions);
const functionMap = new Map<string, FunctionDefinition>();
for (const func of functions) {
functionMap.set(func.name.toLowerCase(), {
...func,
call: () => new data.Null()
});
}
const hv = new HoverVisitor(position, context, functionMap);
const hoverResult = hv.hover(expr);
if (!hoverResult) {
return null;