Skip to content

Commit

Permalink
Add test showing that Go To Definition is broken on object properties
Browse files Browse the repository at this point in the history
  • Loading branch information
bentefay committed May 5, 2022
1 parent 2562f64 commit 3426c47
Show file tree
Hide file tree
Showing 6 changed files with 359 additions and 6 deletions.
52 changes: 52 additions & 0 deletions deno/lib/__tests__/languageServerFeatures.source.ts
@@ -0,0 +1,52 @@
import * as z from "../index.ts";

export const Test = z.object({
f1: z.number(),
f2: z.string().optional(),
f3: z.string().nullable(),
f4: z.array(z.object({ t: z.union([z.string(), z.boolean()]) })),
});

export type Test = z.infer<typeof Test>;

export const instanceOfTest: Test = {
f1: 1,
f2: "f2",
f3: "f3",
f4: [{ t: "f4" }, { t: true }],
};

export const TestMerged = z
.object({
f5: z.literal("literal").optional(),
})
.merge(Test);

export type TestMerged = z.infer<typeof TestMerged>;

export const instanceOfTestMerged: TestMerged = {
f1: 1,
f2: "f2",
f3: "f3",
f4: [{ t: "f4" }, { t: true }],
f5: "literal",
};

export const TestUnioned = z.union([
z.object({
f5: z.literal("literal").optional(),
}),
Test,
]);

export type TestUnioned = z.infer<typeof TestUnioned>;

export const instanceOfTestUnioned: TestUnioned = {
f1: 1,
f2: "f2",
f3: "f3",
f4: [{ t: "f4" }, { t: true }],
f5: "literal",
};

export const filePath = __filename;
100 changes: 100 additions & 0 deletions deno/lib/__tests__/languageServerFeatures.test.ts
@@ -0,0 +1,100 @@
// @ts-ignore TS6133
import { expect } from "https://deno.land/x/expect@v0.2.6/mod.ts";
const test = Deno.test;
import { filePath } from "./languageServerFeatures.source.ts";
import { Project, Node, SyntaxKind } from "ts-morph";
import path from "path";

// The following tool is helpful for understanding the TypeScript AST associated with these tests:
// https://ts-ast-viewer.com/ (just copy the contents of languageServerFeatures.source into the viewer)

describe("Executing Go To Definition (and therefore Find Usages and Rename Refactoring) using an IDE works on inferred object properties", () => {
// Compile file developmentEnvironment.source
const project = new Project({
tsConfigFilePath: path.join(__dirname, "..", "..", "tsconfig.json"),
skipAddingFilesFromTsConfig: true,
});
const sourceFile = project.addSourceFileAtPath(filePath);

test("works for simple objects", () => {
// Find usage of Test.f1 property
const instanceVariable =
sourceFile.getVariableDeclarationOrThrow("instanceOfTest");
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of Test.f1 property
const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
SyntaxKind.VariableDeclaration
);

// Assert that find definition returned the Zod definition of Test
expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
expect(parentOfProperty?.getName()).toEqual("Test");
});

test("works for merged objects", () => {
// Find usage of TestMerged.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestMerged"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestMerged.f1 property
const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
SyntaxKind.VariableDeclaration
);

// Assert that find definition returned the Zod definition of Test
expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
expect(parentOfProperty?.getName()).toEqual("Test");
});

test("works for unioned objects", () => {
// Find usage of TestUnioned.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestUnioned"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestUnioned.f1 property
const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
SyntaxKind.VariableDeclaration
);

// Assert that find definition returned the Zod definition of Test
expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
expect(parentOfProperty?.getName()).toEqual("Test");
});
});

const getPropertyBeingAssigned = (node: Node, name: string) => {
const propertyAssignment = node.forEachDescendant((descendent) =>
Node.isPropertyAssignment(descendent) && descendent.getName() == "f1"
? descendent
: undefined
);

if (propertyAssignment == null)
fail(`Could not find property assignment with name ${name}`);

const propertyLiteral = propertyAssignment.getFirstDescendantByKind(
SyntaxKind.Identifier
);

if (propertyLiteral == null)
fail(`Could not find property literal with name ${name}`);

return propertyLiteral;
};
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -87,6 +87,7 @@
"pretty-quick": "^3.1.3",
"rollup": "^2.70.1",
"ts-jest": "^27.1.3",
"ts-morph": "^14.0.0",
"ts-node": "^10.7.0",
"tslib": "^2.3.1",
"typescript": "^4.6.2"
Expand Down
52 changes: 52 additions & 0 deletions src/__tests__/languageServerFeatures.source.ts
@@ -0,0 +1,52 @@
import * as z from "../index";

