Skip to content

Commit

Permalink
Merge pull request #50 from davelopez/basic_yaml_language_service
Browse files Browse the repository at this point in the history
Basic YAML language service implementation
  • Loading branch information
davelopez authored Jul 2, 2022
2 parents bb0b5a0 + 8cfab29 commit 05ed58d
Show file tree
Hide file tree
Showing 50 changed files with 1,280 additions and 397 deletions.
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

0 comments on commit 05ed58d

Please sign in to comment.