Add file provider to hover

This commit is contained in:
Jacob Wallraff
2023-02-15 14:50:26 -08:00
parent ed9199aa93
commit cc7ca2237d
6 changed files with 120 additions and 5 deletions
+4 -1
View File
@@ -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});
})
});
});
@@ -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 {
@@ -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<string | undefined> {
if (!isReusableWorkflowJob(reusableWorkflowJob)) {
return undefined;
}
return "here's a description"
}
@@ -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");
});
});
+19 -3
View File
@@ -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<string | undefined>;
getDescription(
context: WorkflowContext,
token: TemplateToken,
path: TemplateToken[],
fileProvider?: FileProvider
): Promise<string | undefined>
};
export async function hover(document: TextDocument, position: Position, config?: HoverConfig): Promise<Hover | null> {
@@ -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;
}
@@ -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