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

Transform class references in connect-migrate #1342

Merged
merged 11 commits into from
Jan 8, 2025
Prev Previous commit
Add support for import aliases (#1399)
smaye81 authored Jan 8, 2025
commit baf921d265708a17dac69835f7b40a5fb2d93079
Original file line number Diff line number Diff line change
@@ -16,207 +16,436 @@ import { updateSourceFileInMemory } from "../lib/migrate-source-files";
import transform from "./v2.0.0-transform-class-refs";

describe("v2.0.0 transform class references", () => {
it("transforms new", () => {
const input = [`import {Foo} from "./x_pb.js";`, `new Foo();`].join("\n");
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
describe("new to create", () => {
it("transforms single import", () => {
const input = [`import {Foo} from "./x_pb.js";`, `new Foo();`].join("\n");
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms multiple imports", () => {
const input = [
`import {Foo, Bar} from "./x_pb.js";`,
`new Foo();`,
`new Bar();`,
].join("\n");
const output = [
`import { FooSchema, BarSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(FooSchema);`,
`create(BarSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms single import alias", () => {
const input = [
`import {Foo as MyFoo} from "./x_pb.js";`,
`new MyFoo();`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(MyFooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms multiple import aliases", () => {
const input = [
`import {Foo as MyFoo, Bar as MyBar} from "./x_pb.js";`,
`new MyFoo();`,
`new MyBar();`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema, BarSchema as MyBarSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(MyFooSchema);`,
`create(MyBarSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms mix of regular and import aliases", () => {
const input = [
`import {Foo as MyFoo, Bar} from "./x_pb.js";`,
`new MyFoo();`,
`new Bar();`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema, BarSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(MyFooSchema);`,
`create(BarSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms new with init object", () => {
const input = [
`import {Foo} from "./x_pb.js";`,
`new Foo({x:123});`,
].join("\n");
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(FooSchema, {x:123});`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("transforms new with init object and import alias", () => {
const input = [
`import {Foo as MyFoo} from "./x_pb.js";`,
`new MyFoo({x:123});`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(MyFooSchema, {x:123});`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("adds type import for single import", () => {
const input = [
`import {Foo} from "./x_pb.js";`,
`const foo: Foo = new Foo();`,
].join("\n");
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Foo } from "./x_pb.js";`,
`const foo: Foo = create(FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import for multiple imports", () => {
const input = [
`import {Foo, Bar} from "./x_pb.js";`,
`const foo: Foo = new Foo();`,
`const bar: Bar = new Bar();`,
].join("\n");
const output = [
`import { FooSchema, BarSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Foo, Bar } from "./x_pb.js";`,
`const foo: Foo = create(FooSchema);`,
`const bar: Bar = create(BarSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import for multiple import aliases", () => {
const input = [
`import {Foo as MyFoo, Bar as SomeBar} from "./x_pb.js";`,
`const foo: MyFoo = new MyFoo();`,
`const bar: SomeBar = new SomeBar();`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema, BarSchema as SomeBarSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Foo as MyFoo, Bar as SomeBar } from "./x_pb.js";`,
`const foo: MyFoo = create(MyFooSchema);`,
`const bar: SomeBar = create(SomeBarSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import for multiple imports with mix of aliases and regular", () => {
const input = [
`import {Foo as MyFoo, Bar} from "./x_pb.js";`,
`const foo: MyFoo = new MyFoo();`,
`const bar: Bar = new Bar();`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema, BarSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Foo as MyFoo, Bar } from "./x_pb.js";`,
`const foo: MyFoo = create(MyFooSchema);`,
`const bar: Bar = create(BarSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("does not transform when identifier is not imported from _pb", () => {
const input = `import {Foo} from "./x.js"; new Foo()`;
const output = `import {Foo} from "./x.js"; new Foo()`;
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("does not add type import to .js file", () => {
const input = [
`import {Foo} from "./x_pb.js";`,
`const foo = new Foo();`,
`foo instanceof Foo;`,
].join("\n");
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`const foo = create(FooSchema);`,
`foo instanceof Foo;`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.js");
expect(result.source).toEqual(output);
});
it("adds create import to existing @bufbuild/protobuf import", () => {
const input = [
`import {fake} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`new Foo();`,
].join("\n");
const output = [
`import { fake, create } from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`create(FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
});
it("transforms new with init object", () => {
const input = [`import {Foo} from "./x_pb.js";`, `new Foo({x:123});`].join(
"\n",
);
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`create(FooSchema, {x:123});`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("adds type import when transforming new", () => {
const input = [
`import {Foo} from "./x_pb.js";`,
`const foo: Foo = new Foo();`,
].join("\n");
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Foo } from "./x_pb.js";`,
`const foo: Foo = create(FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("does not add type import to .js file when transforming new", () => {
const input = [
`import {Foo} from "./x_pb.js";`,
`const foo = new Foo();`,
`foo instanceof Foo;`,
].join("\n");
const output = [
`import { FooSchema } from "./x_pb.js";`,
`import { create } from "@bufbuild/protobuf";`,
`const foo = create(FooSchema);`,
`foo instanceof Foo;`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.js");
expect(result.source).toEqual(output);
});
it("adds create import to existing when transforming new", () => {
const input = [
`import {fake} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`new Foo();`,
].join("\n");
const output = [
`import { fake, create } from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`create(FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms isMessage()", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1, Foo);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`isMessage(1, FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("does not transform isMessage() without schema argument", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import when transforming isMessage()", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`type X = Foo;`,
`isMessage(1, Foo);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`import type { Foo } from "./x_pb.js";`,
`type X = Foo;`,
`isMessage(1, FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("does not add type import to .ts file when transforming isMessage()", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1, Foo);`,
`foo instanceof Foo;`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`isMessage(1, FooSchema);`,
`foo instanceof Foo;`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.js");
expect(result.source).toEqual(output);
});
it("transforms wkt", () => {
const input = [
`import {Timestamp} from "@bufbuild/protobuf";`,
`new Timestamp();`,
].join("\n");
const output = [
`import { TimestampSchema } from "@bufbuild/protobuf/wkt";`,
`import { create } from "@bufbuild/protobuf";`,
`create(TimestampSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import when transforming wkt", () => {
const input = [
`import {Timestamp} from "@bufbuild/protobuf";`,
`const ts: Timestamp = new Timestamp();`,
].join("\n");
const output = [
`import { TimestampSchema } from "@bufbuild/protobuf/wkt";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Timestamp } from "@bufbuild/protobuf/wkt";`,
`const ts: Timestamp = create(TimestampSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("does not transform new when identifier is not imported from _pb", () => {
const input = `import {Foo} from "./x.js"; new Foo()`;
const output = `import {Foo} from "./x.js"; new Foo()`;
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("does not transform isMessage when identifier is not imported from _pb", () => {
const input = `import {Foo} from "./x.js"; isMessage(1, Foo);`;
const output = `import {Foo} from "./x.js"; isMessage(1, Foo);`;
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("transforms static fromBinary call", () => {
const input = [
`import {Foo} from "./foo_pb";`,
`Foo.fromBinary(x, y);`,
].join("\n");
const output = [
`import { FooSchema } from "./foo_pb";`,
`import { fromBinary } from "@bufbuild/protobuf";`,
`fromBinary(FooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms static fromJson call", () => {
const input = [`import {Foo} from "./foo_pb";`, `Foo.fromJson(x, y);`].join(
"\n",
);
const output = [
`import { FooSchema } from "./foo_pb";`,
`import { fromJson } from "@bufbuild/protobuf";`,
`fromJson(FooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms static fromJsonString call", () => {
const input = [
`import {Foo} from "./foo_pb";`,
`Foo.fromJsonString(x, y);`,
].join("\n");
const output = [
`import { FooSchema } from "./foo_pb";`,
`import { fromJsonString } from "@bufbuild/protobuf";`,
`fromJsonString(FooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);

describe("isMessage()", () => {
it("transforms with schema argument", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1, Foo);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`isMessage(1, FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms with schema argument and import alias", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo as MyFoo} from "./x_pb.js";`,
`isMessage(1, MyFoo);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema as MyFooSchema } from "./x_pb.js";`,
`isMessage(1, MyFooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("does not transform without schema argument", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`type X = Foo;`,
`isMessage(1, Foo);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`import type { Foo } from "./x_pb.js";`,
`type X = Foo;`,
`isMessage(1, FooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import with import alias", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo as MyFoo} from "./x_pb.js";`,
`type X = MyFoo;`,
`isMessage(1, MyFoo);`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema as MyFooSchema } from "./x_pb.js";`,
`import type { Foo as MyFoo } from "./x_pb.js";`,
`type X = MyFoo;`,
`isMessage(1, MyFooSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("does not transform isMessage when identifier is not imported from _pb", () => {
const input = `import {Foo} from "./x.js"; isMessage(1, Foo);`;
const output = `import {Foo} from "./x.js"; isMessage(1, Foo);`;
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("does not add type import to .js file", () => {
const input = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import {Foo} from "./x_pb.js";`,
`isMessage(1, Foo);`,
`foo instanceof Foo;`,
].join("\n");
const output = [
`import {isMessage} from "@bufbuild/protobuf";`,
`import { FooSchema } from "./x_pb.js";`,
`isMessage(1, FooSchema);`,
`foo instanceof Foo;`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.js");
expect(result.source).toEqual(output);
});
describe("update wkt import", () => {
it("regular import", () => {
const input = [
`import {Timestamp} from "@bufbuild/protobuf";`,
`new Timestamp();`,
].join("\n");
const output = [
`import { TimestampSchema } from "@bufbuild/protobuf/wkt";`,
`import { create } from "@bufbuild/protobuf";`,
`create(TimestampSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("import alias", () => {
const input = [
`import {Timestamp as TS} from "@bufbuild/protobuf";`,
`new TS();`,
].join("\n");
const output = [
`import { TimestampSchema as TSSchema } from "@bufbuild/protobuf/wkt";`,
`import { create } from "@bufbuild/protobuf";`,
`create(TSSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import", () => {
const input = [
`import {Timestamp} from "@bufbuild/protobuf";`,
`const ts: Timestamp = new Timestamp();`,
].join("\n");
const output = [
`import { TimestampSchema } from "@bufbuild/protobuf/wkt";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Timestamp } from "@bufbuild/protobuf/wkt";`,
`const ts: Timestamp = create(TimestampSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("adds type import for multiple import aliases", () => {
const input = [
`import {Timestamp as TS, Duration as Dur} from "@bufbuild/protobuf";`,
`const ts: TS = new TS();`,
`const duration: Dur = new Dur();`,
].join("\n");
const output = [
`import { TimestampSchema as TSSchema, DurationSchema as DurSchema } from "@bufbuild/protobuf/wkt";`,
`import { create } from "@bufbuild/protobuf";`,
`import type { Timestamp as TS, Duration as Dur } from "@bufbuild/protobuf/wkt";`,
`const ts: TS = create(TSSchema);`,
`const duration: Dur = create(DurSchema);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
});
describe("static methods", () => {
it("transforms fromBinary call", () => {
const input = [
`import {Foo} from "./foo_pb";`,
`Foo.fromBinary(x, y);`,
].join("\n");
const output = [
`import { FooSchema } from "./foo_pb";`,
`import { fromBinary } from "@bufbuild/protobuf";`,
`fromBinary(FooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms fromBinary call for alias", () => {
const input = [
`import {Foo as MyFoo} from "./foo_pb";`,
`MyFoo.fromBinary(x, y);`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema } from "./foo_pb";`,
`import { fromBinary } from "@bufbuild/protobuf";`,
`fromBinary(MyFooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms fromJson call", () => {
const input = [
`import {Foo} from "./foo_pb";`,
`Foo.fromJson(x, y);`,
].join("\n");
const output = [
`import { FooSchema } from "./foo_pb";`,
`import { fromJson } from "@bufbuild/protobuf";`,
`fromJson(FooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms fromJson call for alias", () => {
const input = [
`import {Foo as MyFoo} from "./foo_pb";`,
`MyFoo.fromJson(x, y);`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema } from "./foo_pb";`,
`import { fromJson } from "@bufbuild/protobuf";`,
`fromJson(MyFooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms fromJsonString call", () => {
const input = [
`import {Foo} from "./foo_pb";`,
`Foo.fromJsonString(x, y);`,
].join("\n");
const output = [
`import { FooSchema } from "./foo_pb";`,
`import { fromJsonString } from "@bufbuild/protobuf";`,
`fromJsonString(FooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
it("transforms fromJsonString call for alias", () => {
const input = [
`import {Foo as MyFoo} from "./foo_pb";`,
`MyFoo.fromJsonString(x, y);`,
].join("\n");
const output = [
`import { FooSchema as MyFooSchema } from "./foo_pb";`,
`import { fromJsonString } from "@bufbuild/protobuf";`,
`fromJsonString(MyFooSchema, x, y);`,
].join("\n");
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toEqual(output);
});
});
});
});
150 changes: 125 additions & 25 deletions packages/connect-migrate/src/migrations/v2.0.0-transform-class-refs.ts
Original file line number Diff line number Diff line change
@@ -152,12 +152,18 @@ const transform: j.Transform = (file, { j }, options) => {
findPbImports(name, root).forEach((path) => {
path.replace(
j.importDeclaration(
path.value.specifiers?.map((specifier) =>
specifier.type === "ImportSpecifier" &&
specifier.imported.name === name
? j.importSpecifier(j.identifier(name + "Schema"))
: specifier,
),
path.value.specifiers?.map((specifier) => {
if (
specifier.type !== "ImportSpecifier" ||
!namesEqual(name, specifier)
) {
return specifier;
}
return j.importSpecifier(
j.identifier(`${specifier.imported.name}Schema`),
j.identifier(`${specifier.local?.name}Schema`),
);
}),
path.value.source,
path.value.importKind,
),
@@ -171,15 +177,69 @@ const transform: j.Transform = (file, { j }, options) => {
if (root.find(j.Identifier, { name }).size() > 0) {
const pbImports = findPbImports(name + "Schema", root);
const firstImport = pbImports.at(0);
const from = (firstImport.get() as j.ASTPath<j.ImportDeclaration>).value
.source;
firstImport.insertAfter(
j.importDeclaration(
[j.importSpecifier(j.identifier(name))],
from,
"type",
),
);
const from = firstImport.get() as j.ASTPath<j.ImportDeclaration>;
const fromSource = from.value.source;

// Search and see if this name is in the root yet. If it is, replace it.
// If not, insert it
// This is so we end up with:
// import type { Foo, Bar } from “./x.js”;
// instead of:
// import type { Foo } from “./x.js”;
// import type { Bar } from “./x.js”;
const typeImports = root.find(j.ImportDeclaration, {
source: {
type: "StringLiteral",
value: fromSource.value as string,
},
importKind: "type",
});
// If the type import for this source file isn't in the root yet, add it
const importDecl = findImportByName(name + "Schema", root);
if (importDecl !== undefined) {
const importSpecs =
importDecl.value.specifiers?.map((specifier) => {
if (specifier.type !== "ImportSpecifier") {
return specifier;
}
const localMinusSchema = removeSchemaSuffix(
specifier.local?.name,
);
const importedMinusSchema = removeSchemaSuffix(
specifier.imported.name,
);

return j.importSpecifier(
j.identifier(`${importedMinusSchema}`),
j.identifier(`${localMinusSchema}`),
);
}) ?? [];

if (typeImports.length === 0) {
firstImport.insertAfter(
j.importDeclaration(importSpecs, fromSource, "type"),
);
} else {
typeImports.forEach((path) => {
const specs =
path.value.specifiers?.map((specifier) => {
if (specifier.type !== "ImportSpecifier") {
return specifier;
}
return j.importSpecifier(
j.identifier(`${specifier.imported.name}`),
j.identifier(`${specifier.local?.name}`),
);
}) ?? [];

specs.concat(importSpecs);

path.replace(
j.importDeclaration(specs, path.value.source, "type"),
);
});
}
}
}
}
}
@@ -242,10 +302,45 @@ const transform: j.Transform = (file, { j }, options) => {
);
};

function findImportByName(name: string, root: j.Collection) {
const result = root
.find(j.ImportDeclaration, {
specifiers: [
{
type: "ImportSpecifier",
},
],
})
.filter((path) => {
return (
path.value.specifiers?.some((specifier) =>
namesEqual(name, specifier as j.ImportSpecifier),
) ?? false
);
});
if (result.length === 0) {
return undefined;
}
return result.at(0).get() as j.ASTPath<j.ImportDeclaration>;
}

function namesEqual(name: string, importSpecifier: j.ImportSpecifier) {
if (importSpecifier.local?.name !== importSpecifier.imported.name) {
// This is an import alias, so use the localName for comparison
return importSpecifier.local?.name === name;
}

return importSpecifier.imported.name === name;
}

function removeSchemaSuffix(name: string | undefined) {
if (name === undefined) {
return "";
}
return name.endsWith("Schema") ? name.substring(0, name.length - 6) : name;
}

function findPbImports(name: string, root: j.Collection) {
const nameMinusSchema = name.endsWith("Schema")
? name.substring(0, name.length - 6)
: name;
return root
.find(j.ImportDeclaration, {
specifiers: [
@@ -255,22 +350,27 @@ function findPbImports(name: string, root: j.Collection) {
],
})
.filter((path) => {
const containsName =
path.value.specifiers?.some(
(specifier) =>
(specifier as j.ImportSpecifier).imported.name === name,
) ?? false;
if (!containsName) {
// First make sure that the given name is in the root doc
const found = path.value.specifiers?.find((specifier) => {
return namesEqual(name, specifier as j.ImportSpecifier);
});
if (!found) {
return false;
}
const from = path.value.source.value;
if (typeof from !== "string") {
return false;
}

// The given name may be an alias, so to test whether this is fromWkt,
// we need to use the imported name to compare against the list of wkt
// imports
const importedName = (found as j.ImportSpecifier).imported.name;
const importedNameMinusSchema = removeSchemaSuffix(importedName);
const fromIsWkt =
(from === bufbuildProtobufPackage ||
from === bufbuildProtobufPackage + "/wkt") &&
(isWktMessage(name) || isWktMessage(nameMinusSchema));
(isWktMessage(importedName) || isWktMessage(importedNameMinusSchema));
const fromIsPb =
from.endsWith("_pb") ||
from.endsWith("_pb.js") ||
Original file line number Diff line number Diff line change
@@ -46,6 +46,12 @@ describe("v2.0.0 transform connect plugin imports", () => {
const result = updateSourceFileInMemory(transform, input, "foo.tsx");
expect(result.source).toBe(output);
});
it("should modify import alias", () => {
const input = `import { ElizaService as MyEliza } from "./gen/eliza_connect.js";`;
const output = `import { ElizaService as MyEliza } from "./gen/eliza_pb.js";`;
const result = updateSourceFileInMemory(transform, input, "foo.ts");
expect(result.source).toBe(output);
});
it("should not care about existing imports", () => {
const input = `
import { ElizaService } from "./gen/eliza_connect.js";