Merge pull request #3 from github/joshmgross/prettier

Run prettier on language service
This commit is contained in:
Josh Gross
2022-11-15 14:16:37 -05:00
committed by GitHub
16 changed files with 139 additions and 231 deletions
+5
View File
@@ -0,0 +1,5 @@
node_modules
dist
*.md
*.js
*.json
+1 -1
View File
@@ -6,4 +6,4 @@
"bracketSpacing": false, "bracketSpacing": false,
"trailingComma": "none", "trailingComma": "none",
"arrowParens": "avoid" "arrowParens": "avoid"
} }
+4 -2
View File
@@ -33,7 +33,9 @@
"prepublishOnly": "npm run build && npm run test", "prepublishOnly": "npm run build && npm run test",
"test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest", "test": "NODE_OPTIONS=\"--experimental-vm-modules\" jest",
"test-watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch", "test-watch": "NODE_OPTIONS=\"--experimental-vm-modules\" jest --watch",
"watch": "tsc --build tsconfig.build.json --watch" "watch": "tsc --build tsconfig.build.json --watch",
"prettier": "prettier .",
"prettier-fix": "prettier --write ."
}, },
"dependencies": { "dependencies": {
"@github/actions-workflow-parser": "*", "@github/actions-workflow-parser": "*",
@@ -56,4 +58,4 @@
"ts-jest": "^29.0.3", "ts-jest": "^29.0.3",
"typescript": "^4.8.4" "typescript": "^4.8.4"
} }
} }
+13 -24
View File
@@ -1,10 +1,6 @@
import { complete } from "./complete"; import {complete} from "./complete";
import { getPositionFromCursor } from "./test-utils/cursor-position"; import {getPositionFromCursor} from "./test-utils/cursor-position";
import { import {Value, ValueProviderConfig, WorkflowContext} from "./value-providers/config";
Value,
ValueProviderConfig,
WorkflowContext,
} from "./value-providers/config";
describe("completion", () => { describe("completion", () => {
it("runs-on", async () => { it("runs-on", async () => {
@@ -69,7 +65,7 @@ jobs:
|`; |`;
const result = await complete(...getPositionFromCursor(input)); const result = await complete(...getPositionFromCursor(input));
expect(result).not.toBeUndefined(); expect(result).not.toBeUndefined();
expect(result.map((x) => x.label)).not.toContain("runs-on"); expect(result.map(x => x.label)).not.toContain("runs-on");
}); });
it("one-of narrows down to a specific type", async () => { it("one-of narrows down to a specific type", async () => {
@@ -80,22 +76,18 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
|`; |`;
const jobFactoryResult = await complete( const jobFactoryResult = await complete(...getPositionFromCursor(jobFactory));
...getPositionFromCursor(jobFactory)
);
expect(jobFactoryResult).not.toBeUndefined(); expect(jobFactoryResult).not.toBeUndefined();
expect(jobFactoryResult.map((x) => x.label)).not.toContain("uses"); expect(jobFactoryResult.map(x => x.label)).not.toContain("uses");
const workflowJob = `on: push const workflowJob = `on: push
jobs: jobs:
build: build:
uses: octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89 uses: octo-org/this-repo/.github/workflows/workflow-1.yml@172239021f7ba04fe7327647b213799853a9eb89
|`; |`;
const workflowJobResult = await complete( const workflowJobResult = await complete(...getPositionFromCursor(workflowJob));
...getPositionFromCursor(workflowJob)
);
expect(workflowJobResult).not.toBeUndefined(); expect(workflowJobResult).not.toBeUndefined();
expect(workflowJobResult.map((x) => x.label)).not.toContain("runs-on"); expect(workflowJobResult.map(x => x.label)).not.toContain("runs-on");
}); });
it("completes boolean values", async () => { it("completes boolean values", async () => {
@@ -108,7 +100,7 @@ jobs:
expect(result).not.toBeUndefined(); expect(result).not.toBeUndefined();
expect(result.length).toEqual(2); expect(result.length).toEqual(2);
expect(result.map((x) => x.label).sort()).toEqual(["false", "true"]); expect(result.map(x => x.label).sort()).toEqual(["false", "true"]);
}); });
it("completes for empty map values", async () => { it("completes for empty map values", async () => {
@@ -121,7 +113,7 @@ jobs:
expect(result).not.toBeUndefined(); expect(result).not.toBeUndefined();
expect(result.length).toEqual(2); expect(result.length).toEqual(2);
expect(result.map((x) => x.label).sort()).toEqual(["false", "true"]); expect(result.map(x => x.label).sort()).toEqual(["false", "true"]);
}); });
it("does not complete empty map values when cursor is immediately after the position", async () => { it("does not complete empty map values when cursor is immediately after the position", async () => {
@@ -138,17 +130,14 @@ jobs:
it("custom value providers override defaults", async () => { it("custom value providers override defaults", async () => {
const input = "on: push\njobs:\n build:\n runs-on: |"; const input = "on: push\njobs:\n build:\n runs-on: |";
const getCustomValues = async ( const getCustomValues = async (key: string, _: WorkflowContext): Promise<Value[] | undefined> => {
key: string,
_: WorkflowContext
): Promise<Value[] | undefined> => {
if (key === "runs-on") { if (key === "runs-on") {
return [{ label: "my-custom-label" }]; return [{label: "my-custom-label"}];
} }
return []; return [];
}; };
const config: ValueProviderConfig = { const config: ValueProviderConfig = {
getCustomValues: getCustomValues, getCustomValues: getCustomValues
}; };
const result = await complete(...getPositionFromCursor(input), config); const result = await complete(...getPositionFromCursor(input), config);
+20 -37
View File
@@ -1,22 +1,22 @@
import { parseWorkflow } from "@github/actions-workflow-parser"; import {parseWorkflow} from "@github/actions-workflow-parser";
import { import {
SEQUENCE_TYPE, SEQUENCE_TYPE,
STRING_TYPE, STRING_TYPE,
MAPPING_TYPE, MAPPING_TYPE,
TemplateToken, TemplateToken,
NULL_TYPE, NULL_TYPE
} from "@github/actions-workflow-parser/templates/tokens/index"; } 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 {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/sequence-token";
import { StringToken } from "@github/actions-workflow-parser/templates/tokens/string-token"; import {StringToken} from "@github/actions-workflow-parser/templates/tokens/string-token";
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 { nullTrace } from "./nulltrace"; import {nullTrace} from "./nulltrace";
import { findInnerTokenAndParent } from "./utils/find-token"; import {findInnerTokenAndParent} from "./utils/find-token";
import { transform } from "./utils/transform"; import {transform} from "./utils/transform";
import { Value, ValueProviderConfig } from "./value-providers/config"; import {Value, ValueProviderConfig} from "./value-providers/config";
import { defaultValueProviders } from "./value-providers/default"; import {defaultValueProviders} from "./value-providers/default";
export async function complete( export async function complete(
textDocument: TextDocument, textDocument: TextDocument,
@@ -28,19 +28,13 @@ export async function complete(
const file: File = { const file: File = {
name: textDocument.uri, name: textDocument.uri,
content: newDoc.getText(), content: newDoc.getText()
}; };
const result = parseWorkflow(file.name, [file], nullTrace); const result = parseWorkflow(file.name, [file], nullTrace);
const [innerToken, parent] = findInnerTokenAndParent(newPos, result.value); const [innerToken, parent] = findInnerTokenAndParent(newPos, result.value);
const values = await getValues( const values = await getValues(innerToken, parent, newPos, textDocument.uri, valueProviderConfig);
innerToken, return values.map(value => CompletionItem.create(value.label));
parent,
newPos,
textDocument.uri,
valueProviderConfig
);
return values.map((value) => CompletionItem.create(value.label));
} }
async function getValues( async function getValues(
@@ -65,10 +59,7 @@ async function getValues(
let customValues: Value[] | undefined = undefined; let customValues: Value[] | undefined = undefined;
if (token?.definition?.key) { if (token?.definition?.key) {
customValues = await valueProviderConfig?.getCustomValues( customValues = await valueProviderConfig?.getCustomValues(token.definition.key, {uri: workflowUri});
token.definition.key,
{ uri: workflowUri }
);
} }
if (customValues !== undefined) { if (customValues !== undefined) {
@@ -93,10 +84,7 @@ async function getValues(
function getExistingValues(token: TemplateToken | null, parent: TemplateToken) { 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 ( if (token.templateTokenType !== STRING_TYPE || parent.templateTokenType !== SEQUENCE_TYPE) {
token.templateTokenType !== STRING_TYPE ||
parent.templateTokenType !== SEQUENCE_TYPE
) {
return; return;
} }
@@ -127,13 +115,8 @@ function getExistingValues(token: TemplateToken | null, parent: TemplateToken) {
} }
} }
function filterAndSortCompletionOptions( function filterAndSortCompletionOptions(options: Value[], existingValues?: Set<string>) {
options: Value[], options = options.filter(x => !existingValues || !existingValues.has(x.label));
existingValues?: Set<string>
) {
options = options.filter(
(x) => !existingValues || !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;
} }
+18 -24
View File
@@ -1,23 +1,17 @@
import { import {parseWorkflow, ParseWorkflowResult} from "@github/actions-workflow-parser";
parseWorkflow, import {TemplateToken} from "@github/actions-workflow-parser/templates/tokens/template-token";
ParseWorkflowResult, import {MappingToken} from "@github/actions-workflow-parser/templates/tokens/mapping-token";
} from "@github/actions-workflow-parser"; import {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/sequence-token";
import { TemplateToken } from "@github/actions-workflow-parser/templates/tokens/template-token"; import {File} from "@github/actions-workflow-parser/workflows/file";
import { MappingToken } from "@github/actions-workflow-parser/templates/tokens/mapping-token"; import {Position, TextDocument} from "vscode-languageserver-textdocument";
import { SequenceToken } from "@github/actions-workflow-parser/templates/tokens/sequence-token"; import {Hover} from "vscode-languageserver-types";
import { File } from "@github/actions-workflow-parser/workflows/file"; import {nullTrace} from "./nulltrace";
import { Position, TextDocument } from "vscode-languageserver-textdocument"; import {findInnerToken} from "./utils/find-token";
import { Hover } from "vscode-languageserver-types";
import { nullTrace } from "./nulltrace";
import { findInnerToken } from "./utils/find-token";
export async function hover( export async function hover(document: TextDocument, position: Position): Promise<Hover | null> {
document: TextDocument,
position: Position
): Promise<Hover | null> {
const file: File = { const file: File = {
name: document.uri, name: document.uri,
content: document.getText(), content: document.getText()
}; };
const result = parseWorkflow(file.name, [file], nullTrace); const result = parseWorkflow(file.name, [file], nullTrace);
@@ -38,9 +32,9 @@ function getHover(innerToken: TemplateToken): Hover | null {
if (innerToken.definition.evaluatorContext.length > 0) { if (innerToken.definition.evaluatorContext.length > 0) {
// Only add padding if there is a description // Only add padding if there is a description
description += `${description.length > 0 ? `\n\n` : ""}**Context:** ${ description += `${description.length > 0 ? `\n\n` : ""}**Context:** ${innerToken.definition.evaluatorContext.join(
innerToken.definition.evaluatorContext.join(", ") ", "
}`; )}`;
} }
return { return {
@@ -48,13 +42,13 @@ function getHover(innerToken: TemplateToken): Hover | null {
range: { range: {
start: { start: {
line: innerToken.range!.start[0], line: innerToken.range!.start[0],
character: innerToken.range!.start[1], character: innerToken.range!.start[1]
}, },
end: { end: {
line: innerToken.range!.end[0], line: innerToken.range!.end[0],
character: innerToken.range!.end[1], character: innerToken.range!.end[1]
}, }
}, }
} as Hover; } as Hover;
} }
return null; return null;
+3 -3
View File
@@ -1,3 +1,3 @@
export { hover } from "./hover"; export {hover} from "./hover";
export { validate } from "./validate"; export {validate} from "./validate";
export { complete } from "./complete"; export {complete} from "./complete";
+4 -4
View File
@@ -1,7 +1,7 @@
import { TraceWriter } from "@github/actions-workflow-parser/templates/trace-writer"; import {TraceWriter} from "@github/actions-workflow-parser/templates/trace-writer";
export const nullTrace: TraceWriter = { export const nullTrace: TraceWriter = {
info: (x) => {}, info: x => {},
verbose: (x) => {}, verbose: x => {},
error: (x) => {}, error: x => {}
}; };
@@ -1,26 +1,24 @@
import { getPositionFromCursor } from "./cursor-position"; import {getPositionFromCursor} from "./cursor-position";
describe("getPositionFromCursor", () => { describe("getPositionFromCursor", () => {
it("returns the position of the cursor and the document without that cursor", () => { it("returns the position of the cursor and the document without that cursor", () => {
const input = "on: push\njobs:|"; const input = "on: push\njobs:|";
const [newDoc, position] = getPositionFromCursor(input); const [newDoc, position] = getPositionFromCursor(input);
expect(position).toEqual({ line: 1, character: 5 }); expect(position).toEqual({line: 1, character: 5});
expect(newDoc.getText()).toEqual("on: push\njobs:"); expect(newDoc.getText()).toEqual("on: push\njobs:");
}); });
it("throws an error if no cursor is found", () => { it("throws an error if no cursor is found", () => {
const input = "on: push\njobs:"; const input = "on: push\njobs:";
expect(() => getPositionFromCursor(input)).toThrowError( expect(() => getPositionFromCursor(input)).toThrowError("No cursor found in document");
"No cursor found in document"
);
}); });
it("handles a cursor at the beginning of the document", () => { it("handles a cursor at the beginning of the document", () => {
const input = "|on: push\njobs:"; const input = "|on: push\njobs:";
const [newDoc, position] = getPositionFromCursor(input); const [newDoc, position] = getPositionFromCursor(input);
expect(position).toEqual({ line: 0, character: 0 }); expect(position).toEqual({line: 0, character: 0});
expect(newDoc.getText()).toEqual("on: push\njobs:"); expect(newDoc.getText()).toEqual("on: push\njobs:");
}); });
}); });
@@ -1,4 +1,4 @@
import { TextDocument, Position } from "vscode-languageserver-textdocument"; import {TextDocument, Position} from "vscode-languageserver-textdocument";
// Calculates the position of the cursor and the document without that cursor // Calculates the position of the cursor and the document without that cursor
// Cursor is represented by a `|` character // Cursor is represented by a `|` character
@@ -11,12 +11,7 @@ export function getPositionFromCursor(input: string): [TextDocument, Position] {
} }
const position = doc.positionAt(cursorIndex); const position = doc.positionAt(cursorIndex);
const newDoc = TextDocument.create( const newDoc = TextDocument.create(doc.uri, doc.languageId, doc.version, doc.getText().replace("|", ""));
doc.uri,
doc.languageId,
doc.version,
doc.getText().replace("|", "")
);
return [newDoc, position]; return [newDoc, position];
} }
@@ -2,11 +2,11 @@ import {
TemplateToken, TemplateToken,
MAPPING_TYPE, MAPPING_TYPE,
SEQUENCE_TYPE, SEQUENCE_TYPE,
NULL_TYPE, NULL_TYPE
} from "@github/actions-workflow-parser/templates/tokens/index"; } 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 {SequenceToken} from "@github/actions-workflow-parser/templates/tokens/sequence-token";
import { Position } from "vscode-languageserver-textdocument"; import {Position} from "vscode-languageserver-textdocument";
export function findInnerToken(pos: Position, root?: TemplateToken) { export function findInnerToken(pos: Position, root?: TemplateToken) {
const [innerToken, _] = findInnerTokenAndParent(pos, root); const [innerToken, _] = findInnerTokenAndParent(pos, root);
@@ -41,7 +41,7 @@ export function findInnerTokenAndParent(
const mappingToken = token as MappingToken; const mappingToken = token as MappingToken;
parent = mappingToken; parent = mappingToken;
for (let i = 0; i < mappingToken.count; i++) { for (let i = 0; i < mappingToken.count; i++) {
const { key, value } = mappingToken.get(i); const {key, value} = mappingToken.get(i);
// Null tokens don't have a position, we can only use the line information // Null tokens don't have a position, we can only use the line information
if (nullNodeOnLine(pos, key, value)) { if (nullNodeOnLine(pos, key, value)) {
@@ -84,21 +84,14 @@ function posInToken(pos: Position, token: TemplateToken): boolean {
// Position is within the token lines. Check character/column if pos line matches // Position is within the token lines. Check character/column if pos line matches
// start or end // start or end
if ( if ((r.start[0] === tokenLine && tokenChar < r.start[1]) || (r.end[0] === tokenLine && tokenChar > r.end[1])) {
(r.start[0] === tokenLine && tokenChar < r.start[1]) ||
(r.end[0] === tokenLine && tokenChar > r.end[1])
) {
return false; return false;
} }
return true; return true;
} }
function nullNodeOnLine( function nullNodeOnLine(pos: Position, key: TemplateToken, value: TemplateToken): boolean {
pos: Position,
key: TemplateToken,
value: TemplateToken
): boolean {
if (value.templateTokenType !== NULL_TYPE) { if (value.templateTokenType !== NULL_TYPE) {
return false; return false;
} }
+8 -25
View File
@@ -1,13 +1,10 @@
import { Position, TextDocument } from "vscode-languageserver-textdocument"; import {Position, TextDocument} from "vscode-languageserver-textdocument";
const DUMMY_KEY = "dummy"; const DUMMY_KEY = "dummy";
// Transform a document to work around YAML parsing issues // Transform a document to work around YAML parsing issues
// Based on `_transform` in https://github.com/cschleiden/github-actions-parser/blob/main/src/lib/parser/complete.ts#L311 // Based on `_transform` in https://github.com/cschleiden/github-actions-parser/blob/main/src/lib/parser/complete.ts#L311
export function transform( export function transform(doc: TextDocument, pos: Position): [TextDocument, Position] {
doc: TextDocument,
pos: Position
): [TextDocument, Position] {
const input = doc.getText(); const input = doc.getText();
let offset = doc.offsetAt(pos); let offset = doc.offsetAt(pos);
// TODO: Optimize this... // TODO: Optimize this...
@@ -15,9 +12,8 @@ export function transform(
const lineNo = input const lineNo = input
.substring(0, offset) .substring(0, offset)
.split("") .split("")
.filter((x) => x === "\n").length; .filter(x => x === "\n").length;
const linePos = const linePos = offset - lines.slice(0, lineNo).reduce((p, l) => p + l.length + 1, 0);
offset - lines.slice(0, lineNo).reduce((p, l) => p + l.length + 1, 0);
const line = lines[lineNo]; const line = lines[lineNo];
let partialInput = line.trim(); let partialInput = line.trim();
@@ -37,11 +33,7 @@ export function transform(
} }
lines[lineNo] = lines[lineNo] =
line.substring(0, linePos) + line.substring(0, linePos) + spacer + DUMMY_KEY + (trimmedLine === "-" ? "" : ":") + line.substring(linePos);
spacer +
DUMMY_KEY +
(trimmedLine === "-" ? "" : ":") +
line.substring(linePos);
// Adjust pos by one to prevent a sequence node being marked as active // Adjust pos by one to prevent a sequence node being marked as active
offset++; offset++;
@@ -51,22 +43,13 @@ export function transform(
} }
if (trimmedLine.startsWith("-")) { if (trimmedLine.startsWith("-")) {
partialInput = trimmedLine partialInput = trimmedLine.substring(trimmedLine.indexOf("-") + 1).trim();
.substring(trimmedLine.indexOf("-") + 1)
.trim();
} }
} else { } else {
partialInput = ( partialInput = (offset > colon ? line.substring(colon + 1) : line.substring(0, colon)).trim();
offset > colon ? line.substring(colon + 1) : line.substring(0, colon)
).trim();
offset = offset - 1; offset = offset - 1;
} }
} }
const newDoc = TextDocument.create( const newDoc = TextDocument.create(doc.uri, doc.languageId, doc.version, lines.join("\n"));
doc.uri,
doc.languageId,
doc.version,
lines.join("\n")
);
return [newDoc, newDoc.positionAt(offset)]; return [newDoc, newDoc.positionAt(offset)];
} }
+17 -27
View File
@@ -1,6 +1,6 @@
import { parseWorkflow } from "@github/actions-workflow-parser"; import {parseWorkflow} from "@github/actions-workflow-parser";
import { TemplateValidationError } from "@github/actions-workflow-parser/templates/template-validation-error"; import {TemplateValidationError} from "@github/actions-workflow-parser/templates/template-validation-error";
import { nullTrace } from "./nulltrace"; import {nullTrace} from "./nulltrace";
describe("validation", () => { describe("validation", () => {
it("valid workflow", () => { it("valid workflow", () => {
@@ -9,8 +9,8 @@ describe("validation", () => {
[ [
{ {
name: "wf.yaml", name: "wf.yaml",
content: "on: push\njobs:\n build:\n runs-on: ubuntu-latest", content: "on: push\njobs:\n build:\n runs-on: ubuntu-latest"
}, }
], ],
nullTrace nullTrace
); );
@@ -24,23 +24,18 @@ describe("validation", () => {
[ [
{ {
name: "wf.yaml", name: "wf.yaml",
content: "on: push", content: "on: push"
}, }
], ],
nullTrace nullTrace
); );
expect(result.context.errors.getErrors().length).toBe(1); expect(result.context.errors.getErrors().length).toBe(1);
expect(result.context.errors.getErrors()[0]).toEqual( expect(result.context.errors.getErrors()[0]).toEqual(
new TemplateValidationError( new TemplateValidationError("Required property is missing: jobs", "wf.yaml (Line: 1, Col: 1)", undefined, {
"Required property is missing: jobs", start: [1, 1],
"wf.yaml (Line: 1, Col: 1)", end: [1, 9]
undefined, })
{
start: [1, 1],
end: [1, 9],
}
)
); );
}); });
@@ -56,23 +51,18 @@ jobs:
build: build:
runs-on: ubuntu-latest runs-on: ubuntu-latest
steps: steps:
- run: echo`, - run: echo`
}, }
], ],
nullTrace nullTrace
); );
expect(result.context.errors.getErrors().length).toBe(1); expect(result.context.errors.getErrors().length).toBe(1);
expect(result.context.errors.getErrors()[0]).toEqual( expect(result.context.errors.getErrors()[0]).toEqual(
new TemplateValidationError( new TemplateValidationError("Unexpected value 'unknown-key'", "wf.yaml (Line: 2, Col: 1)", undefined, {
"Unexpected value 'unknown-key'", start: [2, 1],
"wf.yaml (Line: 2, Col: 1)", end: [2, 12]
undefined, })
{
start: [2, 1],
end: [2, 12],
}
)
); );
}); });
}); });
+16 -24
View File
@@ -1,13 +1,9 @@
import { import {convertWorkflowTemplate, parseWorkflow, ParseWorkflowResult} from "@github/actions-workflow-parser";
convertWorkflowTemplate, import {TokenRange} from "@github/actions-workflow-parser/templates/tokens/token-range";
parseWorkflow, import {File} from "@github/actions-workflow-parser/workflows/file";
ParseWorkflowResult, import {TextDocument} from "vscode-languageserver-textdocument";
} from "@github/actions-workflow-parser"; import {Diagnostic, Range} from "vscode-languageserver-types";
import { TokenRange } from "@github/actions-workflow-parser/templates/tokens/token-range"; import {nullTrace} from "./nulltrace";
import { File } from "@github/actions-workflow-parser/workflows/file";
import { TextDocument } from "vscode-languageserver-textdocument";
import { Diagnostic, Range } from "vscode-languageserver-types";
import { nullTrace } from "./nulltrace";
/** /**
* Validates a workflow file * Validates a workflow file
@@ -21,15 +17,11 @@ export async function validate(
): Promise<Diagnostic[]> { ): Promise<Diagnostic[]> {
const file: File = { const file: File = {
name: textDocument.uri, name: textDocument.uri,
content: textDocument.getText(), content: textDocument.getText()
}; };
try { try {
const result: ParseWorkflowResult = parseWorkflow( const result: ParseWorkflowResult = parseWorkflow(file.name, [file], nullTrace);
file.name,
[file],
nullTrace
);
if (result.value) { if (result.value) {
// Errors will be updated in the context // Errors will be updated in the context
@@ -37,25 +29,25 @@ export async function validate(
} }
// For now map parser errors directly to diagnostics // For now map parser errors directly to diagnostics
return result.context.errors.getErrors().map((error) => { return result.context.errors.getErrors().map(error => {
let range = mapRange(error.range); let range = mapRange(error.range);
if (!range) { if (!range) {
// Use default range // Use default range
range = { range = {
start: { start: {
line: 1, line: 1,
character: 1, character: 1
}, },
end: { end: {
line: 1, line: 1,
character: 1, character: 1
}, }
}; };
} }
return { return {
message: error.rawMessage, message: error.rawMessage,
range, range
}; };
}); });
} catch (e) { } catch (e) {
@@ -72,11 +64,11 @@ function mapRange(range: TokenRange | undefined): Range | undefined {
return { return {
start: { start: {
line: range.start[0] - 1, line: range.start[0] - 1,
character: range.start[1] - 1, character: range.start[1] - 1
}, },
end: { end: {
line: range.end[0] - 1, line: range.end[0] - 1,
character: range.end[1] - 1, character: range.end[1] - 1
}, }
}; };
} }
@@ -9,16 +9,8 @@ export interface WorkflowContext {
uri: string; uri: string;
} }
export interface ValueProviderConfig { export interface ValueProviderConfig {
getCustomValues: ( getCustomValues: (key: string, context: WorkflowContext) => Promise<Value[] | undefined>;
key: string, getActionInputs?: (owner: string, name: string, ref: string, path?: string) => Promise<ActionInput[]>;
context: WorkflowContext
) => Promise<Value[] | undefined>;
getActionInputs?: (
owner: string,
name: string,
ref: string,
path?: string
) => Promise<ActionInput[]>;
} }
export interface ActionInput { export interface ActionInput {
@@ -1,14 +1,14 @@
import { Definition } from "@github/actions-workflow-parser/templates/schema/definition"; import {Definition} from "@github/actions-workflow-parser/templates/schema/definition";
import { BooleanDefinition } from "@github/actions-workflow-parser/templates/schema/boolean-definition"; import {BooleanDefinition} from "@github/actions-workflow-parser/templates/schema/boolean-definition";
import { MappingDefinition } from "@github/actions-workflow-parser/templates/schema/mapping-definition"; import {MappingDefinition} from "@github/actions-workflow-parser/templates/schema/mapping-definition";
import { OneOfDefinition } from "@github/actions-workflow-parser/templates/schema/one-of-definition"; import {OneOfDefinition} from "@github/actions-workflow-parser/templates/schema/one-of-definition";
import { getWorkflowSchema } from "@github/actions-workflow-parser/workflows/workflow-schema"; import {getWorkflowSchema} from "@github/actions-workflow-parser/workflows/workflow-schema";
import { Value, ValueProvider } from "./config"; import {Value, ValueProvider} from "./config";
export function defaultValueProviders(): { [key: string]: ValueProvider } { export function defaultValueProviders(): {[key: string]: ValueProvider} {
const schema = getWorkflowSchema(); const schema = getWorkflowSchema();
const map: { [key: string]: ValueProvider } = {}; const map: {[key: string]: ValueProvider} = {};
for (const key of Object.keys(schema.definitions)) { for (const key of Object.keys(schema.definitions)) {
const provider = definitionValueProvider(key, schema.definitions); const provider = definitionValueProvider(key, schema.definitions);
if (provider) { if (provider) {
@@ -30,15 +30,12 @@ export function defaultValueProviders(): { [key: string]: ValueProvider } {
"macos-10.15", "macos-10.15",
"macos-10.14", "macos-10.14",
"macos-10.13", "macos-10.13",
"self-hosted", "self-hosted"
]), ])
}; };
} }
function definitionValueProvider( function definitionValueProvider(key: string, definitions: {[key: string]: Definition}): ValueProvider | undefined {
key: string,
definitions: { [key: string]: Definition }
): ValueProvider | undefined {
const def = definitions[key]; const def = definitions[key];
if (def instanceof MappingDefinition) { if (def instanceof MappingDefinition) {
return mappingValueProvider(def); return mappingValueProvider(def);
@@ -49,20 +46,15 @@ function definitionValueProvider(
} }
} }
function mappingValueProvider( function mappingValueProvider(mappingDefinition: MappingDefinition): ValueProvider {
mappingDefinition: MappingDefinition
): ValueProvider {
const properties: Value[] = []; const properties: Value[] = [];
for (const [key, value] of Object.entries(mappingDefinition.properties)) { for (const [key, value] of Object.entries(mappingDefinition.properties)) {
properties.push({ label: key, description: value.description }); properties.push({label: key, description: value.description});
} }
return () => properties; return () => properties;
} }
function oneOfValueProvider( function oneOfValueProvider(oneOfDefinition: OneOfDefinition, definitions: {[key: string]: Definition}): ValueProvider {
oneOfDefinition: OneOfDefinition,
definitions: { [key: string]: Definition }
): ValueProvider {
return () => { return () => {
const values: Value[] = []; const values: Value[] = [];
for (const key of oneOfDefinition.oneOf) { for (const key of oneOfDefinition.oneOf) {
@@ -79,7 +71,7 @@ function oneOfValueProvider(
} }
function stringsToValues(labels: string[]): Value[] { function stringsToValues(labels: string[]): Value[] {
return labels.map((x) => ({ label: x })); return labels.map(x => ({label: x}));
} }
function distinctValues(values: Value[]): Value[] { function distinctValues(values: Value[]): Value[] {