From b4019874b67ca5d318c9c4879717eeca6e5f9af1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 19:47:36 +0000 Subject: [PATCH 1/4] Initial plan From 1a2d6111eb812e4256cfa22f44552fd62e5f6113 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:16:27 +0000 Subject: [PATCH 2/4] feat(openapi3): Add support for importing deprecated directive from OpenAPI - Added TypeSpecDirective interface to represent directives - Added directives field to TypeSpecDeclaration and TypeSpecModelProperty - Created getDirectivesForSchema function to extract deprecated from OpenAPI schemas - Created generateDirectives function to output directive syntax (#deprecated) - Updated all model/scalar/enum/union generation to include directives - Added comprehensive tests for deprecated properties, models, scalars Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- .../convert/generators/generate-decorators.ts | 10 +- .../convert/generators/generate-model.ts | 14 +- .../src/cli/actions/convert/interfaces.ts | 7 + .../transforms/transform-component-schemas.ts | 7 +- .../cli/actions/convert/utils/decorators.ts | 18 +- .../tsp-openapi3/convert-openapi3-doc.test.ts | 207 ++++++++++++++++++ 6 files changed, 258 insertions(+), 5 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/generators/generate-decorators.ts b/packages/openapi3/src/cli/actions/convert/generators/generate-decorators.ts index 85e1e485117..30bc92d0874 100644 --- a/packages/openapi3/src/cli/actions/convert/generators/generate-decorators.ts +++ b/packages/openapi3/src/cli/actions/convert/generators/generate-decorators.ts @@ -1,6 +1,14 @@ -import { TSValue, TypeSpecDecorator } from "../interfaces.js"; +import { TSValue, TypeSpecDecorator, TypeSpecDirective } from "../interfaces.js"; import { stringLiteral } from "./common.js"; +function generateDirective({ name, message }: TypeSpecDirective): string { + return `#${name} ${stringLiteral(message)}`; +} + +export function generateDirectives(directives: TypeSpecDirective[] = []): string[] { + return directives.map(generateDirective); +} + function generateDecorator({ name, args }: TypeSpecDecorator): string { const hasArgs = args.length; const stringifiedArguments = hasArgs diff --git a/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts b/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts index 1bb9a5624e8..baba62a22f1 100644 --- a/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts +++ b/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts @@ -12,7 +12,7 @@ import { import { Context } from "../utils/context.js"; import { getDecoratorsForSchema } from "../utils/decorators.js"; import { generateDocs } from "../utils/docs.js"; -import { generateDecorators } from "./generate-decorators.js"; +import { generateDecorators, generateDirectives } from "./generate-decorators.js"; import { getTypeSpecPrimitiveFromSchema, isReferencedEnumType, @@ -52,6 +52,7 @@ function generateEnum(tsEnum: TypeSpecEnum): string { definitions.push(generateDocs(tsEnum.doc)); } + definitions.push(...generateDirectives(tsEnum.directives)); definitions.push(...generateDecorators(tsEnum.decorators)); definitions.push(`enum ${tsEnum.name} {`); @@ -73,6 +74,7 @@ function generateScalar(scalar: TypeSpecScalar, context: Context): string { definitions.push(generateDocs(scalar.doc)); } + definitions.push(...generateDirectives(scalar.directives)); definitions.push(...generateDecorators(scalar.decorators)); const type = context.generateTypeFromRefableSchema(scalar.schema, scalar.scope); @@ -231,6 +233,7 @@ function generateUnion(union: TypeSpecUnion, context: Context): string { definitions.push(generateDocs(union.doc)); } + definitions.push(...generateDirectives(union.directives)); definitions.push(...generateDecorators(union.decorators)); definitions.push(`union ${union.name} {`); @@ -331,6 +334,7 @@ function generateModel(model: TypeSpecModel, context: Context): string { definitions.push(generateDocs(model.doc)); } + definitions.push(...generateDirectives(model.directives)); definitions.push(...generateDecorators(model.decorators)); definitions.push(modelDeclaration.open); @@ -370,6 +374,9 @@ export function generateModelProperty( ): string { const propertyType = context.generateTypeFromRefableSchema(prop.schema, containerScope); + // Directives come before decorators + const directives = generateDirectives(prop.directives); + // Decorators will be a combination of top-level (parameters) and // schema-level decorators. const decorators = generateDecorators( @@ -384,7 +391,10 @@ export function generateModelProperty( const doc = prop.doc ? generateDocs(prop.doc) : ""; - return `${doc}${decorators} ${prop.name}${prop.isOptional ? "?" : ""}: ${context.getPartType(propertyType, prop.name, isModelReferencedAsMultipartRequestBody ?? false, encoding, isEnumType, isUnionType)};`; + // Format: doc, directives (each on new line), decorators (space separated), property definition + const directiveLines = directives.length > 0 ? directives.join("\n") + "\n" : ""; + + return `${doc}${directiveLines}${decorators} ${prop.name}${prop.isOptional ? "?" : ""}: ${context.getPartType(propertyType, prop.name, isModelReferencedAsMultipartRequestBody ?? false, encoding, isEnumType, isUnionType)};`; } export function generateModelExpression( diff --git a/packages/openapi3/src/cli/actions/convert/interfaces.ts b/packages/openapi3/src/cli/actions/convert/interfaces.ts index 43e51a4cb1e..2a1594647c0 100644 --- a/packages/openapi3/src/cli/actions/convert/interfaces.ts +++ b/packages/openapi3/src/cli/actions/convert/interfaces.ts @@ -39,9 +39,15 @@ export interface TypeSpecExternalDocs { description?: string; } +export interface TypeSpecDirective { + name: string; + message: string; +} + export interface TypeSpecDeclaration { name: string; doc?: string; + directives?: TypeSpecDirective[]; decorators: TypeSpecDecorator[]; scope: string[]; fixmes?: string[]; @@ -142,6 +148,7 @@ export interface TypeSpecModelProperty { name: string; isOptional: boolean; doc?: string; + directives?: TypeSpecDirective[]; /** * A partial list of decorators that can't be ascertained from * the schema. diff --git a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts index 8b39f98132f..30a851150e4 100644 --- a/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts +++ b/packages/openapi3/src/cli/actions/convert/transforms/transform-component-schemas.ts @@ -8,7 +8,7 @@ import { TypeSpecUnion, } from "../interfaces.js"; import { Context } from "../utils/context.js"; -import { getDecoratorsForSchema } from "../utils/decorators.js"; +import { getDecoratorsForSchema, getDirectivesForSchema } from "../utils/decorators.js"; import { getScopeAndName } from "../utils/get-scope-and-name.js"; /** @@ -78,6 +78,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData const tsEnum: TypeSpecEnum = { kind: "enum", ...getScopeAndName(name), + directives: getDirectivesForSchema(schema), decorators: getDecoratorsForSchema(schema), doc: schema.description, schema, @@ -109,6 +110,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData kind: "model", name, scope, + directives: [...getDirectivesForSchema(effectiveSchema)], decorators: [...getDecoratorsForSchema(effectiveSchema)], doc: effectiveSchema.description || schema.description, properties: [ @@ -154,6 +156,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData const union: TypeSpecUnion = { kind: "union", ...getScopeAndName(name), + directives: getDirectivesForSchema(schema), decorators, doc: schema.description ?? unionMetadata.description, schema, @@ -214,6 +217,7 @@ export function transformComponentSchemas(context: Context, models: TypeSpecData types.push({ kind: "scalar", ...getScopeAndName(name), + directives: getDirectivesForSchema(schema), decorators: getDecoratorsForSchema(schema), doc: schema.description, schema: "$ref" in schema ? {} : schema, @@ -346,6 +350,7 @@ function getModelPropertiesFromObjectSchema({ doc: property.description, schema: property, isOptional: !required.includes(name), + directives: [...getDirectivesForSchema(property)], decorators: [...getDecoratorsForSchema(property)], }); } diff --git a/packages/openapi3/src/cli/actions/convert/utils/decorators.ts b/packages/openapi3/src/cli/actions/convert/utils/decorators.ts index cf9a69aacd4..5f68e2122af 100644 --- a/packages/openapi3/src/cli/actions/convert/utils/decorators.ts +++ b/packages/openapi3/src/cli/actions/convert/utils/decorators.ts @@ -10,7 +10,7 @@ import { Refable, } from "../../../../types.js"; import { stringLiteral } from "../generators/common.js"; -import { TSValue, TypeSpecDecorator } from "../interfaces.js"; +import { TSValue, TypeSpecDecorator, TypeSpecDirective } from "../interfaces.js"; const validLocations = ["header", "query", "path"]; const extensionDecoratorName = "extension"; @@ -247,6 +247,22 @@ export function getDecoratorsForSchema( return decorators; } +export function getDirectivesForSchema( + schema: Refable, +): TypeSpecDirective[] { + const directives: TypeSpecDirective[] = []; + + if ("$ref" in schema) { + return directives; + } + + if (schema.deprecated) { + directives.push({ name: "deprecated", message: "deprecated" }); + } + + return directives; +} + function createTSValue(value: string): TSValue { return { __kind: "value", value }; } diff --git a/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts b/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts index c085bf9cbd4..fd08b2e2ba8 100644 --- a/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts +++ b/packages/openapi3/test/tsp-openapi3/convert-openapi3-doc.test.ts @@ -685,4 +685,211 @@ def grade(sample: dict, item: dict) -> float: "Should not parse JSON string as object literal. Got: " + tsp, ); }); + + it("should convert deprecated property to #deprecated directive", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "(title)", + version: "0.0.0", + }, + tags: [], + paths: {}, + components: { + schemas: { + Foo: { + type: "object", + properties: { + bar: { + type: "string", + deprecated: true, + }, + }, + }, + }, + }, + }); + + strictEqual( + tsp, + await formatTypeSpec( + ` + import "@typespec/http"; + import "@typespec/openapi"; + import "@typespec/openapi3"; + + using Http; + using OpenAPI; + + @service(#{ + title: "(title)", + }) + @info(#{ + version: "0.0.0", + }) + namespace title; + + model Foo { + #deprecated "deprecated" + bar?: string; + } + `, + { printWidth: 100, tabWidth: 2 }, + ), + ); + }); + + it("should convert deprecated property with description", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "(title)", + version: "0.0.0", + }, + tags: [], + paths: {}, + components: { + schemas: { + Foo: { + type: "object", + properties: { + bar: { + type: "string", + description: "This field is deprecated", + deprecated: true, + }, + }, + }, + }, + }, + }); + + strictEqual( + tsp, + await formatTypeSpec( + ` + import "@typespec/http"; + import "@typespec/openapi"; + import "@typespec/openapi3"; + + using Http; + using OpenAPI; + + @service(#{ + title: "(title)", + }) + @info(#{ + version: "0.0.0", + }) + namespace title; + + model Foo { + /** This field is deprecated */ + #deprecated "deprecated" + bar?: string; + } + `, + { printWidth: 100, tabWidth: 2 }, + ), + ); + }); + + it("should convert deprecated model to #deprecated directive", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "(title)", + version: "0.0.0", + }, + tags: [], + paths: {}, + components: { + schemas: { + Foo: { + type: "object", + deprecated: true, + properties: { + bar: { + type: "string", + }, + }, + }, + }, + }, + }); + + strictEqual( + tsp, + await formatTypeSpec( + ` + import "@typespec/http"; + import "@typespec/openapi"; + import "@typespec/openapi3"; + + using Http; + using OpenAPI; + + @service(#{ + title: "(title)", + }) + @info(#{ + version: "0.0.0", + }) + namespace title; + + #deprecated "deprecated" + model Foo { + bar?: string; + } + `, + { printWidth: 100, tabWidth: 2 }, + ), + ); + }); + + it("should convert deprecated scalar to #deprecated directive", async () => { + const tsp = await convertOpenAPI3Document({ + openapi: version, + info: { + title: "(title)", + version: "0.0.0", + }, + tags: [], + paths: {}, + components: { + schemas: { + Foo: { + type: "string", + deprecated: true, + }, + }, + }, + }); + + strictEqual( + tsp, + await formatTypeSpec( + ` + import "@typespec/http"; + import "@typespec/openapi"; + import "@typespec/openapi3"; + + using Http; + using OpenAPI; + + @service(#{ + title: "(title)", + }) + @info(#{ + version: "0.0.0", + }) + namespace title; + + #deprecated "deprecated" + scalar Foo extends string; + `, + { printWidth: 100, tabWidth: 2 }, + ), + ); + }); }); From df20484aa7983c59c636db35c2ef0880fa43cd6d Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:23:34 +0000 Subject: [PATCH 3/4] chore: fix formatting --- .../src/cli/actions/convert/generators/generate-model.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts b/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts index baba62a22f1..c30f4c11f40 100644 --- a/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts +++ b/packages/openapi3/src/cli/actions/convert/generators/generate-model.ts @@ -376,7 +376,7 @@ export function generateModelProperty( // Directives come before decorators const directives = generateDirectives(prop.directives); - + // Decorators will be a combination of top-level (parameters) and // schema-level decorators. const decorators = generateDecorators( @@ -393,7 +393,7 @@ export function generateModelProperty( // Format: doc, directives (each on new line), decorators (space separated), property definition const directiveLines = directives.length > 0 ? directives.join("\n") + "\n" : ""; - + return `${doc}${directiveLines}${decorators} ${prop.name}${prop.isOptional ? "?" : ""}: ${context.getPartType(propertyType, prop.name, isModelReferencedAsMultipartRequestBody ?? false, encoding, isEnumType, isUnionType)};`; } From c0deb2e85c64b05a5c2ea73a54bcfdef1caaf4cc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 5 Jan 2026 20:27:00 +0000 Subject: [PATCH 4/4] chore: add changelog entry for deprecated import feature Co-authored-by: baywet <7905502+baywet@users.noreply.github.com> --- ...t-fix-deprecated-decorators-import-2026-0-5-20-26-16.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .chronus/changes/copilot-fix-deprecated-decorators-import-2026-0-5-20-26-16.md diff --git a/.chronus/changes/copilot-fix-deprecated-decorators-import-2026-0-5-20-26-16.md b/.chronus/changes/copilot-fix-deprecated-decorators-import-2026-0-5-20-26-16.md new file mode 100644 index 00000000000..c146a5ff2e1 --- /dev/null +++ b/.chronus/changes/copilot-fix-deprecated-decorators-import-2026-0-5-20-26-16.md @@ -0,0 +1,7 @@ +--- +changeKind: feature +packages: + - "@typespec/openapi3" +--- + +Add support for importing deprecated properties and types from OpenAPI \ No newline at end of file