diff --git a/actions-languageserver/src/connection.ts b/actions-languageserver/src/connection.ts index be145ce..496c37d 100644 --- a/actions-languageserver/src/connection.ts +++ b/actions-languageserver/src/connection.ts @@ -134,7 +134,10 @@ export function initConnection(connection: Connection) { return hover(documents.get(textDocument.uri)!, position, { descriptionProvider: descriptionProvider(client, cache), - contextProviderConfig: repoContext && contextProviders(client, repoContext, cache) + contextProviderConfig: repoContext && contextProviders(client, repoContext, cache), + fileProvider: getFileProvider(client, cache, repoContext?.workspaceUri, async path => { + return await connection.sendRequest(Requests.ReadFile, {path}); + }) }); }); diff --git a/actions-languageserver/src/description-provider.ts b/actions-languageserver/src/description-provider.ts index 128a1ee..fc0758f 100644 --- a/actions-languageserver/src/description-provider.ts +++ b/actions-languageserver/src/description-provider.ts @@ -1,10 +1,11 @@ import {DescriptionProvider} from "@github/actions-languageservice/hover"; import {Octokit} from "@octokit/rest"; import {getActionInputDescription} from "./description-providers/action-input"; +import {getReusableWorkflowInputDescription} from "./description-providers/reusable-workflow-input"; import {TTLCache} from "./utils/cache"; export function descriptionProvider(client: Octokit | undefined, cache: TTLCache): DescriptionProvider { - const getDescription: DescriptionProvider["getDescription"] = async (context, token, path) => { + const getDescription: DescriptionProvider["getDescription"] = async (context, token, path, fileProvider) => { if (!client) { return undefined; } @@ -13,6 +14,10 @@ export function descriptionProvider(client: Octokit | undefined, cache: TTLCache if (context.step && parent.definition?.key === "step-with") { return await getActionInputDescription(client, cache, context.step, token); } + + if (context.reusableWorkflowJob && fileProvider && parent.definition?.key === "workflow-job-with") { + return await getReusableWorkflowInputDescription(client, cache, context.reusableWorkflowJob, token, fileProvider); + } }; return { diff --git a/actions-languageserver/src/description-providers/reusable-workflow-input.ts b/actions-languageserver/src/description-providers/reusable-workflow-input.ts new file mode 100644 index 0000000..3914abf --- /dev/null +++ b/actions-languageserver/src/description-providers/reusable-workflow-input.ts @@ -0,0 +1,23 @@ +import {parseActionReference} from "@github/actions-languageservice/action"; +import {isString} from "@github/actions-workflow-parser"; +import {isActionStep, isReusableWorkflowJob} from "@github/actions-workflow-parser/model/type-guards"; +import {FileProvider} from "@github/actions-workflow-parser/workflows/file-provider"; +import {ReusableWorkflowJob} from "@github/actions-workflow-parser/model/workflow-template"; +import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token"; +import {Octokit} from "@octokit/rest"; +import {fetchActionMetadata} from "../utils/action-metadata"; +import {TTLCache} from "../utils/cache"; + +export async function getReusableWorkflowInputDescription( + client: Octokit, + cache: TTLCache, + reusableWorkflowJob: ReusableWorkflowJob, + token: TemplateToken, + fileProvider: FileProvider +): Promise { + if (!isReusableWorkflowJob(reusableWorkflowJob)) { + return undefined; + } + + return "here's a description" +} diff --git a/actions-languageservice/src/hover.reusable-workflow.test.ts b/actions-languageservice/src/hover.reusable-workflow.test.ts new file mode 100644 index 0000000..d53e858 --- /dev/null +++ b/actions-languageservice/src/hover.reusable-workflow.test.ts @@ -0,0 +1,49 @@ +import {CompletionItem, MarkupContent} from "vscode-languageserver-types"; +import {DescriptionProvider, hover, HoverConfig} from "./hover"; +import {testHoverConfig} from "./hover.test"; +import {getPositionFromCursor} from "./test-utils/cursor-position"; +import {testFileProvider} from "./test-utils/test-file-provider"; + +function mapResult(result: CompletionItem[]) { + return result.map(x => { + return {label: x.label, description: (x.documentation as MarkupContent).value}; + }); +} + +describe("completion with reusable workflows", () => { + it("hover on job input with description", async () => { + const input = ` +on: push + +jobs: + build: + uses: ./reusable-workflow-with-inputs.yaml + with: + us|ername: +`; + const result = await hover( + ...getPositionFromCursor(input), + {fileProvider: testFileProvider} + ); + expect(result).not.toBeUndefined(); + expect(result?.contents).toEqual( + "A username passed from the caller workflow\n\n" + + "**Context:** github, inputs, vars, needs, strategy, matrix" + ); + }); + + it("hover on job input without description", async () => { + const input = ` +on: push + +jobs: + build: + uses: ./reusable-workflow-with-inputs-no-description.yaml + with: + us|ername: +`; + const result = await hover(...getPositionFromCursor(input)); + expect(result).not.toBeUndefined(); + expect(result?.contents).toEqual("**Context:** github, inputs, vars, needs, strategy, matrix"); + }); +}); diff --git a/actions-languageservice/src/hover.ts b/actions-languageservice/src/hover.ts index b36cf16..69f0d69 100644 --- a/actions-languageservice/src/hover.ts +++ b/actions-languageservice/src/hover.ts @@ -9,6 +9,7 @@ import {StringToken} from "@github/actions-workflow-parser/templates/tokens/stri import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token"; import {isBasicExpression, isString} from "@github/actions-workflow-parser/templates/tokens/type-guards"; import {File} from "@github/actions-workflow-parser/workflows/file"; +import {FileProvider} from "@github/actions-workflow-parser/workflows/file-provider"; import {Position, TextDocument} from "vscode-languageserver-textdocument"; import {Hover} from "vscode-languageserver-types"; import {ContextProviderConfig} from "./context-providers/config"; @@ -26,10 +27,16 @@ import {mapRange} from "./utils/range"; export type HoverConfig = { descriptionProvider?: DescriptionProvider; contextProviderConfig?: ContextProviderConfig; + fileProvider?: FileProvider; }; export type DescriptionProvider = { - getDescription(context: WorkflowContext, token: TemplateToken, path: TemplateToken[]): Promise; + getDescription( + context: WorkflowContext, + token: TemplateToken, + path: TemplateToken[], + fileProvider?: FileProvider + ): Promise }; export async function hover(document: TextDocument, position: Position, config?: HoverConfig): Promise { @@ -103,13 +110,22 @@ async function getDescription( path: TemplateToken[] ) { const defaultDescription = token.description || ""; + // TODO fix this check if (!result?.value || !config?.descriptionProvider) { return defaultDescription; } - const template = await convertWorkflowTemplate(result.context, result.value, ErrorPolicy.TryConversion); + const template = await convertWorkflowTemplate( + result.context, + result.value, + ErrorPolicy.TryConversion, + config?.fileProvider, + { + fetchReusableWorkflowDepth: config?.fileProvider ? 1 : 0 + } + ); const workflowContext = getWorkflowContext(document.uri, template, path); - const description = await config.descriptionProvider.getDescription(workflowContext, token, path); + const description = await config.descriptionProvider.getDescription(workflowContext, token, path, config.fileProvider); return description || defaultDescription; } diff --git a/actions-languageservice/src/test-utils/test-file-provider.ts b/actions-languageservice/src/test-utils/test-file-provider.ts index c527755..99d075d 100644 --- a/actions-languageservice/src/test-utils/test-file-provider.ts +++ b/actions-languageservice/src/test-utils/test-file-provider.ts @@ -60,6 +60,25 @@ on: required: false type: string +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + ` + }; + + case "./reusable-workflow-with-inputs-no-description.yaml": + return { + name: "reusable-workflow-with-inputs.yaml", + content: ` +on: + workflow_call: + inputs: + username: + required: true + type: string + jobs: build: runs-on: ubuntu-latest