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 27663a9 commit 1c6d7ad
Show file tree
Hide file tree
Showing 6 changed files with 389 additions and 6 deletions.
46 changes: 46 additions & 0 deletions deno/lib/__tests__/languageServerFeatures.source.ts
@@ -0,0 +1,46 @@
import * as z from "../index.ts";

export const Test = z.object({
f1: z.number(),
});

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

export const instanceOfTest: Test = {
f1: 1,
};

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

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

export const instanceOfTestMerge: TestMerge = {
f1: 1,
};

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

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

export const instanceOfTestUnion: TestUnion = {
f1: 1,
};

export const TestPartial = Test.partial();

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

export const instanceOfTestPartial: TestPartial = {
f1: 1,
};

export const filePath = __filename;
121 changes: 121 additions & 0 deletions deno/lib/__tests__/languageServerFeatures.test.ts
@@ -0,0 +1,121 @@
// @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 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 TestMerge.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestMerge"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestMerge.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 TestUnion.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestUnion"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestUnion.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 partial objects", () => {
// Find usage of TestPartial.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestPartial"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestPartial.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() == name
? 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
46 changes: 46 additions & 0 deletions src/__tests__/languageServerFeatures.source.ts
@@ -0,0 +1,46 @@
import * as z from "../index";

export const Test = z.object({
f1: z.number(),
});

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

export const instanceOfTest: Test = {
f1: 1,
};

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

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

export const instanceOfTestMerge: TestMerge = {
f1: 1,
};

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

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

export const instanceOfTestUnion: TestUnion = {
f1: 1,
};

export const TestPartial = Test.partial();

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

export const instanceOfTestPartial: TestPartial = {
f1: 1,
};

export const filePath = __filename;
120 changes: 120 additions & 0 deletions src/__tests__/languageServerFeatures.test.ts
@@ -0,0 +1,120 @@
// @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 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 TestMerge.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestMerge"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestMerge.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 TestUnion.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestUnion"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestUnion.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 partial objects", () => {
// Find usage of TestPartial.f1 property
const instanceVariable = sourceFile.getVariableDeclarationOrThrow(
"instanceOfTestPartial"
);
const propertyBeingAssigned = getPropertyBeingAssigned(
instanceVariable,
"f1"
);

// Find definition of TestPartial.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() == name
? 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 1c6d7ad

Please sign in to comment.