Provide links to open actions on GitHub
This commit is contained in:
@@ -1,10 +1,11 @@
|
|||||||
import {hover} from "@github/actions-languageservice/hover";
|
import {documentLinks, hover, validate, ValidationConfig} from "@github/actions-languageservice";
|
||||||
import {registerLogger, setLogLevel} from "@github/actions-languageservice/log";
|
import {registerLogger, setLogLevel} from "@github/actions-languageservice/log";
|
||||||
import {validate, ValidationConfig} from "@github/actions-languageservice/validate";
|
|
||||||
import {Octokit} from "@octokit/rest";
|
import {Octokit} from "@octokit/rest";
|
||||||
import {
|
import {
|
||||||
CompletionItem,
|
CompletionItem,
|
||||||
Connection,
|
Connection,
|
||||||
|
DocumentLink,
|
||||||
|
DocumentLinkParams,
|
||||||
Hover,
|
Hover,
|
||||||
HoverParams,
|
HoverParams,
|
||||||
InitializeParams,
|
InitializeParams,
|
||||||
@@ -61,7 +62,10 @@ export function initConnection(connection: Connection) {
|
|||||||
resolveProvider: false,
|
resolveProvider: false,
|
||||||
triggerCharacters: [":", "."]
|
triggerCharacters: [":", "."]
|
||||||
},
|
},
|
||||||
hoverProvider: true
|
hoverProvider: true,
|
||||||
|
documentLinkProvider: {
|
||||||
|
resolveProvider: false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -103,7 +107,6 @@ export function initConnection(connection: Connection) {
|
|||||||
connection.sendDiagnostics({uri: textDocument.uri, diagnostics: result});
|
connection.sendDiagnostics({uri: textDocument.uri, diagnostics: result});
|
||||||
}
|
}
|
||||||
|
|
||||||
// This handler provides the initial list of the completion items.
|
|
||||||
connection.onCompletion(async ({position, textDocument}: TextDocumentPositionParams): Promise<CompletionItem[]> => {
|
connection.onCompletion(async ({position, textDocument}: TextDocumentPositionParams): Promise<CompletionItem[]> => {
|
||||||
return await onCompletion(
|
return await onCompletion(
|
||||||
position,
|
position,
|
||||||
@@ -118,6 +121,10 @@ export function initConnection(connection: Connection) {
|
|||||||
return hover(documents.get(textDocument.uri)!, position);
|
return hover(documents.get(textDocument.uri)!, position);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
connection.onDocumentLinks(async ({textDocument}: DocumentLinkParams): Promise<DocumentLink[] | null> => {
|
||||||
|
return documentLinks(documents.get(textDocument.uri)!);
|
||||||
|
});
|
||||||
|
|
||||||
// Make the text document manager listen on the connection
|
// Make the text document manager listen on the connection
|
||||||
// for open, change and close text document events
|
// for open, change and close text document events
|
||||||
documents.listen(connection);
|
documents.listen(connection);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import {data} from "@github/actions-expressions";
|
import {data} from "@github/actions-expressions";
|
||||||
import {ContextProviderConfig} from "@github/actions-languageservice/.";
|
import {ContextProviderConfig} from "@github/actions-languageservice";
|
||||||
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
|
import {WorkflowContext} from "@github/actions-languageservice/context/workflow-context";
|
||||||
import {Octokit} from "@octokit/rest";
|
import {Octokit} from "@octokit/rest";
|
||||||
import {getSecrets} from "./context-providers/secrets";
|
import {getSecrets} from "./context-providers/secrets";
|
||||||
|
|||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import {documentLinks} from "./document-links";
|
||||||
|
import {createDocument} from "./test-utils/document";
|
||||||
|
|
||||||
|
describe("documentLinks", () => {
|
||||||
|
it("no links without actions", async () => {
|
||||||
|
const input = `on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: [self-hosted]
|
||||||
|
steps:
|
||||||
|
- run: echo "Hello World"`;
|
||||||
|
const result = await documentLinks(createDocument("test.yaml", input));
|
||||||
|
expect(result).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("no links for invalid workflow", async () => {
|
||||||
|
const input = `onFOO: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: [self-hosted]`;
|
||||||
|
const result = await documentLinks(createDocument("test.yaml", input));
|
||||||
|
expect(result).toHaveLength(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("links for actions in workflow", async () => {
|
||||||
|
const input = `on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: [self-hosted]
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
- uses: actions/checkout@v3
|
||||||
|
- uses: github/codeql-action/init@v2`;
|
||||||
|
const result = await documentLinks(createDocument("test.yaml", input));
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
end: {
|
||||||
|
character: 31,
|
||||||
|
line: 5
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
character: 12,
|
||||||
|
line: 5
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target: "https://www.github.com/actions/checkout/tree/v2/",
|
||||||
|
tooltip: "Open action on GitHub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
end: {
|
||||||
|
character: 31,
|
||||||
|
line: 6
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
character: 12,
|
||||||
|
line: 6
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target: "https://www.github.com/actions/checkout/tree/v3/",
|
||||||
|
tooltip: "Open action on GitHub"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
range: {
|
||||||
|
end: {
|
||||||
|
character: 40,
|
||||||
|
line: 7
|
||||||
|
},
|
||||||
|
start: {
|
||||||
|
character: 12,
|
||||||
|
line: 7
|
||||||
|
}
|
||||||
|
},
|
||||||
|
target: "https://www.github.com/github/codeql-action/tree/v2/init",
|
||||||
|
tooltip: "Open action on GitHub"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
import {convertWorkflowTemplate, parseWorkflow} from "@github/actions-workflow-parser";
|
||||||
|
import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert";
|
||||||
|
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 {nullTrace} from "./nulltrace";
|
||||||
|
import {mapRange} from "./utils/range";
|
||||||
|
|
||||||
|
export async function documentLinks(document: TextDocument): Promise<DocumentLink[] | null> {
|
||||||
|
const file: File = {
|
||||||
|
name: document.uri,
|
||||||
|
content: document.getText()
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = parseWorkflow(file.name, [file], nullTrace);
|
||||||
|
if (!result.value) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
const template = convertWorkflowTemplate(result.context, result.value!, ErrorPolicy.TryConversion);
|
||||||
|
|
||||||
|
// 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 || []) {
|
||||||
|
for (const step of job?.steps || []) {
|
||||||
|
if ("uses" in step) {
|
||||||
|
const actionRef = parseActionReference(step.uses.value);
|
||||||
|
if (!actionRef) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const url = `${gitHubBaseUri}${actionRef.owner}/${actionRef.name}/tree/${actionRef.ref}/${
|
||||||
|
actionRef.path || ""
|
||||||
|
}`;
|
||||||
|
|
||||||
|
actionLinks.push({
|
||||||
|
range: mapRange(step.uses.range),
|
||||||
|
target: url,
|
||||||
|
tooltip: `Open action on GitHub`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...actionLinks];
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
export {complete} from "./complete";
|
export {complete} from "./complete";
|
||||||
export {ContextProviderConfig} from "./context-providers/config";
|
export {ContextProviderConfig} from "./context-providers/config";
|
||||||
|
export {documentLinks} from "./document-links";
|
||||||
export {hover} from "./hover";
|
export {hover} from "./hover";
|
||||||
export {Logger, LogLevel, registerLogger, setLogLevel} from "./log";
|
export {Logger, LogLevel, registerLogger, setLogLevel} from "./log";
|
||||||
export {validate} from "./validate";
|
export {validate, ValidationConfig} from "./validate";
|
||||||
export {ValueProviderConfig, ValueProviderKind} from "./value-providers/config";
|
export {ValueProviderConfig, ValueProviderKind} from "./value-providers/config";
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {isScalar, parseWorkflow} from "@github/actions-workflow-parser/.";
|
import {isScalar, parseWorkflow} from "@github/actions-workflow-parser";
|
||||||
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
|
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
|
||||||
import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types";
|
import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types";
|
||||||
import {nullTrace} from "../nulltrace";
|
import {nullTrace} from "../nulltrace";
|
||||||
|
|||||||
Reference in New Issue
Block a user