Merge pull request #15 from github/cschleiden/token-path
Use `tokenPath` to identify position within a workflow
This commit is contained in:
@@ -1,6 +1,7 @@
|
|||||||
import {complete} from "./complete";
|
import {complete} from "./complete";
|
||||||
|
import {WorkflowContext} from "./context/workflow-context";
|
||||||
import {getPositionFromCursor} from "./test-utils/cursor-position";
|
import {getPositionFromCursor} from "./test-utils/cursor-position";
|
||||||
import {Value, ValueProviderConfig, WorkflowContext} from "./value-providers/config";
|
import {Value, ValueProviderConfig} from "./value-providers/config";
|
||||||
|
|
||||||
describe("completion", () => {
|
describe("completion", () => {
|
||||||
it("runs-on", async () => {
|
it("runs-on", async () => {
|
||||||
|
|||||||
@@ -1,25 +1,20 @@
|
|||||||
import {complete as completeExpression} from "@github/actions-expressions";
|
import {complete as completeExpression} from "@github/actions-expressions";
|
||||||
import {
|
import {convertWorkflowTemplate, isSequence, isString, parseWorkflow} from "@github/actions-workflow-parser";
|
||||||
convertWorkflowTemplate,
|
import {ErrorPolicy} from "@github/actions-workflow-parser/model/convert";
|
||||||
isMapping,
|
|
||||||
isSequence,
|
|
||||||
isString,
|
|
||||||
parseWorkflow,
|
|
||||||
} from "@github/actions-workflow-parser";
|
|
||||||
import {CLOSE_EXPRESSION, OPEN_EXPRESSION} from "@github/actions-workflow-parser/templates/template-constants";
|
import {CLOSE_EXPRESSION, OPEN_EXPRESSION} from "@github/actions-workflow-parser/templates/template-constants";
|
||||||
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/index";
|
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/index";
|
||||||
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
|
import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
|
||||||
import {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/sequence-token";
|
|
||||||
import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types";
|
import {TokenType} from "@github/actions-workflow-parser/templates/tokens/types";
|
||||||
import {File} from "@github/actions-workflow-parser/workflows/file";
|
import {File} from "@github/actions-workflow-parser/workflows/file";
|
||||||
import {Position, TextDocument} from "vscode-languageserver-textdocument";
|
import {Position, TextDocument} from "vscode-languageserver-textdocument";
|
||||||
import {CompletionItem} from "vscode-languageserver-types";
|
import {CompletionItem} from "vscode-languageserver-types";
|
||||||
import {ContextProviderConfig} from "./context-providers/config";
|
import {ContextProviderConfig} from "./context-providers/config";
|
||||||
import {getContext} from "./context-providers/default";
|
import {getContext} from "./context-providers/default";
|
||||||
|
import {getWorkflowContext, WorkflowContext} from "./context/workflow-context";
|
||||||
import {nullTrace} from "./nulltrace";
|
import {nullTrace} from "./nulltrace";
|
||||||
import {findToken} from "./utils/find-token";
|
import {findToken} from "./utils/find-token";
|
||||||
import {transform} from "./utils/transform";
|
import {transform} from "./utils/transform";
|
||||||
import {Value, ValueProviderConfig, WorkflowContext} from "./value-providers/config";
|
import {Value, ValueProviderConfig} from "./value-providers/config";
|
||||||
import {defaultValueProviders} from "./value-providers/default";
|
import {defaultValueProviders} from "./value-providers/default";
|
||||||
import {definitionValues} from "./value-providers/definition";
|
import {definitionValues} from "./value-providers/definition";
|
||||||
|
|
||||||
@@ -67,8 +62,8 @@ export async function complete(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const {token, keyToken, parent, parentKey} = findToken(newPos, result.value);
|
const {token, keyToken, parent, path} = findToken(newPos, result.value);
|
||||||
const template = convertWorkflowTemplate(result.context, result.value);
|
const template = convertWorkflowTemplate(result.context, result.value, ErrorPolicy.TryConversion);
|
||||||
|
|
||||||
// If we are inside an expression, take a different code-path. The workflow parser does not correctly create
|
// If we are inside an expression, take a different code-path. The workflow parser does not correctly create
|
||||||
// expression nodes for invalid expressions and during editing expressions are invalid most of the time.
|
// expression nodes for invalid expressions and during editing expressions are invalid most of the time.
|
||||||
@@ -85,21 +80,20 @@ export async function complete(
|
|||||||
|
|
||||||
const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim();
|
const expressionInput = (getExpressionInput(currentInput, relCharPos) || "").trim();
|
||||||
|
|
||||||
const context = await getContext(token.definition?.readerContext || [], contextProviderConfig);
|
const context = getContext(token.definition?.readerContext || [], contextProviderConfig);
|
||||||
|
|
||||||
return completeExpression(expressionInput, context, []);
|
return completeExpression(expressionInput, context, []);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const workflowContext = {uri: textDocument.uri, template: template};
|
const workflowContext = getWorkflowContext(textDocument.uri, template, path);
|
||||||
const values = await getValues(token, parent, parentKey, valueProviderConfig, workflowContext);
|
const values = await getValues(token, parent, valueProviderConfig, workflowContext);
|
||||||
return values.map(value => CompletionItem.create(value.label));
|
return values.map(value => CompletionItem.create(value.label));
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getValues(
|
async function getValues(
|
||||||
token: TemplateToken | null,
|
token: TemplateToken | null,
|
||||||
parent: TemplateToken | null,
|
parent: TemplateToken | null,
|
||||||
parentKey: TemplateToken | null,
|
|
||||||
valueProviderConfig: ValueProviderConfig | undefined,
|
valueProviderConfig: ValueProviderConfig | undefined,
|
||||||
workflowContext: WorkflowContext
|
workflowContext: WorkflowContext
|
||||||
): Promise<Value[]> {
|
): Promise<Value[]> {
|
||||||
@@ -107,26 +101,23 @@ async function getValues(
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const existingValues = getExistingValues(token, parent, parentKey);
|
const existingValues = getExistingValues(token, parent);
|
||||||
|
|
||||||
let customValues: Value[] | undefined = undefined;
|
|
||||||
if (token?.definition?.key) {
|
if (token?.definition?.key) {
|
||||||
customValues = await valueProviderConfig?.getCustomValues(token.definition.key, workflowContext);
|
const customValues = await valueProviderConfig?.getCustomValues(token.definition.key, workflowContext);
|
||||||
}
|
|
||||||
|
|
||||||
if (customValues !== undefined) {
|
if (customValues) {
|
||||||
return filterAndSortCompletionOptions(customValues, existingValues);
|
return filterAndSortCompletionOptions(customValues, existingValues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const valueProviders = defaultValueProviders(workflowContext);
|
|
||||||
|
|
||||||
// Use the value provider from the parent if we don't have a value provider for the current key
|
// Use the value provider from the parent if we don't have a value provider for the current key
|
||||||
const valueProvider =
|
const valueProvider =
|
||||||
(token?.definition?.key && valueProviders[token.definition.key]) ||
|
(token?.definition?.key && defaultValueProviders[token.definition.key]) ||
|
||||||
(parent.definition?.key && valueProviders[parent.definition.key]);
|
(parent.definition?.key && defaultValueProviders[parent.definition.key]);
|
||||||
|
|
||||||
if (valueProvider) {
|
if (valueProvider) {
|
||||||
const values = valueProvider();
|
const values = valueProvider(workflowContext);
|
||||||
return filterAndSortCompletionOptions(values, existingValues);
|
return filterAndSortCompletionOptions(values, existingValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -140,18 +131,16 @@ async function getValues(
|
|||||||
return filterAndSortCompletionOptions(values, existingValues);
|
return filterAndSortCompletionOptions(values, existingValues);
|
||||||
}
|
}
|
||||||
|
|
||||||
function getExistingValues(token: TemplateToken | null, parent: TemplateToken, parentKey: TemplateToken | null) {
|
function getExistingValues(token: TemplateToken | null, parent: TemplateToken) {
|
||||||
// For incomplete YAML, we may only have a parent token
|
// For incomplete YAML, we may only have a parent token
|
||||||
if (token) {
|
if (token) {
|
||||||
if (!isString(token)) {
|
if (!isString(token)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isMapping(parent) && parentKey && isString(parentKey)) {
|
|
||||||
return new Set<string>([parentKey.value]);
|
|
||||||
}
|
|
||||||
if (isSequence(parent)) {
|
if (isSequence(parent)) {
|
||||||
const sequenceValues = new Set<string>();
|
const sequenceValues = new Set<string>();
|
||||||
|
|
||||||
for (let i = 0; i < parent.count; i++) {
|
for (let i = 0; i < parent.count; i++) {
|
||||||
const t = parent.get(i);
|
const t = parent.get(i);
|
||||||
if (isString(t)) {
|
if (isString(t)) {
|
||||||
@@ -159,6 +148,7 @@ function getExistingValues(token: TemplateToken | null, parent: TemplateToken, p
|
|||||||
sequenceValues.add(t.value);
|
sequenceValues.add(t.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sequenceValues;
|
return sequenceValues;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -167,9 +157,11 @@ function getExistingValues(token: TemplateToken | null, parent: TemplateToken, p
|
|||||||
// No token and parent is a mapping, so we're completing a key
|
// No token and parent is a mapping, so we're completing a key
|
||||||
const mapKeys = new Set<string>();
|
const mapKeys = new Set<string>();
|
||||||
const mapToken = parent as MappingToken;
|
const mapToken = parent as MappingToken;
|
||||||
|
|
||||||
for (let i = 0; i < mapToken.count; i++) {
|
for (let i = 0; i < mapToken.count; i++) {
|
||||||
const key = mapToken.get(i).key;
|
const key = mapToken.get(i).key;
|
||||||
if (key.isLiteral && isString(key)) {
|
|
||||||
|
if (isString(key)) {
|
||||||
mapKeys.add(key.value);
|
mapKeys.add(key.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -179,7 +171,7 @@ function getExistingValues(token: TemplateToken | null, parent: TemplateToken, p
|
|||||||
}
|
}
|
||||||
|
|
||||||
function filterAndSortCompletionOptions(options: Value[], existingValues?: Set<string>) {
|
function filterAndSortCompletionOptions(options: Value[], existingValues?: Set<string>) {
|
||||||
options = options.filter(x => !existingValues || !existingValues.has(x.label));
|
options = options.filter(x => !existingValues?.has(x.label));
|
||||||
options.sort((a, b) => a.label.localeCompare(b.label));
|
options.sort((a, b) => a.label.localeCompare(b.label));
|
||||||
return options;
|
return options;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,58 @@
|
|||||||
|
import {convertWorkflowTemplate, parseWorkflow, WorkflowTemplate} from "@github/actions-workflow-parser";
|
||||||
|
import {nullTrace} from "../nulltrace";
|
||||||
|
import {getPositionFromCursor} from "../test-utils/cursor-position";
|
||||||
|
import {findToken} from "../utils/find-token";
|
||||||
|
import {getWorkflowContext, WorkflowContext} from "./workflow-context";
|
||||||
|
|
||||||
|
function testGetWorkflowContext(input: string): [context: WorkflowContext, template?: WorkflowTemplate] {
|
||||||
|
const [textDocument, pos] = getPositionFromCursor(input);
|
||||||
|
const result = parseWorkflow(
|
||||||
|
"wf.yaml",
|
||||||
|
[
|
||||||
|
{
|
||||||
|
content: textDocument.getText(),
|
||||||
|
name: "wf.yaml"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
nullTrace
|
||||||
|
);
|
||||||
|
|
||||||
|
let template: WorkflowTemplate | undefined;
|
||||||
|
|
||||||
|
if (result.value) {
|
||||||
|
template = convertWorkflowTemplate(result.context, result.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
const {path} = findToken(pos, result.value);
|
||||||
|
|
||||||
|
return [getWorkflowContext(textDocument.uri, template, path), template];
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("getWorkflowContext", () => {
|
||||||
|
it("context for workflow", () => {
|
||||||
|
const [context, template] = testGetWorkflowContext(`on: push
|
||||||
|
name: te|st
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- echo Hello`);
|
||||||
|
expect(context.uri).not.toBe("");
|
||||||
|
expect(context.template).not.toBeUndefined();
|
||||||
|
expect(context.job).toBeUndefined();
|
||||||
|
expect(context.step).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it("context for workflow job", () => {
|
||||||
|
const [context, template] = testGetWorkflowContext(`on: push
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-lat|est
|
||||||
|
steps:
|
||||||
|
- run: echo Hello`);
|
||||||
|
expect(context.uri).not.toBe("");
|
||||||
|
expect(context.template).not.toBeUndefined();
|
||||||
|
expect(context.job).not.toBeUndefined();
|
||||||
|
expect(context.step).toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,40 @@
|
|||||||
|
import {WorkflowTemplate} from "@github/actions-workflow-parser";
|
||||||
|
import {JobConfig, StepConfig} from "@github/actions-workflow-parser/model/workflow-template";
|
||||||
|
import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
|
||||||
|
import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
|
||||||
|
|
||||||
|
export interface WorkflowContext {
|
||||||
|
uri: string;
|
||||||
|
|
||||||
|
template: WorkflowTemplate | undefined;
|
||||||
|
|
||||||
|
/** If the context is for a position within a job, this will be the job */
|
||||||
|
job?: JobConfig;
|
||||||
|
|
||||||
|
/** If the context is for a position within a step, this will be the step */
|
||||||
|
step?: StepConfig;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getWorkflowContext(
|
||||||
|
uri: string,
|
||||||
|
template: WorkflowTemplate | undefined,
|
||||||
|
tokenPath: TemplateToken[]
|
||||||
|
): WorkflowContext {
|
||||||
|
const context: WorkflowContext = {uri: uri, template};
|
||||||
|
|
||||||
|
if (template) {
|
||||||
|
// Iterate through the token path to find the job and step
|
||||||
|
for (let i = 0; i < tokenPath.length; ++i) {
|
||||||
|
const token = tokenPath[i];
|
||||||
|
|
||||||
|
switch (token.definition?.key) {
|
||||||
|
case "job-id": {
|
||||||
|
const jobID = (token as StringToken).value;
|
||||||
|
context.job = template.jobs.find(job => job.id === jobID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return context;
|
||||||
|
}
|
||||||
@@ -23,7 +23,7 @@ function testFindToken(input: string): {
|
|||||||
parent: testTokenInfo | null;
|
parent: testTokenInfo | null;
|
||||||
key: testTokenInfo | null;
|
key: testTokenInfo | null;
|
||||||
token: testTokenInfo | null;
|
token: testTokenInfo | null;
|
||||||
parentKey: testTokenInfo | null;
|
path: testTokenInfo[];
|
||||||
} {
|
} {
|
||||||
const [textDocument, pos] = getPositionFromCursor(input);
|
const [textDocument, pos] = getPositionFromCursor(input);
|
||||||
const result = parseWorkflow(
|
const result = parseWorkflow(
|
||||||
@@ -41,17 +41,17 @@ function testFindToken(input: string): {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
parent: getTokenInfo(r.parent),
|
parent: getTokenInfo(r.parent),
|
||||||
parentKey: getTokenInfo(r.parentKey),
|
|
||||||
key: getTokenInfo(r.keyToken),
|
key: getTokenInfo(r.keyToken),
|
||||||
token: getTokenInfo(r.token)
|
token: getTokenInfo(r.token),
|
||||||
|
path: r.path.map(x => getTokenInfo(x)!)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
describe("find-token", () => {
|
describe("find-token", () => {
|
||||||
it("on string key", () => {
|
it("on string key", () => {
|
||||||
expect(testFindToken(`o|n: push`)).toEqual({
|
expect(testFindToken(`o|n: push`)).toEqual({
|
||||||
|
path: [["workflow-root-strict", TokenType.Mapping]],
|
||||||
parent: ["workflow-root-strict", TokenType.Mapping],
|
parent: ["workflow-root-strict", TokenType.Mapping],
|
||||||
parentKey: null,
|
|
||||||
key: null,
|
key: null,
|
||||||
token: [null, TokenType.String, "on"]
|
token: [null, TokenType.String, "on"]
|
||||||
});
|
});
|
||||||
@@ -59,8 +59,11 @@ describe("find-token", () => {
|
|||||||
|
|
||||||
it("on string value", () => {
|
it("on string value", () => {
|
||||||
expect(testFindToken(`on: pu|sh`)).toEqual({
|
expect(testFindToken(`on: pu|sh`)).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "on"]
|
||||||
|
],
|
||||||
parent: ["workflow-root-strict", TokenType.Mapping],
|
parent: ["workflow-root-strict", TokenType.Mapping],
|
||||||
parentKey: null,
|
|
||||||
key: [null, TokenType.String, "on"],
|
key: [null, TokenType.String, "on"],
|
||||||
token: ["on-strict", TokenType.String, "push"]
|
token: ["on-strict", TokenType.String, "push"]
|
||||||
});
|
});
|
||||||
@@ -71,8 +74,12 @@ describe("find-token", () => {
|
|||||||
testFindToken(`on:
|
testFindToken(`on:
|
||||||
pu|sh:`)
|
pu|sh:`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "on"],
|
||||||
|
["on-mapping-strict", TokenType.Mapping]
|
||||||
|
],
|
||||||
parent: ["on-mapping-strict", TokenType.Mapping],
|
parent: ["on-mapping-strict", TokenType.Mapping],
|
||||||
parentKey: [null, TokenType.String, "on"],
|
|
||||||
key: null,
|
key: null,
|
||||||
token: [null, TokenType.String, "push"]
|
token: [null, TokenType.String, "push"]
|
||||||
});
|
});
|
||||||
@@ -83,8 +90,12 @@ describe("find-token", () => {
|
|||||||
testFindToken(`on:
|
testFindToken(`on:
|
||||||
- pu|sh`)
|
- pu|sh`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "on"],
|
||||||
|
["on-strict", TokenType.Sequence]
|
||||||
|
],
|
||||||
parent: ["on-strict", TokenType.Sequence],
|
parent: ["on-strict", TokenType.Sequence],
|
||||||
parentKey: null,
|
|
||||||
key: null,
|
key: null,
|
||||||
token: ["non-empty-string", TokenType.String, "push"]
|
token: ["non-empty-string", TokenType.String, "push"]
|
||||||
});
|
});
|
||||||
@@ -95,8 +106,12 @@ describe("find-token", () => {
|
|||||||
testFindToken(`on:
|
testFindToken(`on:
|
||||||
-| push`)
|
-| push`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "on"],
|
||||||
|
["on-strict", TokenType.Sequence]
|
||||||
|
],
|
||||||
parent: ["on-strict", TokenType.Sequence],
|
parent: ["on-strict", TokenType.Sequence],
|
||||||
parentKey: null,
|
|
||||||
key: null,
|
key: null,
|
||||||
token: null
|
token: null
|
||||||
});
|
});
|
||||||
@@ -108,8 +123,12 @@ describe("find-token", () => {
|
|||||||
- push
|
- push
|
||||||
- pull_request|`)
|
- pull_request|`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "on"],
|
||||||
|
["on-strict", TokenType.Sequence]
|
||||||
|
],
|
||||||
parent: ["on-strict", TokenType.Sequence],
|
parent: ["on-strict", TokenType.Sequence],
|
||||||
parentKey: null,
|
|
||||||
key: null,
|
key: null,
|
||||||
token: ["non-empty-string", TokenType.String, "pull_request"]
|
token: ["non-empty-string", TokenType.String, "pull_request"]
|
||||||
});
|
});
|
||||||
@@ -122,8 +141,16 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: [ubuntu-latest, self|`)
|
runs-on: [ubuntu-latest, self|`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "runs-on"],
|
||||||
|
["runs-on", TokenType.Sequence]
|
||||||
|
],
|
||||||
parent: ["runs-on", TokenType.Sequence],
|
parent: ["runs-on", TokenType.Sequence],
|
||||||
parentKey: null,
|
|
||||||
key: null,
|
key: null,
|
||||||
token: ["non-empty-string", TokenType.String, "self"]
|
token: ["non-empty-string", TokenType.String, "self"]
|
||||||
});
|
});
|
||||||
@@ -135,8 +162,8 @@ jobs:
|
|||||||
jo|bs:
|
jo|bs:
|
||||||
build:`)
|
build:`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [["workflow-root-strict", TokenType.Mapping]],
|
||||||
parent: ["workflow-root-strict", TokenType.Mapping],
|
parent: ["workflow-root-strict", TokenType.Mapping],
|
||||||
parentKey: null,
|
|
||||||
key: null,
|
key: null,
|
||||||
token: [null, TokenType.String, "jobs"]
|
token: [null, TokenType.String, "jobs"]
|
||||||
});
|
});
|
||||||
@@ -149,8 +176,15 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-on: ubu|`)
|
runs-on: ubu|`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "runs-on"]
|
||||||
|
],
|
||||||
parent: ["job-factory", TokenType.Mapping],
|
parent: ["job-factory", TokenType.Mapping],
|
||||||
parentKey: ["job-id", TokenType.String, "build"],
|
|
||||||
key: [null, TokenType.String, "runs-on"],
|
key: [null, TokenType.String, "runs-on"],
|
||||||
token: ["runs-on", TokenType.String, "ubu"]
|
token: ["runs-on", TokenType.String, "ubu"]
|
||||||
});
|
});
|
||||||
@@ -163,8 +197,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
run|s-on: ubu`)
|
run|s-on: ubu`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping]
|
||||||
|
],
|
||||||
parent: ["job-factory", TokenType.Mapping],
|
parent: ["job-factory", TokenType.Mapping],
|
||||||
parentKey: ["job-id", TokenType.String, "build"],
|
|
||||||
key: null,
|
key: null,
|
||||||
token: [null, TokenType.String, "runs-on"]
|
token: [null, TokenType.String, "runs-on"]
|
||||||
});
|
});
|
||||||
@@ -177,8 +217,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
continue-on-error:|`)
|
continue-on-error:|`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping]
|
||||||
|
],
|
||||||
parent: ["job-factory", TokenType.Mapping],
|
parent: ["job-factory", TokenType.Mapping],
|
||||||
parentKey: ["job-id", TokenType.String, "build"],
|
|
||||||
key: [null, TokenType.String, "continue-on-error"],
|
key: [null, TokenType.String, "continue-on-error"],
|
||||||
token: ["boolean-strategy-context", TokenType.Null, ""]
|
token: ["boolean-strategy-context", TokenType.Null, ""]
|
||||||
});
|
});
|
||||||
@@ -191,8 +237,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
container:|`)
|
container:|`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping]
|
||||||
|
],
|
||||||
parent: ["job-factory", TokenType.Mapping],
|
parent: ["job-factory", TokenType.Mapping],
|
||||||
parentKey: ["job-id", TokenType.String, "build"],
|
|
||||||
key: [null, TokenType.String, "container"],
|
key: [null, TokenType.String, "container"],
|
||||||
token: ["container", TokenType.String, ""]
|
token: ["container", TokenType.String, ""]
|
||||||
});
|
});
|
||||||
@@ -205,8 +257,13 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
continue-on-error:|foo`)
|
continue-on-error:|foo`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"]
|
||||||
|
],
|
||||||
parent: ["jobs", TokenType.Mapping],
|
parent: ["jobs", TokenType.Mapping],
|
||||||
parentKey: [null, TokenType.String, "jobs"],
|
|
||||||
key: ["job-id", TokenType.String, "build"],
|
key: ["job-id", TokenType.String, "build"],
|
||||||
token: ["job", TokenType.String, "continue-on-error:foo"]
|
token: ["job", TokenType.String, "continue-on-error:foo"]
|
||||||
});
|
});
|
||||||
@@ -219,8 +276,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
continue-on-error:| foo`)
|
continue-on-error:| foo`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping]
|
||||||
|
],
|
||||||
parent: ["job-factory", TokenType.Mapping],
|
parent: ["job-factory", TokenType.Mapping],
|
||||||
parentKey: null,
|
|
||||||
key: null,
|
key: null,
|
||||||
token: null
|
token: null
|
||||||
});
|
});
|
||||||
@@ -233,8 +296,14 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
continue-on-error|: foo`)
|
continue-on-error|: foo`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping]
|
||||||
|
],
|
||||||
parent: ["job-factory", TokenType.Mapping],
|
parent: ["job-factory", TokenType.Mapping],
|
||||||
parentKey: ["job-id", TokenType.String, "build"],
|
|
||||||
key: null,
|
key: null,
|
||||||
token: [null, TokenType.String, "continue-on-error"]
|
token: [null, TokenType.String, "continue-on-error"]
|
||||||
});
|
});
|
||||||
@@ -247,8 +316,13 @@ jobs:
|
|||||||
build:
|
build:
|
||||||
runs-|`)
|
runs-|`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"]
|
||||||
|
],
|
||||||
parent: ["jobs", TokenType.Mapping],
|
parent: ["jobs", TokenType.Mapping],
|
||||||
parentKey: [null, TokenType.String, "jobs"],
|
|
||||||
key: ["job-id", TokenType.String, "build"],
|
key: ["job-id", TokenType.String, "build"],
|
||||||
token: ["job", TokenType.String, "runs-"]
|
token: ["job", TokenType.String, "runs-"]
|
||||||
});
|
});
|
||||||
@@ -262,8 +336,13 @@ jobs:
|
|||||||
runs-|
|
runs-|
|
||||||
#`)
|
#`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"]
|
||||||
|
],
|
||||||
parent: ["jobs", TokenType.Mapping],
|
parent: ["jobs", TokenType.Mapping],
|
||||||
parentKey: [null, TokenType.String, "jobs"],
|
|
||||||
key: ["job-id", TokenType.String, "build"],
|
key: ["job-id", TokenType.String, "build"],
|
||||||
token: ["job", TokenType.String, "runs-"]
|
token: ["job", TokenType.String, "runs-"]
|
||||||
});
|
});
|
||||||
@@ -277,8 +356,15 @@ jobs:
|
|||||||
concurrency:
|
concurrency:
|
||||||
runs-on: ubu|`)
|
runs-on: ubu|`)
|
||||||
).toEqual({
|
).toEqual({
|
||||||
|
path: [
|
||||||
|
["workflow-root-strict", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "jobs"],
|
||||||
|
["jobs", TokenType.Mapping],
|
||||||
|
["job-id", TokenType.String, "build"],
|
||||||
|
["job-factory", TokenType.Mapping],
|
||||||
|
[null, TokenType.String, "runs-on"]
|
||||||
|
],
|
||||||
parent: ["job-factory", TokenType.Mapping],
|
parent: ["job-factory", TokenType.Mapping],
|
||||||
parentKey: ["job-id", TokenType.String, "build"],
|
|
||||||
key: [null, TokenType.String, "runs-on"],
|
key: [null, TokenType.String, "runs-on"],
|
||||||
token: ["runs-on", TokenType.String, "ubu"]
|
token: ["runs-on", TokenType.String, "ubu"]
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -11,10 +11,11 @@ export function findInnerToken(pos: Position, root?: TemplateToken) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type TokenResult = {
|
export type TokenResult = {
|
||||||
token: TemplateToken | null;
|
|
||||||
keyToken: TemplateToken | null;
|
|
||||||
parent: TemplateToken | null;
|
parent: TemplateToken | null;
|
||||||
parentKey: TemplateToken | null;
|
keyToken: TemplateToken | null;
|
||||||
|
token: TemplateToken | null;
|
||||||
|
|
||||||
|
path: TemplateToken[];
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -35,23 +36,24 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult {
|
|||||||
token: null,
|
token: null,
|
||||||
keyToken: null,
|
keyToken: null,
|
||||||
parent: null,
|
parent: null,
|
||||||
parentKey: null
|
path: []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
let lastMatchingToken: TemplateToken | null = null;
|
let lastMatching: TokenResult | null = null;
|
||||||
|
|
||||||
const s: TokenResult[] = [
|
const s: TokenResult[] = [
|
||||||
{
|
{
|
||||||
token: root,
|
token: root,
|
||||||
keyToken: null,
|
keyToken: null,
|
||||||
parent: null,
|
parent: null,
|
||||||
parentKey: null
|
path: []
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
while (s.length > 0) {
|
while (s.length > 0) {
|
||||||
const {parent, token, keyToken, parentKey} = s.shift()!;
|
const result = s.shift()!;
|
||||||
|
const {parent, token, keyToken, path} = result;
|
||||||
if (!token) {
|
if (!token) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -61,7 +63,7 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Pos is in token, remember this token
|
// Pos is in token, remember this token
|
||||||
lastMatchingToken = token;
|
lastMatching = result;
|
||||||
|
|
||||||
// Position is in token, enqueue children if there are any
|
// Position is in token, enqueue children if there are any
|
||||||
switch (token.templateTokenType) {
|
switch (token.templateTokenType) {
|
||||||
@@ -76,7 +78,7 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult {
|
|||||||
parent: mappingToken,
|
parent: mappingToken,
|
||||||
keyToken: null,
|
keyToken: null,
|
||||||
token: key,
|
token: key,
|
||||||
parentKey: keyToken
|
path: [...path, mappingToken]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +89,7 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult {
|
|||||||
parent: mappingToken,
|
parent: mappingToken,
|
||||||
keyToken: key,
|
keyToken: key,
|
||||||
token: value,
|
token: value,
|
||||||
parentKey: keyToken
|
path: [...path, mappingToken]
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -95,7 +97,7 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult {
|
|||||||
parent: mappingToken,
|
parent: mappingToken,
|
||||||
keyToken: key,
|
keyToken: key,
|
||||||
token: value,
|
token: value,
|
||||||
parentKey: keyToken
|
path: [...path, mappingToken, key]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -107,7 +109,7 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult {
|
|||||||
parent: sequenceToken,
|
parent: sequenceToken,
|
||||||
keyToken: null,
|
keyToken: null,
|
||||||
token: sequenceToken.get(i),
|
token: sequenceToken.get(i),
|
||||||
parentKey: null
|
path: [...path, sequenceToken]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
continue;
|
continue;
|
||||||
@@ -117,16 +119,16 @@ export function findToken(pos: Position, root?: TemplateToken): TokenResult {
|
|||||||
token,
|
token,
|
||||||
keyToken,
|
keyToken,
|
||||||
parent,
|
parent,
|
||||||
parentKey
|
path
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Did not find a matching token, return the last matching token as parent
|
// Did not find a matching token, return the last matching token as parent
|
||||||
return {
|
return {
|
||||||
token: null,
|
token: null,
|
||||||
parent: lastMatchingToken,
|
parent: lastMatching?.token ?? null,
|
||||||
keyToken: null,
|
keyToken: null,
|
||||||
parentKey: null
|
path: lastMatching?.token ? [...lastMatching.path, lastMatching.token] : []
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import {WorkflowTemplate} from "@github/actions-workflow-parser/.";
|
import {WorkflowContext} from "../context/workflow-context";
|
||||||
|
|
||||||
export interface Value {
|
export interface Value {
|
||||||
label: string;
|
label: string;
|
||||||
@@ -7,10 +7,6 @@ export interface Value {
|
|||||||
|
|
||||||
export type ValueProvider = () => Value[];
|
export type ValueProvider = () => Value[];
|
||||||
|
|
||||||
export interface WorkflowContext {
|
|
||||||
uri: string;
|
|
||||||
template: WorkflowTemplate | undefined;
|
|
||||||
}
|
|
||||||
export interface ValueProviderConfig {
|
export interface ValueProviderConfig {
|
||||||
getCustomValues: (key: string, context: WorkflowContext) => Promise<Value[] | undefined>;
|
getCustomValues: (key: string, context: WorkflowContext) => Promise<Value[] | undefined>;
|
||||||
getActionInputs?: (owner: string, name: string, ref: string, path?: string) => Promise<ActionInput[]>;
|
getActionInputs?: (owner: string, name: string, ref: string, path?: string) => Promise<ActionInput[]>;
|
||||||
|
|||||||
@@ -1,25 +1,24 @@
|
|||||||
import {Value, ValueProvider, WorkflowContext} from "./config";
|
import {WorkflowContext} from "../context/workflow-context";
|
||||||
import {getJobNames} from "./needs";
|
import {Value} from "./config";
|
||||||
|
import {needs} from "./needs";
|
||||||
|
|
||||||
export function defaultValueProviders(workflowContext: WorkflowContext): {[key: string]: ValueProvider} {
|
export const defaultValueProviders: {[key: string]: (workflowContext: WorkflowContext) => Value[]} = {
|
||||||
return {
|
needs,
|
||||||
needs: () => getJobNames(workflowContext.template),
|
"runs-on": () =>
|
||||||
"runs-on": () =>
|
stringsToValues([
|
||||||
stringsToValues([
|
"ubuntu-latest",
|
||||||
"ubuntu-latest",
|
"ubuntu-18.04",
|
||||||
"ubuntu-18.04",
|
"ubuntu-16.04",
|
||||||
"ubuntu-16.04",
|
"windows-latest",
|
||||||
"windows-latest",
|
"windows-2019",
|
||||||
"windows-2019",
|
"windows-2016",
|
||||||
"windows-2016",
|
"macos-latest",
|
||||||
"macos-latest",
|
"macos-10.15",
|
||||||
"macos-10.15",
|
"macos-10.14",
|
||||||
"macos-10.14",
|
"macos-10.13",
|
||||||
"macos-10.13",
|
"self-hosted"
|
||||||
"self-hosted"
|
])
|
||||||
])
|
};
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringsToValues(labels: string[]): Value[] {
|
export function stringsToValues(labels: string[]): Value[] {
|
||||||
return labels.map(x => ({label: x}));
|
return labels.map(x => ({label: x}));
|
||||||
|
|||||||
@@ -1,19 +1,13 @@
|
|||||||
|
import {WorkflowContext} from "../context/workflow-context";
|
||||||
import {Value} from "./config";
|
import {Value} from "./config";
|
||||||
import {WorkflowTemplate} from "@github/actions-workflow-parser/model/workflow-template";
|
|
||||||
|
|
||||||
export function getJobNames(template: WorkflowTemplate | undefined): Value[] {
|
export function needs(context: WorkflowContext): Value[] {
|
||||||
if (!template) {
|
if (!context.template) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const jobNames = new Set<string>();
|
const uniquejobIDs = new Set(context.template.jobs.map(j => j.id)).values();
|
||||||
const jobList = template.jobs;
|
return Array.from(uniquejobIDs)
|
||||||
for (const job of jobList) {
|
.filter(x => x !== context.job?.id)
|
||||||
const name = job.id;
|
.map(x => ({label: x}));
|
||||||
if (name && !jobNames.has(name)) {
|
|
||||||
jobNames.add(name);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array.from(jobNames).map(label => ({label}));
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user