mirror of
https://gitee.com/zhangsken/opencode.git
synced 2026-10-16 10:30:01 +08:00
fix(codegen): preserve equivalent suffixed type references (#48024)
This commit is contained in:
@@ -46,7 +46,10 @@ export type EffectOutputType = {
|
||||
readonly import: string
|
||||
}
|
||||
|
||||
type ResolvedEffectTypeReference = Omit<EffectTypeReference, "schema"> & { readonly ast: SchemaAST.AST }
|
||||
type ResolvedEffectTypeReference = Omit<EffectTypeReference, "schema"> & {
|
||||
readonly ast: SchemaAST.AST
|
||||
readonly type: string | undefined
|
||||
}
|
||||
|
||||
export class GenerationError extends Schema.TaggedError<GenerationError>()("GenerationError", {
|
||||
reason: Schema.String,
|
||||
@@ -454,7 +457,6 @@ function effectTypeReferences(input: ReadonlyArray<EffectTypeReference>) {
|
||||
const asts = new Map<SchemaAST.AST, ResolvedEffectTypeReference>()
|
||||
const brands = new Map<string, ResolvedEffectTypeReference>()
|
||||
for (const reference of input) {
|
||||
const value = { name: reference.name, import: reference.import, ast: reference.schema.ast }
|
||||
const document = SchemaRepresentation.toCodeDocument(
|
||||
SchemaRepresentation.toRepresentations([codegenAst(Schema.toType(reference.schema).ast)]),
|
||||
)
|
||||
@@ -463,6 +465,7 @@ function effectTypeReferences(input: ReadonlyArray<EffectTypeReference>) {
|
||||
name === undefined
|
||||
? undefined
|
||||
: (document.references.nonRecursives.find((item) => item.$ref === name)?.code.Type ?? name)
|
||||
const value = { name: reference.name, import: reference.import, ast: reference.schema.ast, type }
|
||||
if (type?.includes("Brand.Brand<") && !brands.has(type)) brands.set(type, value)
|
||||
if (SchemaAST.resolveIdentifier(reference.schema.ast) !== undefined || type?.includes("Brand.Brand<")) {
|
||||
asts.set(reference.schema.ast, value)
|
||||
@@ -499,7 +502,10 @@ function effectType(schema: Schema.Top, references: ReturnType<typeof effectType
|
||||
"g",
|
||||
)
|
||||
if (!pattern.test(type)) continue
|
||||
const reference = references.names.get(name)
|
||||
// Optional/encoded occurrences can acquire a numeric suffix. Reuse the named
|
||||
// type only when its definition matches; a suffix can also denote a different shape.
|
||||
const candidate = references.names.get(name.replace(/_\d+$/, ""))
|
||||
const reference = references.names.get(name) ?? (candidate?.type === value ? candidate : undefined)
|
||||
if (reference !== undefined) {
|
||||
imports.add(reference.import)
|
||||
type = type.replace(pattern, reference.name)
|
||||
|
||||
@@ -143,6 +143,52 @@ describe("HttpApiCodegen.generate", () => {
|
||||
expect(source).not.toContain("@example/api")
|
||||
})
|
||||
|
||||
test("preserves named Effect references across optional schema occurrences", () => {
|
||||
const State = Schema.Record(Schema.String, Schema.Unknown).annotate({ identifier: "Message.State" })
|
||||
const output = emitEffectShape(
|
||||
compileContract(
|
||||
api(
|
||||
HttpApiEndpoint.get("get", "/session", {
|
||||
success: Schema.Struct({
|
||||
encoded: Schema.toEncoded(Schema.Struct({ state: Schema.optionalKey(State) })),
|
||||
optional: Schema.optional(State),
|
||||
}),
|
||||
}),
|
||||
),
|
||||
),
|
||||
{
|
||||
typeReferences: [
|
||||
{ schema: State, name: "Message.State", import: 'import type { Message } from "@example/schema/message"' },
|
||||
],
|
||||
},
|
||||
)
|
||||
const source = output.files[0]?.content
|
||||
expect(source).toContain('readonly "state"?: Message.State')
|
||||
expect(source).toContain('readonly "optional"?: Message.State | undefined')
|
||||
})
|
||||
|
||||
test("does not reuse named Effect references for different suffixed shapes", () => {
|
||||
const State = Schema.Record(Schema.String, Schema.Unknown).annotate({ identifier: "Message.State" })
|
||||
const Different = Schema.Record(Schema.String, Schema.Number).annotate({ identifier: "Message.State" })
|
||||
const output = emitEffectShape(
|
||||
compileContract(
|
||||
api(
|
||||
HttpApiEndpoint.get("get", "/session", {
|
||||
success: Schema.Struct({ original: State, different: Different }),
|
||||
}),
|
||||
),
|
||||
),
|
||||
{
|
||||
typeReferences: [
|
||||
{ schema: State, name: "Message.State", import: 'import type { Message } from "@example/schema/message"' },
|
||||
],
|
||||
},
|
||||
)
|
||||
const source = output.files[0]?.content
|
||||
expect(source).toContain('readonly "original": Message.State')
|
||||
expect(source).toContain('readonly "different": ({ readonly [x: string]: number })')
|
||||
})
|
||||
|
||||
test("allows composed Effect outputs to use an authoritative named type", () => {
|
||||
const output = emitEffectShape(
|
||||
compileContract(api(HttpApiEndpoint.get("events", "/event", { success: Schema.Unknown }))),
|
||||
|
||||
Reference in New Issue
Block a user