Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic YAML language service implementation #50

Merged
merged 11 commits into from
Jul 2, 2022
4 changes: 2 additions & 2 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@ jobs:
- name: Lint Code
run: npm run lint
- name: Run server unit tests
run: npm run test-unit-server
run: npm run test-server
- name: Run client unit tests
run: npm run test-unit-client
run: npm run test-client
- name: Run integration tests
run: xvfb-run -a npm run test:e2e
if: runner.os == 'Linux'
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,8 +52,8 @@ The following table shows all the implemented features and the current support f
| [Validation](#workflow-validation) | ✔️ | 🔜 |
| [Documentation on Hover](#documentation-on-hover) | ✔️ | 🔜 |
| [IntelliSense](#intellisense) | ✔️ | 🔜 |
| [Formatting](#formatting) | ✔️ | 🔜 |
| [Custom Outline](#custom-outline) | ✔️ | 🔜 |
| [Formatting](#formatting) | ✔️ | ✔️ |
| [Custom Outline](#custom-outline) | ✔️ | ✔️ |
| [Workflow Cleanup Command](#workflow-cleanup-command) | ✔️ | ❔ |
| [Simplified Workflow Diffs](#simplified-workflow-diffs) | 🔶 | ❔ |

Expand Down
17 changes: 4 additions & 13 deletions client/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,15 @@
// https://jestjs.io/docs/en/configuration.html

module.exports = {
// A set of global variables that need to be available in all test environments
preset: "ts-jest",
globals: {
"ts-jest": {
tsconfig: "tsconfig.json",
tsconfig: "../tsconfig.json",
},
},

// An array of directory names to be searched recursively up from the requiring module's location
moduleDirectories: ["node_modules"],
// The glob patterns Jest uses to detect test files
testMatch: ["**/__tests__/*.+(ts|tsx|js)", "**/unit/*.test.ts"],

// An array of file extensions your modules use
moduleFileExtensions: ["ts", "tsx", "js"],

// The test environment that will be used for testing
testEnvironment: "node",

// A map from regular expressions to paths to transformers
transform: {
"^.+\\.(ts|tsx)$": "ts-jest",
},
};
2 changes: 1 addition & 1 deletion client/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,6 @@
"scripts": {
"webpack": "webpack",
"watch": "webpack --watch --progress",
"test-unit": "jest"
"test": "jest"
}
}
2 changes: 1 addition & 1 deletion client/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,5 +8,5 @@
"skipLibCheck": true
},
"include": ["tests"],
"exclude": ["node_modules", ".vscode-test-web"]
"exclude": ["node_modules"]
}
2 changes: 1 addition & 1 deletion docs/CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ You can run all the unit tests with:
npm test
```

Alternatively, you can choose to run only the [server](../server/tests/unit/) or the [client](../client/tests/unit/) tests using `npm run test-unit-server` or `npm run test-unit-client` respectively.
Alternatively, you can choose to run only the [server](../server/) or the [client](../client/tests/unit/) tests using `npm run test-server` or `npm run test-client` respectively.

The [integration or end to end (e2e) tests](../client/tests/e2e/suite/) will download (the first time) and launch a testing version of VSCode and then run the tests on it. You can run these tests with:

Expand Down
9 changes: 9 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,15 @@
// https://jestjs.io/docs/en/configuration.html

module.exports = {
preset: "ts-jest",
globals: {
"ts-jest": {
tsconfig: "tsconfig.json",
},
},
// The glob patterns Jest uses to detect test files
testMatch: ["**/__tests__/*.+(ts|tsx|js)", "**/unit/*.test.ts"],

// An array of file extensions your modules use
moduleFileExtensions: ["ts", "tsx", "js"],
};
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -213,9 +213,9 @@
"watch": "concurrently --kill-others \"npm run watch-server\" \"npm run watch-client\"",
"watch-server": "cd server && npm run watch",
"watch-client": "cd client && npm run watch",
"test": "npm run test-unit-client && npm run test-unit-server",
"test-unit-client": "cd client && npm run test-unit && cd ..",
"test-unit-server": "cd server && npm run test-unit && cd ..",
"test": "jest",
"test-client": "cd client && npm test",
"test-server": "cd server && npm test",
"test-compile": "tsc --project ./client --outDir client/out",
"pretest:e2e": "npm run compile && npm run test-compile",
"test:e2e": "node ./client/out/e2e/runTests.js"
Expand Down
63 changes: 8 additions & 55 deletions server/gx-workflow-ls-format2/src/gxFormat2WorkflowDocument.ts
Original file line number Diff line number Diff line change
@@ -1,64 +1,17 @@
import { ObjectASTNode } from "vscode-json-languageservice";
import { TextDocument, Range, Position, ASTNode, WorkflowDocument } from "@gxwf/server-common/src/languageTypes";
import { TextDocument, WorkflowDocument } from "@gxwf/server-common/src/languageTypes";
import { YAMLDocument } from "@gxwf/yaml-language-service/src";

/**
* This class provides information about a gxformat2 workflow document structure.
*/
export class GxFormat2WorkflowDocument extends WorkflowDocument {
constructor(textDocument: TextDocument) {
super(textDocument);
private _yamlDocument: YAMLDocument;
constructor(textDocument: TextDocument, yamlDocument: YAMLDocument) {
super(textDocument, yamlDocument);
this._yamlDocument = yamlDocument;
}

public get rootNode(): ASTNode | undefined {
return;
}

public override getNodeAtPosition(position: Position): ASTNode | undefined {
return;
}

public override getDocumentRange(): Range {
return Range.create(this.textDocument.positionAt(0), this.textDocument.positionAt(1));
}

public getNodeRange(node: ASTNode): Range {
return Range.create(
this.textDocument.positionAt(node.offset),
this.textDocument.positionAt(node.offset + node.length)
);
}

public getNodeRangeAtPosition(position: Position): Range {
const node = this.getNodeAtPosition(position);
return node ? this.getNodeRange(node) : this.getDefaultRangeAtPosition(position);
}

public isLastNodeInParent(node: ASTNode): boolean {
const parent = node.parent;
if (!parent || !parent.children) {
return true; // Must be root
}
const lastNode = parent.children[parent.children.length - 1];
return node === lastNode;
}

public getPreviousSiblingNode(node: ASTNode): ASTNode | null {
const parent = node.parent;
if (!parent || !parent.children) {
return null;
}
const previousNodeIndex = parent.children.indexOf(node) - 1;
if (previousNodeIndex < 0) {
return null;
}
return parent.children[previousNodeIndex];
}

public override getNodeFromPath(path: string): ASTNode | null {
return null;
}

public override getStepNodes(): ObjectASTNode[] {
return [];
public get yamlDocument(): YAMLDocument {
return this._yamlDocument;
}
}
10 changes: 6 additions & 4 deletions server/gx-workflow-ls-format2/src/languageService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,12 @@ export class GxFormat2WorkflowLanguageService extends WorkflowLanguageService {
}

public override parseWorkflowDocument(document: TextDocument): WorkflowDocument {
return new GxFormat2WorkflowDocument(document);
const yamlDocument = this._yamlLanguageService.parseYAMLDocument(document);
return new GxFormat2WorkflowDocument(document, yamlDocument);
}

public override format(document: TextDocument, range: Range, options: FormattingOptions): TextEdit[] {
return [];
public override format(document: TextDocument, _: Range, options: FormattingOptions): TextEdit[] {
return this._yamlLanguageService.doFormat(document, options);
}

public override async doHover(workflowDocument: WorkflowDocument, position: Position): Promise<Hover | null> {
Expand All @@ -44,6 +45,7 @@ export class GxFormat2WorkflowLanguageService extends WorkflowLanguageService {
}

protected override async doValidation(workflowDocument: WorkflowDocument): Promise<Diagnostic[]> {
return [];
const format2WorkflowDocument = workflowDocument as GxFormat2WorkflowDocument;
return this._yamlLanguageService.doValidation(format2WorkflowDocument.yamlDocument);
}
}
23 changes: 23 additions & 0 deletions server/gx-workflow-ls-format2/tests/testHelpers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { ASTNode } from "@gxwf/server-common/src/ast/types";
import { TextDocument } from "@gxwf/server-common/src/languageTypes";
import { getLanguageService, YAMLDocument } from "@gxwf/yaml-language-service/src";
import { GxFormat2WorkflowDocument } from "../src/gxFormat2WorkflowDocument";

export function toYamlDocument(contents: string): { textDoc: TextDocument; yamlDoc: YAMLDocument } {
const textDoc = TextDocument.create("foo://bar/file.gxwf.yaml", "gxformat2", 0, contents);

const ls = getLanguageService();
const yamlDoc = ls.parseYAMLDocument(textDoc) as YAMLDocument;
return { textDoc, yamlDoc };
}

export function getYamlDocumentRoot(contents: string): ASTNode {
const { yamlDoc } = toYamlDocument(contents);
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return yamlDoc.root!;
}

export function createFormat2WorkflowDocument(contents: string): GxFormat2WorkflowDocument {
const { textDoc, yamlDoc } = toYamlDocument(contents);
return new GxFormat2WorkflowDocument(textDoc, yamlDoc);
}
41 changes: 41 additions & 0 deletions server/gx-workflow-ls-format2/tests/unit/astUtils-yaml.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { getPropertyNodeFromPath } from "@gxwf/server-common/src/ast/utils";
import { getYamlDocumentRoot } from "../testHelpers";
import { expectPropertyNodeToHaveKey } from "@gxwf/server-common/tests/testHelpers";

describe("AST Utility Functions with YAML", () => {
describe("getPropertyNodeFromPath", () => {
describe("with valid path", () => {
it.each([
["key:\n key2: 0\n", "key"],
["key:\n key2: 0\n", "key/key2"],
["key:\n key2: 0\nkey3: val", "key3"],
["key:\n key2: 0}\n key3: val", "key/key3"],
["key:\n key2:\n key3: val", "key/key2"],
["key:\n key2:\n key3: val", "key/key2/key3"],
])("returns the expected property node at the given path", (contents: string, path: string) => {
const root = getYamlDocumentRoot(contents);
const pathItems = path.split("/");
const expectedPropertyKey = pathItems[pathItems.length - 1] as string;

const propertyNode = getPropertyNodeFromPath(root, path);

expectPropertyNodeToHaveKey(propertyNode, expectedPropertyKey);
});
});

describe("with invalid path", () => {
it.each([
["key:\n key2: 0\n", "key2"],
["key:\n key2: 0\n", "key3"],
["key:\n key2: 0\n", "key/5"],
["key:\n key2:\n key3: val", "key/key3"],
])("returns null", (contents: string, path: string) => {
const root = getYamlDocumentRoot(contents);

const propertyNode = getPropertyNodeFromPath(root, path);

expect(propertyNode).toBeNull();
});
});
});
});
25 changes: 0 additions & 25 deletions server/gx-workflow-ls-native/jest.config.js

This file was deleted.

81 changes: 3 additions & 78 deletions server/gx-workflow-ls-native/src/nativeWorkflowDocument.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { JSONDocument, ObjectASTNode } from "vscode-json-languageservice";
import { getPropertyNodeFromPath } from "./jsonUtils";
import { TextDocument, Range, Position, ASTNode, WorkflowDocument } from "@gxwf/server-common/src/languageTypes";
import { JSONDocument } from "vscode-json-languageservice";
import { TextDocument, WorkflowDocument } from "@gxwf/server-common/src/languageTypes";

/**
* This class provides information about a Native workflow document structure.
Expand All @@ -9,85 +8,11 @@ export class NativeWorkflowDocument extends WorkflowDocument {
private _jsonDocument: JSONDocument;

constructor(textDocument: TextDocument, jsonDocument: JSONDocument) {
super(textDocument);
super(textDocument, jsonDocument);
this._jsonDocument = jsonDocument;
}

public get jsonDocument(): JSONDocument {
return this._jsonDocument;
}

public get rootNode(): ASTNode | undefined {
return this._jsonDocument.root;
}

public override getNodeAtPosition(position: Position): ASTNode | undefined {
const offset = this.textDocument.offsetAt(position);
return this.jsonDocument.getNodeFromOffset(offset);
}

public override getDocumentRange(): Range {
const root = this.jsonDocument.root;
if (root) {
return Range.create(this.textDocument.positionAt(root.offset), this.textDocument.positionAt(root.length));
}
return Range.create(this.textDocument.positionAt(0), this.textDocument.positionAt(1));
}

public getNodeRange(node: ASTNode): Range {
return Range.create(
this.textDocument.positionAt(node.offset),
this.textDocument.positionAt(node.offset + node.length)
);
}

public getNodeRangeAtPosition(position: Position): Range {
const node = this.getNodeAtPosition(position);
return node ? this.getNodeRange(node) : this.getDefaultRangeAtPosition(position);
}

public isLastNodeInParent(node: ASTNode): boolean {
const parent = node.parent;
if (!parent || !parent.children) {
return true; // Must be root
}
const lastNode = parent.children[parent.children.length - 1];
return node === lastNode;
}

public getPreviousSiblingNode(node: ASTNode): ASTNode | null {
const parent = node.parent;
if (!parent || !parent.children) {
return null;
}
const previousNodeIndex = parent.children.indexOf(node) - 1;
if (previousNodeIndex < 0) {
return null;
}
return parent.children[previousNodeIndex];
}

public override getNodeFromPath(path: string): ASTNode | null {
const root = this._jsonDocument.root;
if (!root) return null;
return getPropertyNodeFromPath(root, path);
}

public override getStepNodes(): ObjectASTNode[] {
const root = this._jsonDocument.root;
if (!root) {
return [];
}
const result: ObjectASTNode[] = [];
const stepsNode = this.getNodeFromPath("steps");
if (stepsNode && stepsNode.type === "property" && stepsNode.valueNode && stepsNode.valueNode.type === "object") {
stepsNode.valueNode.properties.forEach((stepProperty) => {
const stepNode = stepProperty.valueNode;
if (stepNode && stepNode.type === "object") {
result.push(stepNode);
}
});
}
return result;
}
}
3 changes: 2 additions & 1 deletion server/gx-workflow-ls-native/tests/testHelpers.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { ASTNode, getLanguageService, JSONDocument } from "vscode-json-languageservice";
import { ASTNode } from "@gxwf/server-common/src/ast/types";
import { getLanguageService, JSONDocument } from "vscode-json-languageservice";
import { TextDocument } from "@gxwf/server-common/src/languageTypes";
import { NativeWorkflowDocument } from "../src/nativeWorkflowDocument";

Expand Down
Loading