export const Test = z.object({
f1: z.number(),
f2: z.string().optional(),
f3: z.string().nullable(),
f4: z.array(z.object({ t: z.union([z.string(), z.boolean()]) })),
});

export type Test = z.infer<typeof Test>;

export const instanceOfTest: Test = {
f1: 1,
f2: "f2",
f3: "f3",
f4: [{ t: "f4" }, { t: true }],
};

export const TestMerged = z
.object({
f5: z.literal("literal").optional(),
})
.merge(Test);

export type TestMerged = z.infer<typeof TestMerged>;

export const instanceOfTestMerged: TestMerged = {
f1: 1,
f2: "f2",
f3: "f3",
f4: [{ t: "f4" }, { t: true }],
f5: "literal",
};

export const TestUnioned = z.union([
z.object({
f5: z.literal("literal").optional(),
}),
Test,
]);

export type TestUnioned = z.infer<typeof TestUnioned>;

export const instanceOfTestUnioned: TestUnioned = {
f1: 1,
f2: "f2",
f3: "f3",
f4: [{ t: "f4" }, { t: true }],
f5: "literal",
};

export const filePath = __filename;
99 changes: 99 additions & 0 deletions src/__tests__/languageServerFeatures.test.ts
@@ -0,0 +1,99 @@
// @ts-ignore TS6133
import { expect, fit } from "@jest/globals";
import { filePath } from "./languageServerFeatures.source";
import { Project, Node, SyntaxKind } from "ts-morph";
import path from "path";

// The following tool is helpful for understanding the TypeScript AST associated with these tests:
// https://ts-ast-viewer.com/ (just copy the contents of languageServerFeatures.source into the viewer)

describe("Executing Go To Definition (and therefore Find Usages and Rename Refactoring) using an IDE works on inferred object properties", () => {
// Compile file developmentEnvironment.source
const project = new Project({
tsConfigFilePath: path.join(__dirname, "..", "..", "tsconfig.json"),
skipAddingFilesFromTsConfig: true,
});
const sourceFile = project.addSourceFileAtPath(filePath);

test("works for simple objects", () => {
// Find usage of Test.f1 property
const instanceVariable =
sourceFile.getVariableDeclarationOrThrow("instanceOfTest");
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of Test.f1 property
const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
SyntaxKind.VariableDeclaration
);

// Assert that find definition returned the Zod definition of Test
expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
expect(parentOfProperty?.getName()).toEqual("Test");
});

test("works for merged objects", () => {
// Find usage of TestMerged.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestMerged"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestMerged.f1 property
const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
SyntaxKind.VariableDeclaration
);

// Assert that find definition returned the Zod definition of Test
expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
expect(parentOfProperty?.getName()).toEqual("Test");
});

test("works for unioned objects", () => {
// Find usage of TestUnioned.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestUnioned"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestUnioned.f1 property
const definitionOfProperty = propertyBeingAssigned?.getDefinitionNodes()[0];
const parentOfProperty = definitionOfProperty?.getFirstAncestorByKind(
SyntaxKind.VariableDeclaration
);

// Assert that find definition returned the Zod definition of Test
expect(definitionOfProperty?.getText()).toEqual("f1: z.number()");
expect(parentOfProperty?.getName()).toEqual("Test");
});
});

const getPropertyBeingAssigned = (node: Node, name: string) => {
const propertyAssignment = node.forEachDescendant((descendent) =>
Node.isPropertyAssignment(descendent) && descendent.getName() == "f1"
? descendent
: undefined
);

if (propertyAssignment == null)
fail(`Could not find property assignment with name ${name}`);

const propertyLiteral = propertyAssignment.getFirstDescendantByKind(
SyntaxKind.Identifier
);

if (propertyLiteral == null)
fail(`Could not find property literal with name ${name}`);

return propertyLiteral;
};

0 comments on commit 3426c47

Please sign in to comment.