diff --git a/actions-languageservice/src/hover.test.ts b/actions-languageservice/src/hover.test.ts index d67fbc3..ab2f5b3 100644 --- a/actions-languageservice/src/hover.test.ts +++ b/actions-languageservice/src/hover.test.ts @@ -1,12 +1,33 @@ +import {TextDocument} from "vscode-languageserver-textdocument"; +import {hover} from "./hover"; + describe("validation", () => { it("valid workflow", async () => { - // const result = await hover(null as any, { - // line: 1, - // character: 2, - // }); - // expect(result).not.toBeUndefined(); - // expect(result?.contents).toEqual( - // "The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows." - // ); + const input = `on: push +jobs: + build: + runs-on: [self-hosted, u|]`; + const doc = TextDocument.create("test://test/test.yaml", "yaml", 0, input); + const result = await hover(doc, { + line: 0, + character: 0 + }); + expect(result).not.toBeUndefined(); + expect(result?.contents).toEqual( + "The name of the GitHub event that triggers the workflow. You can provide a single event string, array of events, array of event types, or an event configuration map that schedules a workflow or restricts the execution of a workflow to specific files, tags, or branch changes. For a list of available events, see https://help.github.com/en/github/automating-your-workflow-with-github-actions/events-that-trigger-workflows." + ); + }); + + it("hover on value", async () => { + const input = `on: push +jobs: + build: + runs-on: [self-hosted, u|]`; + const doc = TextDocument.create("test://test/test.yaml", "yaml", 0, input); + const result = await hover(doc, { + line: 0, + character: 5 + }); + expect(result?.contents).toBeUndefined(); }); }); diff --git a/actions-languageservice/src/hover.ts b/actions-languageservice/src/hover.ts index 9ec3856..8af5131 100644 --- a/actions-languageservice/src/hover.ts +++ b/actions-languageservice/src/hover.ts @@ -1,13 +1,14 @@ -import {parseWorkflow, ParseWorkflowResult} from "@github/actions-workflow-parser"; +import {parseWorkflow} from "@github/actions-workflow-parser"; import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token"; import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token"; -import {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/sequence-token"; import {File} from "@github/actions-workflow-parser/workflows/file"; import {Position, TextDocument} from "vscode-languageserver-textdocument"; import {Hover} from "vscode-languageserver-types"; import {nullTrace} from "./nulltrace"; -import {findInnerToken} from "./utils/find-token"; +import {findInnerToken, findToken} from "./utils/find-token"; +import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types"; +// Render value description and Context when hovering over a key in a MappingToken export async function hover(document: TextDocument, position: Position): Promise { const file: File = { name: document.uri, @@ -15,38 +16,47 @@ export async function hover(document: TextDocument, position: Position): Promise }; const result = parseWorkflow(file.name, [file], nullTrace); - // Find inner token returns null if position is not in a token - const innerToken = findInnerToken(position, result.value); - if (result.value && innerToken) { - return getHover(innerToken); + const {token, keyToken, parent} = findToken(position, result.value); + + if (result.value && token) { + // If the parent is a MappingToken and no keyToken was returned, our token is the key + if (parent?.templateTokenType === TokenType.Mapping && !keyToken) { + const mappingToken = parent as MappingToken; + const value = mappingToken.find(token.toString()); + if (value) { + return getHover(token, value); + } + } } return null; } -function getHover(innerToken: TemplateToken): Hover | null { - if (innerToken.definition) { +// PositionToken is the token that the cursor is on +// DescriptionToken may differ if the description is stored on an associated token, such as when hovering over a key in a mapping +function getHover(positionToken: TemplateToken, descriptionToken: TemplateToken): Hover | null { + if (descriptionToken.definition) { let description = ""; - if (innerToken.description) { - description = innerToken.description; + if (descriptionToken.description) { + description = descriptionToken.description; } - if (innerToken.definition.evaluatorContext.length > 0) { + if (descriptionToken.definition.evaluatorContext.length > 0) { // Only add padding if there is a description - description += `${description.length > 0 ? `\n\n` : ""}**Context:** ${innerToken.definition.evaluatorContext.join( - ", " - )}`; + description += `${ + description.length > 0 ? `\n\n` : "" + }**Context:** ${descriptionToken.definition.evaluatorContext.join(", ")}`; } return { contents: description, range: { start: { - line: innerToken.range!.start[0], - character: innerToken.range!.start[1] + line: positionToken.range!.start[0] - 1, + character: positionToken.range!.start[1] - 1 }, end: { - line: innerToken.range!.end[0], - character: innerToken.range!.end[1] + line: positionToken.range!.end[0] - 1, + character: positionToken.range!.end[1] - 1 } } } as Hover; diff --git a/actions-languageservice/src/utils/find-token.ts b/actions-languageservice/src/utils/find-token.ts index 88e43ee..8d2cbfd 100644 --- a/actions-languageservice/src/utils/find-token.ts +++ b/actions-languageservice/src/utils/find-token.ts @@ -56,12 +56,20 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult { const {key, value} = mappingToken.get(i); if (onSameLine(pos, key, value)) { - if (posInToken(pos, key) && key.range!.end[1] + 1 === value.range!.start[1]) { - // There's no space between the key and value, there's nothing valid to complete here + if (posInToken(pos, key)) { + if (key.range!.end[1] + 1 === value.range!.start[1]) { + // There's no space between the key and value, this is not valid + return { + token: null, + keyToken: null, + parent: null + }; + } + return { - token: null, + token: key, keyToken: null, - parent: null + parent: mappingToken }; } @@ -76,9 +84,9 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult { } s.push({ - parent: mappingToken, + token: value, keyToken: key, - token: value + parent: mappingToken }); } continue;