From 75e773cfa897c4fd538acaa5e188ae12bd7fa1ae Mon Sep 17 00:00:00 2001 From: Timothee Guerin Date: Mon, 12 Aug 2024 12:16:21 -0700 Subject: [PATCH 1/2] Add new way to define decorator implementation with `$decorators` export (#4139) fix [#3719](https://github.com/microsoft/typespec/issues/3719) --- .../decorators-export-2024-7-12-2-29-0.md | 8 + .../decorators-export-2024-7-9-21-2-11.md | 16 + .../decorators-export-2024-7-9-22-3-58.md | 15 + docs/extending-typespec/create-decorators.md | 16 +- packages/bundler/src/bundler.ts | 7 +- packages/compiler/generated-defs/TypeSpec.ts | 45 +++ .../generated-defs/TypeSpec.ts-test.ts | 181 +-------- packages/compiler/lib/std/decorators.tsp | 2 +- packages/compiler/src/core/binder.ts | 140 ++++--- packages/compiler/src/core/types.ts | 19 + packages/compiler/src/index.ts | 3 + packages/compiler/src/lib/tsp-index.ts | 98 +++++ packages/compiler/test/binder.test.ts | 33 ++ .../compiler/test/checker/decorators.test.ts | 98 +++-- .../generated-defs/TypeSpec.Http.Private.ts | 6 + packages/http/generated-defs/TypeSpec.Http.ts | 22 ++ .../generated-defs/TypeSpec.Http.ts-test.ts | 89 +---- packages/http/lib/http.tsp | 2 +- packages/http/src/index.ts | 5 +- packages/http/src/private.decorators.ts | 22 +- packages/http/src/tsp-index.ts | 50 +++ .../TypeSpec.JsonSchema.Private.ts | 4 + .../generated-defs/TypeSpec.JsonSchema.ts | 19 + .../TypeSpec.JsonSchema.ts-test.ts | 77 +--- packages/json-schema/lib/main.tsp | 2 +- packages/json-schema/src/decorators.ts | 324 ++++++++++++++++ packages/json-schema/src/index.ts | 345 +----------------- packages/json-schema/src/on-emit.ts | 25 ++ packages/json-schema/src/tsp-index.ts | 48 +++ .../generated-defs/TypeSpec.OpenAPI.ts | 8 + .../TypeSpec.OpenAPI.ts-test.ts | 33 +- packages/openapi/lib/main.tsp | 2 +- packages/openapi/src/decorators.ts | 14 +- packages/openapi/src/lib.ts | 7 +- packages/openapi/src/tsp-index.ts | 15 + .../generated-defs/TypeSpec.OpenAPI.ts | 5 + .../TypeSpec.OpenAPI.ts-test.ts | 15 +- packages/openapi3/lib/decorators.tsp | 2 +- packages/openapi3/src/index.ts | 1 + packages/openapi3/src/tsp-index.ts | 12 + .../TypeSpec.Protobuf.Private.ts | 15 + .../generated-defs/TypeSpec.Protobuf.ts | 9 + .../TypeSpec.Protobuf.ts-test.ts | 30 +- packages/protobuf/lib/proto.tsp | 11 +- packages/protobuf/src/index.ts | 2 + packages/protobuf/src/proto.ts | 15 +- packages/protobuf/src/tsp-index.ts | 29 ++ .../generated-defs/TypeSpec.Rest.Private.ts | 8 + packages/rest/generated-defs/TypeSpec.Rest.ts | 19 + .../generated-defs/TypeSpec.Rest.ts-test.ts | 77 +--- packages/rest/lib/resource.tsp | 1 - packages/rest/lib/rest.tsp | 1 + packages/rest/src/index.ts | 6 +- packages/rest/src/internal-decorators.ts | 20 +- packages/rest/src/lib.ts | 9 +- packages/rest/src/tsp-index.ts | 43 +++ .../decorators-signatures.ts | 65 ++-- .../gen-extern-signatures.ts | 4 +- .../decorators-signatures.test.ts | 52 +++ packages/tspd/tsconfig.json | 3 +- .../generated-defs/TypeSpec.Versioning.ts | 12 + .../TypeSpec.Versioning.ts-test.ts | 49 +-- packages/versioning/src/index.ts | 3 + packages/versioning/src/lib.ts | 8 +- packages/versioning/src/tsp-index.ts | 28 ++ packages/xml/generated-defs/TypeSpec.Xml.ts | 8 + .../generated-defs/TypeSpec.Xml.ts-test.ts | 27 +- packages/xml/src/decorators.ts | 1 + packages/xml/src/index.ts | 4 + packages/xml/src/lib.ts | 8 +- packages/xml/src/tsp-index.ts | 15 + 71 files changed, 1348 insertions(+), 1069 deletions(-) create mode 100644 .chronus/changes/decorators-export-2024-7-12-2-29-0.md create mode 100644 .chronus/changes/decorators-export-2024-7-9-21-2-11.md create mode 100644 .chronus/changes/decorators-export-2024-7-9-22-3-58.md create mode 100644 packages/compiler/src/lib/tsp-index.ts create mode 100644 packages/http/src/tsp-index.ts create mode 100644 packages/json-schema/src/decorators.ts create mode 100644 packages/json-schema/src/on-emit.ts create mode 100644 packages/json-schema/src/tsp-index.ts create mode 100644 packages/openapi/src/tsp-index.ts create mode 100644 packages/openapi3/src/tsp-index.ts create mode 100644 packages/protobuf/generated-defs/TypeSpec.Protobuf.Private.ts create mode 100644 packages/protobuf/src/tsp-index.ts create mode 100644 packages/rest/src/tsp-index.ts create mode 100644 packages/versioning/src/tsp-index.ts create mode 100644 packages/xml/src/tsp-index.ts diff --git a/.chronus/changes/decorators-export-2024-7-12-2-29-0.md b/.chronus/changes/decorators-export-2024-7-12-2-29-0.md new file mode 100644 index 00000000000..173235ab8bb --- /dev/null +++ b/.chronus/changes/decorators-export-2024-7-12-2-29-0.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/bundler" +--- + +Allow bundling libraries that don't import their `main` file from the TypeSpec diff --git a/.chronus/changes/decorators-export-2024-7-9-21-2-11.md b/.chronus/changes/decorators-export-2024-7-9-21-2-11.md new file mode 100644 index 00000000000..6d9b8192edc --- /dev/null +++ b/.chronus/changes/decorators-export-2024-7-9-21-2-11.md @@ -0,0 +1,16 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/compiler" +--- + +Add new way to define decorator implementation with `$decorators` export. +```ts +export const $decorators = { + "TypeSpec.OpenAPI": { + useRef: $useRef, + oneOf: $oneOf, + }, +}; +``` diff --git a/.chronus/changes/decorators-export-2024-7-9-22-3-58.md b/.chronus/changes/decorators-export-2024-7-9-22-3-58.md new file mode 100644 index 00000000000..f03bb3e78fd --- /dev/null +++ b/.chronus/changes/decorators-export-2024-7-9-22-3-58.md @@ -0,0 +1,15 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: feature +packages: + - "@typespec/http" + - "@typespec/json-schema" + - "@typespec/openapi" + - "@typespec/openapi3" + - "@typespec/protobuf" + - "@typespec/rest" + - "@typespec/versioning" + - "@typespec/xml" +--- + +Internals: Migrate to new api for declaring decorator implementation diff --git a/docs/extending-typespec/create-decorators.md b/docs/extending-typespec/create-decorators.md index 199d218203c..3c4986ea2a3 100644 --- a/docs/extending-typespec/create-decorators.md +++ b/docs/extending-typespec/create-decorators.md @@ -71,7 +71,21 @@ const tagName: string = "widgets"; ## JavaScript decorator implementation -Decorators can be implemented in JavaScript by prefixing the function name with `$`. A decorator function must have the following parameters: +Decorators can be implemented in JavaScript in 2 ways: + +1. Prefixing the function name with `$`. e.g `export function $doc(target, name) {...}` **Great to get started/play with decorators** +2. Exporting all decorators for your library using `$decorators` variable. **Recommended** + +```ts +export const $decorators = { + // Namespace + "MyOrg.MyLib": { + doc: docDecoratorFn, + }, +}; +``` + +A decorator implementation takes the following parameters: - `1`: `context` of type `DecoratorContext` - `2`: `target` The TypeSpec type target. (`Namespace`, `Interface`, etc.) diff --git a/packages/bundler/src/bundler.ts b/packages/bundler/src/bundler.ts index 8afb6808d93..8661d557f6c 100644 --- a/packages/bundler/src/bundler.ts +++ b/packages/bundler/src/bundler.ts @@ -147,22 +147,23 @@ async function createRollupConfig(definition: TypeSpecBundleDefinition): Promise const program = await compile(NodeHost, libraryPath, { noEmit: true, }); - const jsFiles: string[] = []; + const jsFiles = new Set([resolvePath(libraryPath, definition.packageJson.main)]); for (const file of program.jsSourceFiles.keys()) { if (file.startsWith(libraryPath)) { - jsFiles.push(file); + jsFiles.add(file); } } const typespecFiles: Record = { [normalizePath(join(libraryPath, "package.json"))]: JSON.stringify(definition.packageJson), }; + for (const [filename, sourceFile] of program.sourceFiles) { typespecFiles[filename] = sourceFile.file.text; } const content = createBundleEntrypoint({ libraryPath, mainFile: definition.main, - jsSourceFileNames: jsFiles, + jsSourceFileNames: [...jsFiles], typespecSourceFiles: typespecFiles, }); diff --git a/packages/compiler/generated-defs/TypeSpec.ts b/packages/compiler/generated-defs/TypeSpec.ts index 5d6514db237..6c2e2d64d1c 100644 --- a/packages/compiler/generated-defs/TypeSpec.ts +++ b/packages/compiler/generated-defs/TypeSpec.ts @@ -749,3 +749,48 @@ export type ReturnTypeVisibilityDecorator = ( target: Operation, ...visibilities: string[] ) => void; + +export type TypeSpecDecorators = { + encode: EncodeDecorator; + doc: DocDecorator; + withOptionalProperties: WithOptionalPropertiesDecorator; + withUpdateableProperties: WithUpdateablePropertiesDecorator; + withoutOmittedProperties: WithoutOmittedPropertiesDecorator; + withPickedProperties: WithPickedPropertiesDecorator; + withoutDefaultValues: WithoutDefaultValuesDecorator; + withDefaultKeyVisibility: WithDefaultKeyVisibilityDecorator; + summary: SummaryDecorator; + returnsDoc: ReturnsDocDecorator; + errorsDoc: ErrorsDocDecorator; + deprecated: DeprecatedDecorator; + service: ServiceDecorator; + error: ErrorDecorator; + format: FormatDecorator; + pattern: PatternDecorator; + minLength: MinLengthDecorator; + maxLength: MaxLengthDecorator; + minItems: MinItemsDecorator; + maxItems: MaxItemsDecorator; + minValue: MinValueDecorator; + maxValue: MaxValueDecorator; + minValueExclusive: MinValueExclusiveDecorator; + maxValueExclusive: MaxValueExclusiveDecorator; + secret: SecretDecorator; + list: ListDecorator; + tag: TagDecorator; + friendlyName: FriendlyNameDecorator; + knownValues: KnownValuesDecorator; + key: KeyDecorator; + overload: OverloadDecorator; + projectedName: ProjectedNameDecorator; + encodedName: EncodedNameDecorator; + discriminator: DiscriminatorDecorator; + example: ExampleDecorator; + opExample: OpExampleDecorator; + visibility: VisibilityDecorator; + withVisibility: WithVisibilityDecorator; + inspectType: InspectTypeDecorator; + inspectTypeName: InspectTypeNameDecorator; + parameterVisibility: ParameterVisibilityDecorator; + returnTypeVisibility: ReturnTypeVisibilityDecorator; +}; diff --git a/packages/compiler/generated-defs/TypeSpec.ts-test.ts b/packages/compiler/generated-defs/TypeSpec.ts-test.ts index 3297363694d..a5eb47202e2 100644 --- a/packages/compiler/generated-defs/TypeSpec.ts-test.ts +++ b/packages/compiler/generated-defs/TypeSpec.ts-test.ts @@ -1,180 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { - $deprecated, - $discriminator, - $doc, - $encode, - $encodedName, - $error, - $errorsDoc, - $example, - $format, - $friendlyName, - $inspectType, - $inspectTypeName, - $key, - $knownValues, - $list, - $maxItems, - $maxLength, - $maxValue, - $maxValueExclusive, - $minItems, - $minLength, - $minValue, - $minValueExclusive, - $opExample, - $overload, - $parameterVisibility, - $pattern, - $projectedName, - $returnTypeVisibility, - $returnsDoc, - $secret, - $service, - $summary, - $tag, - $visibility, - $withDefaultKeyVisibility, - $withOptionalProperties, - $withPickedProperties, - $withUpdateableProperties, - $withVisibility, - $withoutDefaultValues, - $withoutOmittedProperties, -} from "../src/index.js"; -import type { - DeprecatedDecorator, - DiscriminatorDecorator, - DocDecorator, - EncodeDecorator, - EncodedNameDecorator, - ErrorDecorator, - ErrorsDocDecorator, - ExampleDecorator, - FormatDecorator, - FriendlyNameDecorator, - InspectTypeDecorator, - InspectTypeNameDecorator, - KeyDecorator, - KnownValuesDecorator, - ListDecorator, - MaxItemsDecorator, - MaxLengthDecorator, - MaxValueDecorator, - MaxValueExclusiveDecorator, - MinItemsDecorator, - MinLengthDecorator, - MinValueDecorator, - MinValueExclusiveDecorator, - OpExampleDecorator, - OverloadDecorator, - ParameterVisibilityDecorator, - PatternDecorator, - ProjectedNameDecorator, - ReturnTypeVisibilityDecorator, - ReturnsDocDecorator, - SecretDecorator, - ServiceDecorator, - SummaryDecorator, - TagDecorator, - VisibilityDecorator, - WithDefaultKeyVisibilityDecorator, - WithOptionalPropertiesDecorator, - WithPickedPropertiesDecorator, - WithUpdateablePropertiesDecorator, - WithVisibilityDecorator, - WithoutDefaultValuesDecorator, - WithoutOmittedPropertiesDecorator, -} from "./TypeSpec.js"; - -type Decorators = { - $encode: EncodeDecorator; - $doc: DocDecorator; - $withOptionalProperties: WithOptionalPropertiesDecorator; - $withUpdateableProperties: WithUpdateablePropertiesDecorator; - $withoutOmittedProperties: WithoutOmittedPropertiesDecorator; - $withPickedProperties: WithPickedPropertiesDecorator; - $withoutDefaultValues: WithoutDefaultValuesDecorator; - $withDefaultKeyVisibility: WithDefaultKeyVisibilityDecorator; - $summary: SummaryDecorator; - $returnsDoc: ReturnsDocDecorator; - $errorsDoc: ErrorsDocDecorator; - $deprecated: DeprecatedDecorator; - $service: ServiceDecorator; - $error: ErrorDecorator; - $format: FormatDecorator; - $pattern: PatternDecorator; - $minLength: MinLengthDecorator; - $maxLength: MaxLengthDecorator; - $minItems: MinItemsDecorator; - $maxItems: MaxItemsDecorator; - $minValue: MinValueDecorator; - $maxValue: MaxValueDecorator; - $minValueExclusive: MinValueExclusiveDecorator; - $maxValueExclusive: MaxValueExclusiveDecorator; - $secret: SecretDecorator; - $list: ListDecorator; - $tag: TagDecorator; - $friendlyName: FriendlyNameDecorator; - $knownValues: KnownValuesDecorator; - $key: KeyDecorator; - $overload: OverloadDecorator; - $projectedName: ProjectedNameDecorator; - $encodedName: EncodedNameDecorator; - $discriminator: DiscriminatorDecorator; - $example: ExampleDecorator; - $opExample: OpExampleDecorator; - $visibility: VisibilityDecorator; - $withVisibility: WithVisibilityDecorator; - $inspectType: InspectTypeDecorator; - $inspectTypeName: InspectTypeNameDecorator; - $parameterVisibility: ParameterVisibilityDecorator; - $returnTypeVisibility: ReturnTypeVisibilityDecorator; -}; - +import { $decorators } from "../src/index.js"; +import type { TypeSpecDecorators } from "./TypeSpec.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $encode, - $doc, - $withOptionalProperties, - $withUpdateableProperties, - $withoutOmittedProperties, - $withPickedProperties, - $withoutDefaultValues, - $withDefaultKeyVisibility, - $summary, - $returnsDoc, - $errorsDoc, - $deprecated, - $service, - $error, - $format, - $pattern, - $minLength, - $maxLength, - $minItems, - $maxItems, - $minValue, - $maxValue, - $minValueExclusive, - $maxValueExclusive, - $secret, - $list, - $tag, - $friendlyName, - $knownValues, - $key, - $overload, - $projectedName, - $encodedName, - $discriminator, - $example, - $opExample, - $visibility, - $withVisibility, - $inspectType, - $inspectTypeName, - $parameterVisibility, - $returnTypeVisibility, -}; +const _: TypeSpecDecorators = $decorators["TypeSpec"]; diff --git a/packages/compiler/lib/std/decorators.tsp b/packages/compiler/lib/std/decorators.tsp index 2875241d334..0b981258740 100644 --- a/packages/compiler/lib/std/decorators.tsp +++ b/packages/compiler/lib/std/decorators.tsp @@ -1,4 +1,4 @@ -import "../../dist/src/lib/decorators.js"; +import "../../dist/src/lib/tsp-index.js"; using TypeSpec.Reflection; diff --git a/packages/compiler/src/core/binder.ts b/packages/compiler/src/core/binder.ts index eb5a1fc91f5..3bd5c8a6798 100644 --- a/packages/compiler/src/core/binder.ts +++ b/packages/compiler/src/core/binder.ts @@ -8,6 +8,7 @@ import { ConstStatementNode, Declaration, DecoratorDeclarationStatementNode, + DecoratorImplementations, EnumStatementNode, FileLibraryMetadata, FunctionDeclarationStatementNode, @@ -120,7 +121,6 @@ export function createBinder(program: Program): Binder { if ((sourceFile.symbol as any) !== undefined) { return; } - const tracer = program.tracer.sub("bind.js"); fileNamespace = undefined; mutate(sourceFile).symbol = createSymbol( @@ -133,12 +133,24 @@ export function createBinder(program: Program): Binder { for (const [key, member] of Object.entries(sourceFile.esmExports)) { let name: string; let kind: "decorator" | "function"; - let containerSymbol = sourceFile.symbol; if (key === "$flags") { const context = getLocationContext(program, sourceFile); if (context.type === "library" || context.type === "project") { mutate(context).flags = member as any; } + } else if (key === "$decorators") { + const value: DecoratorImplementations = member as any; + for (const [namespaceName, decorators] of Object.entries(value)) { + for (const [decoratorName, decorator] of Object.entries(decorators)) { + bindFunctionImplementation( + namespaceName === "" ? [] : namespaceName.split("."), + "decorator", + decoratorName, + decorator, + sourceFile + ); + } + } } else if (typeof member === "function") { // lots of 'any' casts here because control flow narrowing `member` to Function // isn't particularly useful it turns out. @@ -161,68 +173,78 @@ export function createBinder(program: Program): Binder { name = key; kind = "function"; } - const nsParts = resolveJSMemberNamespaceParts(rootNs, member); - for (const part of nsParts) { - const existingBinding = containerSymbol.exports!.get(part); - const jsNamespaceNode: JsNamespaceDeclarationNode = { - kind: SyntaxKind.JsNamespaceDeclaration, - id: { - kind: SyntaxKind.Identifier, - sv: part, - pos: 0, - end: 0, - flags: NodeFlags.None, - symbol: undefined!, - }, - pos: sourceFile.pos, - end: sourceFile.end, - parent: sourceFile, - flags: NodeFlags.None, - symbol: undefined!, - }; - const sym = createSymbol(jsNamespaceNode, part, SymbolFlags.Namespace, containerSymbol); - mutate(jsNamespaceNode).symbol = sym; - if (existingBinding) { - if (existingBinding.flags & SymbolFlags.Namespace) { - // since the namespace was "declared" as part of this source file, - // we can simply re-use it. - containerSymbol = existingBinding; - } else { - // we have some conflict, lets report a duplicate binding error. - mutate(containerSymbol.exports)!.set(part, sym); - } - } else { - mutate(sym).exports = createSymbolTable(); - mutate(containerSymbol.exports!).set(part, sym); - containerSymbol = sym; - } - } - let sym; - if (kind === "decorator") { - tracer.trace( - "decorator", - `Bound decorator "@${name}" in namespace "${nsParts.join(".")}".` - ); - sym = createSymbol( - sourceFile, - "@" + name, - SymbolFlags.Decorator | SymbolFlags.Implementation, - containerSymbol - ); + bindFunctionImplementation(nsParts, kind, name, member as any, sourceFile); + } + } + } + + function bindFunctionImplementation( + nsParts: string[], + kind: "decorator" | "function", + name: string, + fn: (...args: any[]) => any, + sourceFile: JsSourceFileNode + ) { + let containerSymbol = sourceFile.symbol; + + const tracer = program.tracer.sub("bind.js"); + + for (const part of nsParts) { + const existingBinding = containerSymbol.exports!.get(part); + const jsNamespaceNode: JsNamespaceDeclarationNode = { + kind: SyntaxKind.JsNamespaceDeclaration, + id: { + kind: SyntaxKind.Identifier, + sv: part, + pos: 0, + end: 0, + flags: NodeFlags.None, + symbol: undefined!, + }, + pos: sourceFile.pos, + end: sourceFile.end, + parent: sourceFile, + flags: NodeFlags.None, + symbol: undefined!, + }; + const sym = createSymbol(jsNamespaceNode, part, SymbolFlags.Namespace, containerSymbol); + mutate(jsNamespaceNode).symbol = sym; + if (existingBinding) { + if (existingBinding.flags & SymbolFlags.Namespace) { + // since the namespace was "declared" as part of this source file, + // we can simply re-use it. + containerSymbol = existingBinding; } else { - tracer.trace("function", `Bound function "${name}" in namespace "${nsParts.join(".")}".`); - sym = createSymbol( - sourceFile, - name, - SymbolFlags.Function | SymbolFlags.Implementation, - containerSymbol - ); + // we have some conflict, lets report a duplicate binding error. + mutate(containerSymbol.exports)!.set(part, sym); } - mutate(sym).value = member as any; - mutate(containerSymbol.exports)!.set(sym.name, sym); + } else { + mutate(sym).exports = createSymbolTable(); + mutate(containerSymbol.exports!).set(part, sym); + containerSymbol = sym; } } + let sym; + if (kind === "decorator") { + tracer.trace("decorator", `Bound decorator "@${name}" in namespace "${nsParts.join(".")}".`); + sym = createSymbol( + sourceFile, + "@" + name, + SymbolFlags.Decorator | SymbolFlags.Implementation, + containerSymbol + ); + } else { + tracer.trace("function", `Bound function "${name}" in namespace "${nsParts.join(".")}".`); + sym = createSymbol( + sourceFile, + name, + SymbolFlags.Function | SymbolFlags.Implementation, + containerSymbol + ); + } + mutate(sym).value = fn; + mutate(containerSymbol.exports)!.set(sym.name, sym); } function resolveJSMemberNamespaceParts(rootNs: string | undefined, member: any) { diff --git a/packages/compiler/src/core/types.ts b/packages/compiler/src/core/types.ts index 193ce64e580..093ec02f54d 100644 --- a/packages/compiler/src/core/types.ts +++ b/packages/compiler/src/core/types.ts @@ -2451,6 +2451,25 @@ export interface TypeSpecLibraryDef< readonly state?: Record; } +/** + * Type for the $decorators export from libraries. + * + * @example + * ``` + * export const $decorators = { + * "Azure.Core": { + * flags: $flags, + * "foo-bar": fooBarDecorator + * } + * } + * ``` + */ +export interface DecoratorImplementations { + readonly [namespace: string]: { + readonly [name: string]: DecoratorFunction; + }; +} + export interface PackageFlags { /** * Decorator arg marshalling algorithm. Specify how TypeSpec values are marshalled to decorator arguments. diff --git a/packages/compiler/src/index.ts b/packages/compiler/src/index.ts index edf1197192d..7d7736d040a 100644 --- a/packages/compiler/src/index.ts +++ b/packages/compiler/src/index.ts @@ -19,3 +19,6 @@ export { /** @deprecated Use TypeSpecPrettierPlugin */ export const CadlPrettierPlugin = TypeSpecPrettierPlugin; + +/** @internal for Typespec compiler */ +export { $decorators } from "./lib/tsp-index.js"; diff --git a/packages/compiler/src/lib/tsp-index.ts b/packages/compiler/src/lib/tsp-index.ts new file mode 100644 index 00000000000..2b82bdee650 --- /dev/null +++ b/packages/compiler/src/lib/tsp-index.ts @@ -0,0 +1,98 @@ +import { TypeSpecDecorators } from "../../generated-defs/TypeSpec.js"; +import { + $deprecated, + $discriminator, + $doc, + $encode, + $encodedName, + $error, + $errorsDoc, + $example, + $format, + $friendlyName, + $inspectType, + $inspectTypeName, + $key, + $knownValues, + $list, + $maxItems, + $maxLength, + $maxValue, + $maxValueExclusive, + $minItems, + $minLength, + $minValue, + $minValueExclusive, + $opExample, + $overload, + $parameterVisibility, + $pattern, + $projectedName, + $returnTypeVisibility, + $returnsDoc, + $secret, + $service, + $summary, + $tag, + $visibility, + $withDefaultKeyVisibility, + $withOptionalProperties, + $withPickedProperties, + $withUpdateableProperties, + $withVisibility, + $withoutDefaultValues, + $withoutOmittedProperties, +} from "./decorators.js"; + +/** @internal */ +export const $decorators = { + TypeSpec: { + encode: $encode, + doc: $doc, + withOptionalProperties: $withOptionalProperties, + withUpdateableProperties: $withUpdateableProperties, + withoutOmittedProperties: $withoutOmittedProperties, + withPickedProperties: $withPickedProperties, + withoutDefaultValues: $withoutDefaultValues, + withDefaultKeyVisibility: $withDefaultKeyVisibility, + summary: $summary, + returnsDoc: $returnsDoc, + errorsDoc: $errorsDoc, + deprecated: $deprecated, + service: $service, + error: $error, + format: $format, + pattern: $pattern, + minLength: $minLength, + maxLength: $maxLength, + minItems: $minItems, + maxItems: $maxItems, + minValue: $minValue, + maxValue: $maxValue, + minValueExclusive: $minValueExclusive, + maxValueExclusive: $maxValueExclusive, + secret: $secret, + // eslint-disable-next-line deprecation/deprecation + list: $list, + tag: $tag, + friendlyName: $friendlyName, + knownValues: $knownValues, + key: $key, + overload: $overload, + projectedName: $projectedName, + encodedName: $encodedName, + discriminator: $discriminator, + example: $example, + opExample: $opExample, + visibility: $visibility, + withVisibility: $withVisibility, + inspectType: $inspectType, + inspectTypeName: $inspectTypeName, + parameterVisibility: $parameterVisibility, + returnTypeVisibility: $returnTypeVisibility, + } satisfies TypeSpecDecorators, +}; + +// Projection function exports +export const namespace = "TypeSpec"; +export { getProjectedName, hasProjectedName } from "./decorators.js"; diff --git a/packages/compiler/test/binder.test.ts b/packages/compiler/test/binder.test.ts index 1ff2fa6ffb7..3f91ee9f34c 100644 --- a/packages/compiler/test/binder.test.ts +++ b/packages/compiler/test/binder.test.ts @@ -458,6 +458,39 @@ describe("compiler: binder", () => { }); }); + it("binds $decorators in JS file", () => { + const exports = { + $decorators: { + "Foo.Bar": { myDec2: () => {} }, + Foo: { myDec: () => {} }, + }, + }; + + const sourceFile = bindJs(exports); + assertBindings("jsFile", sourceFile.symbol.exports!, { + Foo: { + flags: SymbolFlags.Namespace, + declarations: [SyntaxKind.JsNamespaceDeclaration], + exports: { + Bar: { + flags: SymbolFlags.Namespace, + declarations: [SyntaxKind.JsNamespaceDeclaration], + exports: { + "@myDec2": { + flags: SymbolFlags.Decorator | SymbolFlags.Implementation, + declarations: [SyntaxKind.JsSourceFile], + }, + }, + }, + "@myDec": { + flags: SymbolFlags.Decorator | SymbolFlags.Implementation, + declarations: [SyntaxKind.JsSourceFile], + }, + }, + }, + }); + }); + function bindTypeSpec(code: string) { const sourceFile = parse(code); expectDiagnosticEmpty(sourceFile.parseDiagnostics); diff --git a/packages/compiler/test/checker/decorators.test.ts b/packages/compiler/test/checker/decorators.test.ts index ecf58ed479f..cfaf134adc7 100644 --- a/packages/compiler/test/checker/decorators.test.ts +++ b/packages/compiler/test/checker/decorators.test.ts @@ -1,6 +1,12 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; -import { PackageFlags, isNullType, setTypeSpecNamespace } from "../../src/core/index.js"; +import { + DecoratorFunction, + Namespace, + PackageFlags, + isNullType, + setTypeSpecNamespace, +} from "../../src/core/index.js"; import { numericRanges } from "../../src/core/numeric-ranges.js"; import { Numeric } from "../../src/core/numeric.js"; import { @@ -35,42 +41,74 @@ describe("compiler: checker: decorators", () => { }); }); - it("bind implementation to declaration", async () => { - await runner.compile(` - extern dec testDec(target: unknown); - `); + describe("bind implementation to declaration", () => { + let $otherDec: DecoratorFunction; + function expectDecorator(ns: Namespace) { + const otherDecDecorator = ns.decoratorDeclarations.get("otherDec"); + ok(otherDecDecorator); + strictEqual(otherDecDecorator.implementation, $otherDec); + } - const testDecDecorator = runner.program - .getGlobalNamespaceType() - .decoratorDeclarations.get("testDec"); - ok(testDecDecorator); + describe("with $fn", () => { + beforeEach(() => { + $otherDec = () => {}; + testJs.$otherDec = $otherDec; + }); - strictEqual(testDecDecorator.implementation, $testDec); - }); + it("defined at root", async () => { + await runner.compile(` + extern dec otherDec(target: unknown); + `); - it("bind implementation to declaration when in a namespace", async () => { - const $otherDec = () => {}; - testJs.$otherDec = $otherDec; - setTypeSpecNamespace("MyLib", $otherDec); + expectDecorator(runner.program.getGlobalNamespaceType()); + }); - await runner.compile(` - extern dec testDec(target: unknown); - namespace MyLib { - extern dec otherDec(target: unknown); - } - `); + it("in a namespace", async () => { + setTypeSpecNamespace("Foo.Bar", $otherDec); + + await runner.compile(` + namespace Foo.Bar { + extern dec otherDec(target: unknown); + } + `); + + const ns = runner.program + .getGlobalNamespaceType() + .namespaces.get("Foo") + ?.namespaces.get("Bar"); + ok(ns); + expectDecorator(ns); + }); + }); - const testDecDecorator = runner.program - .getGlobalNamespaceType() - .decoratorDeclarations.get("testDec"); - ok(testDecDecorator); + describe("with $decorators", () => { + it("defined at root", async () => { + testJs.$decorators = { "": { otherDec: $otherDec } }; - const myLib = runner.program.getGlobalNamespaceType().namespaces.get("MyLib"); - ok(myLib); - const otherDecDecorator = myLib.decoratorDeclarations.get("otherDec"); - ok(otherDecDecorator); + await runner.compile(` + extern dec otherDec(target: unknown); + `); - strictEqual(otherDecDecorator.implementation, $otherDec); + expectDecorator(runner.program.getGlobalNamespaceType()); + }); + + it("in a namespace", async () => { + testJs.$decorators = { "Foo.Bar": { otherDec: $otherDec } }; + + await runner.compile(` + namespace Foo.Bar { + extern dec otherDec(target: unknown); + } + `); + + const ns = runner.program + .getGlobalNamespaceType() + .namespaces.get("Foo") + ?.namespaces.get("Bar"); + ok(ns); + expectDecorator(ns); + }); + }); }); it("errors if decorator is missing extern modifier", async () => { diff --git a/packages/http/generated-defs/TypeSpec.Http.Private.ts b/packages/http/generated-defs/TypeSpec.Http.Private.ts index 6ddd4064d52..887b2847ce5 100644 --- a/packages/http/generated-defs/TypeSpec.Http.Private.ts +++ b/packages/http/generated-defs/TypeSpec.Http.Private.ts @@ -14,3 +14,9 @@ export type HttpPartDecorator = ( type: Type, options: HttpPartOptions ) => void; + +export type TypeSpecHttpPrivateDecorators = { + plainData: PlainDataDecorator; + httpFile: HttpFileDecorator; + httpPart: HttpPartDecorator; +}; diff --git a/packages/http/generated-defs/TypeSpec.Http.ts b/packages/http/generated-defs/TypeSpec.Http.ts index a14f1cec2a4..424ce646ca0 100644 --- a/packages/http/generated-defs/TypeSpec.Http.ts +++ b/packages/http/generated-defs/TypeSpec.Http.ts @@ -316,3 +316,25 @@ export type RouteDecorator = ( * ``` */ export type SharedRouteDecorator = (context: DecoratorContext, target: Operation) => void; + +export type TypeSpecHttpDecorators = { + statusCode: StatusCodeDecorator; + body: BodyDecorator; + header: HeaderDecorator; + query: QueryDecorator; + path: PathDecorator; + bodyRoot: BodyRootDecorator; + bodyIgnore: BodyIgnoreDecorator; + multipartBody: MultipartBodyDecorator; + get: GetDecorator; + put: PutDecorator; + post: PostDecorator; + patch: PatchDecorator; + delete: DeleteDecorator; + head: HeadDecorator; + server: ServerDecorator; + useAuth: UseAuthDecorator; + includeInapplicableMetadataInPayload: IncludeInapplicableMetadataInPayloadDecorator; + route: RouteDecorator; + sharedRoute: SharedRouteDecorator; +}; diff --git a/packages/http/generated-defs/TypeSpec.Http.ts-test.ts b/packages/http/generated-defs/TypeSpec.Http.ts-test.ts index fcdfdf90781..cc3462fac55 100644 --- a/packages/http/generated-defs/TypeSpec.Http.ts-test.ts +++ b/packages/http/generated-defs/TypeSpec.Http.ts-test.ts @@ -1,88 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { - $body, - $bodyIgnore, - $bodyRoot, - $delete, - $get, - $head, - $header, - $includeInapplicableMetadataInPayload, - $multipartBody, - $patch, - $path, - $post, - $put, - $query, - $route, - $server, - $sharedRoute, - $statusCode, - $useAuth, -} from "@typespec/http"; -import type { - BodyDecorator, - BodyIgnoreDecorator, - BodyRootDecorator, - DeleteDecorator, - GetDecorator, - HeadDecorator, - HeaderDecorator, - IncludeInapplicableMetadataInPayloadDecorator, - MultipartBodyDecorator, - PatchDecorator, - PathDecorator, - PostDecorator, - PutDecorator, - QueryDecorator, - RouteDecorator, - ServerDecorator, - SharedRouteDecorator, - StatusCodeDecorator, - UseAuthDecorator, -} from "./TypeSpec.Http.js"; - -type Decorators = { - $statusCode: StatusCodeDecorator; - $body: BodyDecorator; - $header: HeaderDecorator; - $query: QueryDecorator; - $path: PathDecorator; - $bodyRoot: BodyRootDecorator; - $bodyIgnore: BodyIgnoreDecorator; - $multipartBody: MultipartBodyDecorator; - $get: GetDecorator; - $put: PutDecorator; - $post: PostDecorator; - $patch: PatchDecorator; - $delete: DeleteDecorator; - $head: HeadDecorator; - $server: ServerDecorator; - $useAuth: UseAuthDecorator; - $includeInapplicableMetadataInPayload: IncludeInapplicableMetadataInPayloadDecorator; - $route: RouteDecorator; - $sharedRoute: SharedRouteDecorator; -}; - +import { $decorators } from "@typespec/http"; +import type { TypeSpecHttpDecorators } from "./TypeSpec.Http.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $statusCode, - $body, - $header, - $query, - $path, - $bodyRoot, - $bodyIgnore, - $multipartBody, - $get, - $put, - $post, - $patch, - $delete, - $head, - $server, - $useAuth, - $includeInapplicableMetadataInPayload, - $route, - $sharedRoute, -}; +const _: TypeSpecHttpDecorators = $decorators["TypeSpec.Http"]; diff --git a/packages/http/lib/http.tsp b/packages/http/lib/http.tsp index 411a7c1f3d9..7734cfdbda7 100644 --- a/packages/http/lib/http.tsp +++ b/packages/http/lib/http.tsp @@ -1,4 +1,4 @@ -import "../dist/src/index.js"; +import "../dist/src/tsp-index.js"; import "./decorators.tsp"; import "./private.decorators.tsp"; import "./auth.tsp"; diff --git a/packages/http/src/index.ts b/packages/http/src/index.ts index 57629e986e9..c5621f0a41e 100644 --- a/packages/http/src/index.ts +++ b/packages/http/src/index.ts @@ -18,5 +18,8 @@ export { } from "./private.decorators.js"; export * from "./responses.js"; export * from "./route.js"; + export * from "./types.js"; -export * from "./validate.js"; + +/** @internal */ +export { $decorators } from "./tsp-index.js"; diff --git a/packages/http/src/private.decorators.ts b/packages/http/src/private.decorators.ts index 0812014a802..5564e634933 100644 --- a/packages/http/src/private.decorators.ts +++ b/packages/http/src/private.decorators.ts @@ -11,12 +11,14 @@ import { HttpPartDecorator, HttpPartOptions, PlainDataDecorator, + TypeSpecHttpPrivateDecorators, } from "../generated-defs/TypeSpec.Http.Private.js"; import { HttpStateKeys } from "./lib.js"; +/** @internal */ export const namespace = "TypeSpec.Http.Private"; -export const $plainData: PlainDataDecorator = (context: DecoratorContext, entity: Model) => { +const $plainData: PlainDataDecorator = (context: DecoratorContext, entity: Model) => { const { program } = context; const decoratorsToRemove = ["$header", "$body", "$query", "$path", "$statusCode"]; @@ -44,7 +46,7 @@ export const $plainData: PlainDataDecorator = (context: DecoratorContext, entity } }; -export const $httpFile: HttpFileDecorator = (context: DecoratorContext, target: Model) => { +const $httpFile: HttpFileDecorator = (context: DecoratorContext, target: Model) => { context.program.stateSet(HttpStateKeys.file).add(target); }; @@ -92,12 +94,7 @@ export function getHttpFileModel(program: Program, type: Type): HttpFileModel | return { contents, contentType, filename, type }; } -export const $httpPart: HttpPartDecorator = ( - context: DecoratorContext, - target: Model, - type, - options -) => { +const $httpPart: HttpPartDecorator = (context: DecoratorContext, target: Model, type, options) => { context.program.stateMap(HttpStateKeys.httpPart).set(target, { type, options }); }; @@ -110,3 +107,12 @@ export interface HttpPart { export function getHttpPart(program: Program, target: Type): HttpPart | undefined { return program.stateMap(HttpStateKeys.httpPart).get(target); } + +/** @internal */ +export const $decorators = { + "TypeSpec.Http.Private": { + httpFile: $httpFile, + httpPart: $httpPart, + plainData: $plainData, + } satisfies TypeSpecHttpPrivateDecorators, +}; diff --git a/packages/http/src/tsp-index.ts b/packages/http/src/tsp-index.ts new file mode 100644 index 00000000000..11c34db4446 --- /dev/null +++ b/packages/http/src/tsp-index.ts @@ -0,0 +1,50 @@ +import { TypeSpecHttpDecorators } from "../generated-defs/TypeSpec.Http.js"; +import { + $body, + $bodyIgnore, + $bodyRoot, + $delete, + $get, + $head, + $header, + $includeInapplicableMetadataInPayload, + $multipartBody, + $patch, + $path, + $post, + $put, + $query, + $route, + $server, + $sharedRoute, + $statusCode, + $useAuth, +} from "./decorators.js"; + +export { $lib } from "./lib.js"; +export { $onValidate } from "./validate.js"; + +/** @internal */ +export const $decorators = { + "TypeSpec.Http": { + body: $body, + bodyIgnore: $bodyIgnore, + bodyRoot: $bodyRoot, + delete: $delete, + get: $get, + header: $header, + head: $head, + includeInapplicableMetadataInPayload: $includeInapplicableMetadataInPayload, + multipartBody: $multipartBody, + patch: $patch, + path: $path, + post: $post, + put: $put, + query: $query, + route: $route, + server: $server, + sharedRoute: $sharedRoute, + statusCode: $statusCode, + useAuth: $useAuth, + } satisfies TypeSpecHttpDecorators, +}; diff --git a/packages/json-schema/generated-defs/TypeSpec.JsonSchema.Private.ts b/packages/json-schema/generated-defs/TypeSpec.JsonSchema.Private.ts index 39514bf0e93..8dd54a21936 100644 --- a/packages/json-schema/generated-defs/TypeSpec.JsonSchema.Private.ts +++ b/packages/json-schema/generated-defs/TypeSpec.JsonSchema.Private.ts @@ -5,3 +5,7 @@ export type ValidatesRawJsonDecorator = ( target: Model, value: Type ) => void; + +export type TypeSpecJsonSchemaPrivateDecorators = { + validatesRawJson: ValidatesRawJsonDecorator; +}; diff --git a/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts b/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts index bd4ee0bcbe3..3c1c3b057c1 100644 --- a/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts +++ b/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts @@ -198,3 +198,22 @@ export type ExtensionDecorator = ( key: string, value: Type | unknown ) => void; + +export type TypeSpecJsonSchemaDecorators = { + jsonSchema: JsonSchemaDecorator; + baseUri: BaseUriDecorator; + id: IdDecorator; + oneOf: OneOfDecorator; + multipleOf: MultipleOfDecorator; + contains: ContainsDecorator; + minContains: MinContainsDecorator; + maxContains: MaxContainsDecorator; + uniqueItems: UniqueItemsDecorator; + minProperties: MinPropertiesDecorator; + maxProperties: MaxPropertiesDecorator; + contentEncoding: ContentEncodingDecorator; + prefixItems: PrefixItemsDecorator; + contentMediaType: ContentMediaTypeDecorator; + contentSchema: ContentSchemaDecorator; + extension: ExtensionDecorator; +}; diff --git a/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts-test.ts b/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts-test.ts index 80ecdef7f8e..105ea89c5e4 100644 --- a/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts-test.ts +++ b/packages/json-schema/generated-defs/TypeSpec.JsonSchema.ts-test.ts @@ -1,76 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { - $baseUri, - $contains, - $contentEncoding, - $contentMediaType, - $contentSchema, - $extension, - $id, - $jsonSchema, - $maxContains, - $maxProperties, - $minContains, - $minProperties, - $multipleOf, - $oneOf, - $prefixItems, - $uniqueItems, -} from "@typespec/json-schema"; -import type { - BaseUriDecorator, - ContainsDecorator, - ContentEncodingDecorator, - ContentMediaTypeDecorator, - ContentSchemaDecorator, - ExtensionDecorator, - IdDecorator, - JsonSchemaDecorator, - MaxContainsDecorator, - MaxPropertiesDecorator, - MinContainsDecorator, - MinPropertiesDecorator, - MultipleOfDecorator, - OneOfDecorator, - PrefixItemsDecorator, - UniqueItemsDecorator, -} from "./TypeSpec.JsonSchema.js"; - -type Decorators = { - $jsonSchema: JsonSchemaDecorator; - $baseUri: BaseUriDecorator; - $id: IdDecorator; - $oneOf: OneOfDecorator; - $multipleOf: MultipleOfDecorator; - $contains: ContainsDecorator; - $minContains: MinContainsDecorator; - $maxContains: MaxContainsDecorator; - $uniqueItems: UniqueItemsDecorator; - $minProperties: MinPropertiesDecorator; - $maxProperties: MaxPropertiesDecorator; - $contentEncoding: ContentEncodingDecorator; - $prefixItems: PrefixItemsDecorator; - $contentMediaType: ContentMediaTypeDecorator; - $contentSchema: ContentSchemaDecorator; - $extension: ExtensionDecorator; -}; - +import { $decorators } from "@typespec/json-schema"; +import type { TypeSpecJsonSchemaDecorators } from "./TypeSpec.JsonSchema.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $jsonSchema, - $baseUri, - $id, - $oneOf, - $multipleOf, - $contains, - $minContains, - $maxContains, - $uniqueItems, - $minProperties, - $maxProperties, - $contentEncoding, - $prefixItems, - $contentMediaType, - $contentSchema, - $extension, -}; +const _: TypeSpecJsonSchemaDecorators = $decorators["TypeSpec.JsonSchema"]; diff --git a/packages/json-schema/lib/main.tsp b/packages/json-schema/lib/main.tsp index 391e908652e..335e2199f33 100644 --- a/packages/json-schema/lib/main.tsp +++ b/packages/json-schema/lib/main.tsp @@ -1,4 +1,4 @@ -import "../dist/src/index.js"; +import "../dist/src/tsp-index.js"; namespace TypeSpec.JsonSchema; diff --git a/packages/json-schema/src/decorators.ts b/packages/json-schema/src/decorators.ts new file mode 100644 index 00000000000..76f3b661a65 --- /dev/null +++ b/packages/json-schema/src/decorators.ts @@ -0,0 +1,324 @@ +import { + DecoratorContext, + Enum, + Model, + ModelProperty, + Namespace, + Numeric, + Program, + Scalar, + Tuple, + Type, + Union, + isType, + setTypeSpecNamespace, + typespecTypeToJson, +} from "@typespec/compiler"; +import { ValidatesRawJsonDecorator } from "../generated-defs/TypeSpec.JsonSchema.Private.js"; +import { + BaseUriDecorator, + ContainsDecorator, + ContentEncodingDecorator, + ContentMediaTypeDecorator, + ContentSchemaDecorator, + ExtensionDecorator, + IdDecorator, + JsonSchemaDecorator, + MaxContainsDecorator, + MaxPropertiesDecorator, + MinContainsDecorator, + MinPropertiesDecorator, + MultipleOfDecorator, + OneOfDecorator, + PrefixItemsDecorator, + UniqueItemsDecorator, +} from "../generated-defs/TypeSpec.JsonSchema.js"; +import { createStateSymbol } from "./lib.js"; + +export type JsonSchemaDeclaration = Model | Union | Enum | Scalar; + +const jsonSchemaKey = createStateSymbol("JsonSchema"); + +export const $jsonSchema: JsonSchemaDecorator = ( + context: DecoratorContext, + target: Type, + baseUriOrId?: string +) => { + context.program.stateSet(jsonSchemaKey).add(target); + if (baseUriOrId) { + if (target.kind === "Namespace") { + context.call($baseUri, target, baseUriOrId); + } else { + context.call($id, target, baseUriOrId); + } + } +}; + +const baseUriKey = createStateSymbol("JsonSchema.baseURI"); +export const $baseUri: BaseUriDecorator = ( + context: DecoratorContext, + target: Namespace, + baseUri: string +) => { + context.program.stateMap(baseUriKey).set(target, baseUri); +}; + +export function getBaseUri(program: Program, target: Type) { + return program.stateMap(baseUriKey).get(target); +} + +export function findBaseUri( + program: Program, + target: JsonSchemaDeclaration | Namespace +): string | undefined { + let baseUrl: string | undefined; + let current: JsonSchemaDeclaration | Namespace | undefined = target; + do { + baseUrl = getBaseUri(program, current); + current = current.namespace; + } while (!baseUrl && current); + + return baseUrl; +} + +export function isJsonSchemaDeclaration(program: Program, target: JsonSchemaDeclaration) { + let current: JsonSchemaDeclaration | Namespace | undefined = target; + do { + if (getJsonSchema(program, current)) { + return true; + } + + current = current.namespace; + } while (current); + + return false; +} + +export function getJsonSchemaTypes(program: Program): (Namespace | Model)[] { + return [...(program.stateSet(jsonSchemaKey) || [])] as (Namespace | Model)[]; +} + +export function getJsonSchema(program: Program, target: Type) { + return program.stateSet(jsonSchemaKey).has(target); +} + +const multipleOfKey = createStateSymbol("JsonSchema.multipleOf"); +export const $multipleOf: MultipleOfDecorator = ( + context: DecoratorContext, + target: Scalar | ModelProperty, + value: Numeric +) => { + context.program.stateMap(multipleOfKey).set(target, value); +}; + +export function getMultipleOfAsNumeric(program: Program, target: Type): Numeric | undefined { + return program.stateMap(multipleOfKey).get(target); +} +export function getMultipleOf(program: Program, target: Type): number | undefined { + return getMultipleOfAsNumeric(program, target)?.asNumber() ?? undefined; +} + +const idKey = createStateSymbol("JsonSchema.id"); +export const $id: IdDecorator = (context: DecoratorContext, target: Type, value: string) => { + context.program.stateMap(idKey).set(target, value); +}; + +export function getId(program: Program, target: Type) { + return program.stateMap(idKey).get(target); +} + +const oneOfKey = createStateSymbol("JsonSchema.oneOf"); +export const $oneOf: OneOfDecorator = (context: DecoratorContext, target: Type) => { + context.program.stateMap(oneOfKey).set(target, true); +}; + +export function isOneOf(program: Program, target: Type) { + return program.stateMap(oneOfKey).has(target); +} + +const containsKey = createStateSymbol("JsonSchema.contains"); +export const $contains: ContainsDecorator = ( + context: DecoratorContext, + target: Type, + value: Type +) => { + context.program.stateMap(containsKey).set(target, value); +}; + +export function getContains(program: Program, target: Type) { + return program.stateMap(containsKey).get(target); +} + +const minContainsKey = createStateSymbol("JsonSchema.minContains"); +export const $minContains: MinContainsDecorator = ( + context: DecoratorContext, + target: Type, + value: number +) => { + context.program.stateMap(minContainsKey).set(target, value); +}; + +export function getMinContains(program: Program, target: Type) { + return program.stateMap(minContainsKey).get(target); +} + +const maxContainsKey = createStateSymbol("JsonSchema.maxContains"); +export const $maxContains: MaxContainsDecorator = ( + context: DecoratorContext, + target: Type, + value: number +) => { + context.program.stateMap(maxContainsKey).set(target, value); +}; + +export function getMaxContains(program: Program, target: Type) { + return program.stateMap(maxContainsKey).get(target); +} + +const uniqueItemsKey = createStateSymbol("JsonSchema.uniqueItems"); +export const $uniqueItems: UniqueItemsDecorator = (context: DecoratorContext, target: Type) => { + context.program.stateMap(uniqueItemsKey).set(target, true); +}; + +export function getUniqueItems(program: Program, target: Type) { + return program.stateMap(uniqueItemsKey).get(target); +} + +const minPropertiesKey = createStateSymbol("JsonSchema.minProperties"); +export const $minProperties: MinPropertiesDecorator = ( + context: DecoratorContext, + target: Type, + value: number +) => { + context.program.stateMap(minPropertiesKey).set(target, value); +}; + +export function getMinProperties(program: Program, target: Type) { + return program.stateMap(minPropertiesKey).get(target); +} + +const maxPropertiesKey = createStateSymbol("JsonSchema.maxProperties"); +export const $maxProperties: MaxPropertiesDecorator = ( + context: DecoratorContext, + target: Type, + value: number +) => { + context.program.stateMap(maxPropertiesKey).set(target, value); +}; + +export function getMaxProperties(program: Program, target: Type) { + return program.stateMap(maxPropertiesKey).get(target); +} + +const contentEncodingKey = createStateSymbol("JsonSchema.contentEncoding"); +export const $contentEncoding: ContentEncodingDecorator = ( + context: DecoratorContext, + target: Scalar | ModelProperty, + value: string +) => { + context.program.stateMap(contentEncodingKey).set(target, value); +}; + +export function getContentEncoding(program: Program, target: Type): string { + return program.stateMap(contentEncodingKey).get(target); +} + +const contentMediaType = createStateSymbol("JsonSchema.contentMediaType"); +export const $contentMediaType: ContentMediaTypeDecorator = ( + context: DecoratorContext, + target: Scalar | ModelProperty, + value: string +) => { + context.program.stateMap(contentMediaType).set(target, value); +}; + +export function getContentMediaType(program: Program, target: Type): string { + return program.stateMap(contentMediaType).get(target); +} + +const contentSchemaKey = createStateSymbol("JsonSchema.contentSchema"); +export const $contentSchema: ContentSchemaDecorator = ( + context: DecoratorContext, + target: Scalar | ModelProperty, + value: Type +) => { + context.program.stateMap(contentSchemaKey).set(target, value); +}; + +export function getContentSchema(program: Program, target: Type) { + return program.stateMap(contentSchemaKey).get(target); +} + +const prefixItemsKey = createStateSymbol("JsonSchema.prefixItems"); +export const $prefixItems: PrefixItemsDecorator = ( + context: DecoratorContext, + target: Type, + value: Type +) => { + context.program.stateMap(prefixItemsKey).set(target, value); +}; + +export function getPrefixItems(program: Program, target: Type): Tuple | undefined { + return program.stateMap(prefixItemsKey).get(target); +} + +export interface ExtensionRecord { + key: string; + value: Type | unknown; +} + +const extensionsKey = createStateSymbol("JsonSchema.extension"); +export const $extension: ExtensionDecorator = ( + context: DecoratorContext, + target: Type, + key: string, + value: unknown +) => { + setExtension(context.program, target, key, value); +}; + +export function getExtensions(program: Program, target: Type): ExtensionRecord[] { + return program.stateMap(extensionsKey).get(target) ?? []; +} + +export function setExtension(program: Program, target: Type, key: string, value: unknown) { + const stateMap = program.stateMap(extensionsKey) as Map; + const extensions = stateMap.has(target) + ? stateMap.get(target)! + : stateMap.set(target, []).get(target)!; + + // Check if we were handed the `Json` template model + if (isJsonTemplateType(value)) { + extensions.push({ + key, + value: typespecTypeToJson(value.properties.get("value")!.type, target)[0], + }); + } else { + extensions.push({ key, value }); + } +} + +function isJsonTemplateType( + value: any +): value is Type & { kind: "Model"; name: "Json"; namespace: { name: "JsonSchema" } } { + return ( + typeof value === "object" && + value !== null && + isType(value) && + value.kind === "Model" && + value.name === "Json" && + value.namespace?.name === "JsonSchema" + ); +} + +export const $validatesRawJson: ValidatesRawJsonDecorator = ( + context: DecoratorContext, + target: Model, + value: Type +) => { + const [_, diagnostics] = typespecTypeToJson(value, target); + if (diagnostics.length > 0) { + context.program.reportDiagnostics(diagnostics); + } +}; +setTypeSpecNamespace("Private", $validatesRawJson); diff --git a/packages/json-schema/src/index.ts b/packages/json-schema/src/index.ts index 47a4b0dc22a..ac6a098380b 100644 --- a/packages/json-schema/src/index.ts +++ b/packages/json-schema/src/index.ts @@ -1,345 +1,10 @@ -import { - DecoratorContext, - EmitContext, - Enum, - Model, - ModelProperty, - Namespace, - Numeric, - Program, - Scalar, - Tuple, - Type, - Union, - isType, - setTypeSpecNamespace, - typespecTypeToJson, -} from "@typespec/compiler"; -import { createAssetEmitter } from "@typespec/compiler/emitter-framework"; -import { ValidatesRawJsonDecorator } from "../generated-defs/TypeSpec.JsonSchema.Private.js"; -import { - BaseUriDecorator, - ContainsDecorator, - ContentEncodingDecorator, - ContentMediaTypeDecorator, - ContentSchemaDecorator, - ExtensionDecorator, - IdDecorator, - JsonSchemaDecorator, - MaxContainsDecorator, - MaxPropertiesDecorator, - MinContainsDecorator, - MinPropertiesDecorator, - MultipleOfDecorator, - OneOfDecorator, - PrefixItemsDecorator, - UniqueItemsDecorator, -} from "../generated-defs/TypeSpec.JsonSchema.js"; -import { JsonSchemaEmitter } from "./json-schema-emitter.js"; -import { JSONSchemaEmitterOptions, createStateSymbol } from "./lib.js"; - export { JsonSchemaEmitter } from "./json-schema-emitter.js"; export { $flags, $lib, EmitterOptionsSchema, JSONSchemaEmitterOptions } from "./lib.js"; +/** @internal */ export const namespace = "TypeSpec.JsonSchema"; -export type JsonSchemaDeclaration = Model | Union | Enum | Scalar; - -const jsonSchemaKey = createStateSymbol("JsonSchema"); - -export async function $onEmit(context: EmitContext) { - const emitter = createAssetEmitter(context.program, JsonSchemaEmitter as any, context); - - if (emitter.getOptions().emitAllModels) { - emitter.emitProgram({ emitTypeSpecNamespace: false }); - } else { - for (const item of getJsonSchemaTypes(context.program)) { - emitter.emitType(item); - } - } - - await emitter.writeOutput(); -} - -export const $jsonSchema: JsonSchemaDecorator = ( - context: DecoratorContext, - target: Type, - baseUriOrId?: string -) => { - context.program.stateSet(jsonSchemaKey).add(target); - if (baseUriOrId) { - if (target.kind === "Namespace") { - context.call($baseUri, target, baseUriOrId); - } else { - context.call($id, target, baseUriOrId); - } - } -}; - -const baseUriKey = createStateSymbol("JsonSchema.baseURI"); -export const $baseUri: BaseUriDecorator = ( - context: DecoratorContext, - target: Namespace, - baseUri: string -) => { - context.program.stateMap(baseUriKey).set(target, baseUri); -}; - -export function getBaseUri(program: Program, target: Type) { - return program.stateMap(baseUriKey).get(target); -} - -export function findBaseUri( - program: Program, - target: JsonSchemaDeclaration | Namespace -): string | undefined { - let baseUrl: string | undefined; - let current: JsonSchemaDeclaration | Namespace | undefined = target; - do { - baseUrl = getBaseUri(program, current); - current = current.namespace; - } while (!baseUrl && current); - - return baseUrl; -} - -export function isJsonSchemaDeclaration(program: Program, target: JsonSchemaDeclaration) { - let current: JsonSchemaDeclaration | Namespace | undefined = target; - do { - if (getJsonSchema(program, current)) { - return true; - } - - current = current.namespace; - } while (current); - - return false; -} - -export function getJsonSchemaTypes(program: Program): (Namespace | Model)[] { - return [...(program.stateSet(jsonSchemaKey) || [])] as (Namespace | Model)[]; -} - -export function getJsonSchema(program: Program, target: Type) { - return program.stateSet(jsonSchemaKey).has(target); -} - -const multipleOfKey = createStateSymbol("JsonSchema.multipleOf"); -export const $multipleOf: MultipleOfDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: Numeric -) => { - context.program.stateMap(multipleOfKey).set(target, value); -}; - -export function getMultipleOfAsNumeric(program: Program, target: Type): Numeric | undefined { - return program.stateMap(multipleOfKey).get(target); -} -export function getMultipleOf(program: Program, target: Type): number | undefined { - return getMultipleOfAsNumeric(program, target)?.asNumber() ?? undefined; -} - -const idKey = createStateSymbol("JsonSchema.id"); -export const $id: IdDecorator = (context: DecoratorContext, target: Type, value: string) => { - context.program.stateMap(idKey).set(target, value); -}; - -export function getId(program: Program, target: Type) { - return program.stateMap(idKey).get(target); -} - -const oneOfKey = createStateSymbol("JsonSchema.oneOf"); -export const $oneOf: OneOfDecorator = (context: DecoratorContext, target: Type) => { - context.program.stateMap(oneOfKey).set(target, true); -}; - -export function isOneOf(program: Program, target: Type) { - return program.stateMap(oneOfKey).has(target); -} - -const containsKey = createStateSymbol("JsonSchema.contains"); -export const $contains: ContainsDecorator = ( - context: DecoratorContext, - target: Type, - value: Type -) => { - context.program.stateMap(containsKey).set(target, value); -}; - -export function getContains(program: Program, target: Type) { - return program.stateMap(containsKey).get(target); -} - -const minContainsKey = createStateSymbol("JsonSchema.minContains"); -export const $minContains: MinContainsDecorator = ( - context: DecoratorContext, - target: Type, - value: number -) => { - context.program.stateMap(minContainsKey).set(target, value); -}; - -export function getMinContains(program: Program, target: Type) { - return program.stateMap(minContainsKey).get(target); -} - -const maxContainsKey = createStateSymbol("JsonSchema.maxContains"); -export const $maxContains: MaxContainsDecorator = ( - context: DecoratorContext, - target: Type, - value: number -) => { - context.program.stateMap(maxContainsKey).set(target, value); -}; - -export function getMaxContains(program: Program, target: Type) { - return program.stateMap(maxContainsKey).get(target); -} - -const uniqueItemsKey = createStateSymbol("JsonSchema.uniqueItems"); -export const $uniqueItems: UniqueItemsDecorator = (context: DecoratorContext, target: Type) => { - context.program.stateMap(uniqueItemsKey).set(target, true); -}; - -export function getUniqueItems(program: Program, target: Type) { - return program.stateMap(uniqueItemsKey).get(target); -} - -const minPropertiesKey = createStateSymbol("JsonSchema.minProperties"); -export const $minProperties: MinPropertiesDecorator = ( - context: DecoratorContext, - target: Type, - value: number -) => { - context.program.stateMap(minPropertiesKey).set(target, value); -}; - -export function getMinProperties(program: Program, target: Type) { - return program.stateMap(minPropertiesKey).get(target); -} - -const maxPropertiesKey = createStateSymbol("JsonSchema.maxProperties"); -export const $maxProperties: MaxPropertiesDecorator = ( - context: DecoratorContext, - target: Type, - value: number -) => { - context.program.stateMap(maxPropertiesKey).set(target, value); -}; - -export function getMaxProperties(program: Program, target: Type) { - return program.stateMap(maxPropertiesKey).get(target); -} - -const contentEncodingKey = createStateSymbol("JsonSchema.contentEncoding"); -export const $contentEncoding: ContentEncodingDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: string -) => { - context.program.stateMap(contentEncodingKey).set(target, value); -}; - -export function getContentEncoding(program: Program, target: Type): string { - return program.stateMap(contentEncodingKey).get(target); -} - -const contentMediaType = createStateSymbol("JsonSchema.contentMediaType"); -export const $contentMediaType: ContentMediaTypeDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: string -) => { - context.program.stateMap(contentMediaType).set(target, value); -}; - -export function getContentMediaType(program: Program, target: Type): string { - return program.stateMap(contentMediaType).get(target); -} - -const contentSchemaKey = createStateSymbol("JsonSchema.contentSchema"); -export const $contentSchema: ContentSchemaDecorator = ( - context: DecoratorContext, - target: Scalar | ModelProperty, - value: Type -) => { - context.program.stateMap(contentSchemaKey).set(target, value); -}; - -export function getContentSchema(program: Program, target: Type) { - return program.stateMap(contentSchemaKey).get(target); -} - -const prefixItemsKey = createStateSymbol("JsonSchema.prefixItems"); -export const $prefixItems: PrefixItemsDecorator = ( - context: DecoratorContext, - target: Type, - value: Type -) => { - context.program.stateMap(prefixItemsKey).set(target, value); -}; - -export function getPrefixItems(program: Program, target: Type): Tuple | undefined { - return program.stateMap(prefixItemsKey).get(target); -} - -export interface ExtensionRecord { - key: string; - value: Type | unknown; -} - -const extensionsKey = createStateSymbol("JsonSchema.extension"); -export const $extension: ExtensionDecorator = ( - context: DecoratorContext, - target: Type, - key: string, - value: unknown -) => { - setExtension(context.program, target, key, value); -}; - -export function getExtensions(program: Program, target: Type): ExtensionRecord[] { - return program.stateMap(extensionsKey).get(target) ?? []; -} - -export function setExtension(program: Program, target: Type, key: string, value: unknown) { - const stateMap = program.stateMap(extensionsKey) as Map; - const extensions = stateMap.has(target) - ? stateMap.get(target)! - : stateMap.set(target, []).get(target)!; - - // Check if we were handed the `Json` template model - if (isJsonTemplateType(value)) { - extensions.push({ - key, - value: typespecTypeToJson(value.properties.get("value")!.type, target)[0], - }); - } else { - extensions.push({ key, value }); - } -} - -function isJsonTemplateType( - value: any -): value is Type & { kind: "Model"; name: "Json"; namespace: { name: "JsonSchema" } } { - return ( - typeof value === "object" && - value !== null && - isType(value) && - value.kind === "Model" && - value.name === "Json" && - value.namespace?.name === "JsonSchema" - ); -} -export const $validatesRawJson: ValidatesRawJsonDecorator = ( - context: DecoratorContext, - target: Model, - value: Type -) => { - const [_, diagnostics] = typespecTypeToJson(value, target); - if (diagnostics.length > 0) { - context.program.reportDiagnostics(diagnostics); - } -}; -setTypeSpecNamespace("Private", $validatesRawJson); +export * from "./decorators.js"; +export { $onEmit } from "./on-emit.js"; +/** @internal */ +export { $decorators } from "./tsp-index.js"; diff --git a/packages/json-schema/src/on-emit.ts b/packages/json-schema/src/on-emit.ts new file mode 100644 index 00000000000..c2e0321ecaa --- /dev/null +++ b/packages/json-schema/src/on-emit.ts @@ -0,0 +1,25 @@ +import { EmitContext, Enum, Model, Scalar, Union } from "@typespec/compiler"; +import { createAssetEmitter } from "@typespec/compiler/emitter-framework"; +import { getJsonSchemaTypes } from "./decorators.js"; +import { JsonSchemaEmitter } from "./json-schema-emitter.js"; +import { JSONSchemaEmitterOptions } from "./lib.js"; + +export { JsonSchemaEmitter } from "./json-schema-emitter.js"; +export { $flags, $lib, EmitterOptionsSchema, JSONSchemaEmitterOptions } from "./lib.js"; + +export const namespace = "TypeSpec.JsonSchema"; +export type JsonSchemaDeclaration = Model | Union | Enum | Scalar; + +export async function $onEmit(context: EmitContext) { + const emitter = createAssetEmitter(context.program, JsonSchemaEmitter as any, context); + + if (emitter.getOptions().emitAllModels) { + emitter.emitProgram({ emitTypeSpecNamespace: false }); + } else { + for (const item of getJsonSchemaTypes(context.program)) { + emitter.emitType(item); + } + } + + await emitter.writeOutput(); +} diff --git a/packages/json-schema/src/tsp-index.ts b/packages/json-schema/src/tsp-index.ts new file mode 100644 index 00000000000..ac065b5744c --- /dev/null +++ b/packages/json-schema/src/tsp-index.ts @@ -0,0 +1,48 @@ +import { TypeSpecJsonSchemaDecorators } from "../generated-defs/TypeSpec.JsonSchema.js"; +import { TypeSpecJsonSchemaPrivateDecorators } from "../generated-defs/TypeSpec.JsonSchema.Private.js"; +import { + $baseUri, + $contains, + $contentEncoding, + $contentMediaType, + $contentSchema, + $extension, + $id, + $jsonSchema, + $maxContains, + $maxProperties, + $minContains, + $minProperties, + $multipleOf, + $oneOf, + $prefixItems, + $uniqueItems, + $validatesRawJson, +} from "./decorators.js"; + +export { $flags, $lib } from "./lib.js"; + +/** @internal */ +export const $decorators = { + "TypeSpec.JsonSchema": { + jsonSchema: $jsonSchema, + baseUri: $baseUri, + id: $id, + oneOf: $oneOf, + multipleOf: $multipleOf, + contains: $contains, + minContains: $minContains, + maxContains: $maxContains, + uniqueItems: $uniqueItems, + minProperties: $minProperties, + maxProperties: $maxProperties, + contentEncoding: $contentEncoding, + prefixItems: $prefixItems, + contentMediaType: $contentMediaType, + contentSchema: $contentSchema, + extension: $extension, + } satisfies TypeSpecJsonSchemaDecorators, + "TypeSpec.JsonSchema.Private": { + validatesRawJson: $validatesRawJson, + } satisfies TypeSpecJsonSchemaPrivateDecorators, +}; diff --git a/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts b/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts index 5c9183b654c..66d2f18c990 100644 --- a/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts +++ b/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts @@ -78,3 +78,11 @@ export type InfoDecorator = ( target: Namespace, additionalInfo: Type ) => void; + +export type TypeSpecOpenAPIDecorators = { + operationId: OperationIdDecorator; + extension: ExtensionDecorator; + defaultResponse: DefaultResponseDecorator; + externalDocs: ExternalDocsDecorator; + info: InfoDecorator; +}; diff --git a/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts-test.ts b/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts-test.ts index 54e7d252a11..874d6d86e23 100644 --- a/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts-test.ts +++ b/packages/openapi/generated-defs/TypeSpec.OpenAPI.ts-test.ts @@ -1,32 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { - $defaultResponse, - $extension, - $externalDocs, - $info, - $operationId, -} from "@typespec/openapi"; -import type { - DefaultResponseDecorator, - ExtensionDecorator, - ExternalDocsDecorator, - InfoDecorator, - OperationIdDecorator, -} from "./TypeSpec.OpenAPI.js"; - -type Decorators = { - $operationId: OperationIdDecorator; - $extension: ExtensionDecorator; - $defaultResponse: DefaultResponseDecorator; - $externalDocs: ExternalDocsDecorator; - $info: InfoDecorator; -}; - +import { $decorators } from "@typespec/openapi"; +import type { TypeSpecOpenAPIDecorators } from "./TypeSpec.OpenAPI.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $operationId, - $extension, - $defaultResponse, - $externalDocs, - $info, -}; +const _: TypeSpecOpenAPIDecorators = $decorators["TypeSpec.OpenAPI"]; diff --git a/packages/openapi/lib/main.tsp b/packages/openapi/lib/main.tsp index 9847aa00028..da8917d9d6f 100644 --- a/packages/openapi/lib/main.tsp +++ b/packages/openapi/lib/main.tsp @@ -1,2 +1,2 @@ -import "../dist/src/index.js"; +import "../dist/src/tsp-index.js"; import "./decorators.tsp"; diff --git a/packages/openapi/src/decorators.ts b/packages/openapi/src/decorators.ts index 12356f22021..d080e37320e 100644 --- a/packages/openapi/src/decorators.ts +++ b/packages/openapi/src/decorators.ts @@ -18,12 +18,11 @@ import { ExternalDocsDecorator, InfoDecorator, OperationIdDecorator, + TypeSpecOpenAPIDecorators, } from "../generated-defs/TypeSpec.OpenAPI.js"; import { createStateSymbol, reportDiagnostic } from "./lib.js"; import { AdditionalInfo, ExtensionKey } from "./types.js"; -export const namespace = "TypeSpec.OpenAPI"; - const operationIdsKey = createStateSymbol("operationIds"); /** * Set a specific operation ID. @@ -188,3 +187,14 @@ export function resolveInfo(program: Program, entity: Namespace): AdditionalInfo function omitUndefined>(data: T): T { return Object.fromEntries(Object.entries(data).filter(([k, v]) => v !== undefined)) as any; } + +/** @internal */ +export const $decorators = { + "TypeSpec.OpenAPI": { + defaultResponse: $defaultResponse, + extension: $extension, + externalDocs: $externalDocs, + info: $info, + operationId: $operationId, + } satisfies TypeSpecOpenAPIDecorators, +}; diff --git a/packages/openapi/src/lib.ts b/packages/openapi/src/lib.ts index 0ebd2bd96bc..0c5c097fba1 100644 --- a/packages/openapi/src/lib.ts +++ b/packages/openapi/src/lib.ts @@ -1,6 +1,6 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler"; -export const libDef = { +export const $lib = createTypeSpecLibrary({ name: "@typespec/openapi", diagnostics: { "invalid-extension-key": { @@ -17,5 +17,6 @@ export const libDef = { }, }, }, -} as const; -export const { reportDiagnostic, createStateSymbol } = createTypeSpecLibrary(libDef); +}); + +export const { reportDiagnostic, createStateSymbol } = $lib; diff --git a/packages/openapi/src/tsp-index.ts b/packages/openapi/src/tsp-index.ts new file mode 100644 index 00000000000..a29d5b785e1 --- /dev/null +++ b/packages/openapi/src/tsp-index.ts @@ -0,0 +1,15 @@ +import { TypeSpecOpenAPIDecorators } from "../generated-defs/TypeSpec.OpenAPI.js"; +import { $defaultResponse, $extension, $externalDocs, $info, $operationId } from "./decorators.js"; + +export { $lib } from "./lib.js"; + +/** @internal */ +export const $decorators = { + "TypeSpec.OpenAPI": { + defaultResponse: $defaultResponse, + extension: $extension, + externalDocs: $externalDocs, + info: $info, + operationId: $operationId, + } satisfies TypeSpecOpenAPIDecorators, +}; diff --git a/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts b/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts index 47cda3356e5..056e197b757 100644 --- a/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts +++ b/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts @@ -15,3 +15,8 @@ export type UseRefDecorator = ( target: Model | ModelProperty, ref: string ) => void; + +export type TypeSpecOpenAPIDecorators = { + oneOf: OneOfDecorator; + useRef: UseRefDecorator; +}; diff --git a/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts-test.ts b/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts-test.ts index 0286e8320df..bc0db1f08a6 100644 --- a/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts-test.ts +++ b/packages/openapi3/generated-defs/TypeSpec.OpenAPI.ts-test.ts @@ -1,14 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { $oneOf, $useRef } from "@typespec/openapi3"; -import type { OneOfDecorator, UseRefDecorator } from "./TypeSpec.OpenAPI.js"; - -type Decorators = { - $oneOf: OneOfDecorator; - $useRef: UseRefDecorator; -}; - +import { $decorators } from "@typespec/openapi3"; +import type { TypeSpecOpenAPIDecorators } from "./TypeSpec.OpenAPI.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $oneOf, - $useRef, -}; +const _: TypeSpecOpenAPIDecorators = $decorators["TypeSpec.OpenAPI"]; diff --git a/packages/openapi3/lib/decorators.tsp b/packages/openapi3/lib/decorators.tsp index d791474709f..45582441488 100644 --- a/packages/openapi3/lib/decorators.tsp +++ b/packages/openapi3/lib/decorators.tsp @@ -1,4 +1,4 @@ -import "../dist/src/index.js"; +import "../dist/src/tsp-index.js"; namespace TypeSpec.OpenAPI; diff --git a/packages/openapi3/src/index.ts b/packages/openapi3/src/index.ts index f25aded32b3..f65942fb528 100644 --- a/packages/openapi3/src/index.ts +++ b/packages/openapi3/src/index.ts @@ -4,3 +4,4 @@ export { convertOpenAPI3Document } from "./cli/actions/convert/convert.js"; export * from "./decorators.js"; export { $lib } from "./lib.js"; export * from "./openapi.js"; +export { $decorators } from "./tsp-index.js"; diff --git a/packages/openapi3/src/tsp-index.ts b/packages/openapi3/src/tsp-index.ts new file mode 100644 index 00000000000..c355496d7ea --- /dev/null +++ b/packages/openapi3/src/tsp-index.ts @@ -0,0 +1,12 @@ +import { TypeSpecOpenAPIDecorators } from "../generated-defs/TypeSpec.OpenAPI.js"; +import { $oneOf, $useRef } from "./decorators.js"; + +export { $lib } from "./lib.js"; + +/** @internal */ +export const $decorators = { + "TypeSpec.OpenAPI": { + useRef: $useRef, + oneOf: $oneOf, + } satisfies TypeSpecOpenAPIDecorators, +}; diff --git a/packages/protobuf/generated-defs/TypeSpec.Protobuf.Private.ts b/packages/protobuf/generated-defs/TypeSpec.Protobuf.Private.ts new file mode 100644 index 00000000000..94df2de4ebe --- /dev/null +++ b/packages/protobuf/generated-defs/TypeSpec.Protobuf.Private.ts @@ -0,0 +1,15 @@ +import type { DecoratorContext, Model, Type } from "@typespec/compiler"; + +export type ExternRefDecorator = ( + context: DecoratorContext, + target: Model, + path: Type, + name: Type +) => void; + +export type _mapDecorator = (context: DecoratorContext, target: Model) => void; + +export type TypeSpecProtobufPrivateDecorators = { + externRef: ExternRefDecorator; + _map: _mapDecorator; +}; diff --git a/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts b/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts index 65997979dd3..ad3578a1e49 100644 --- a/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts +++ b/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts @@ -126,3 +126,12 @@ export type PackageDecorator = ( * ``` */ export type StreamDecorator = (context: DecoratorContext, target: Operation, mode: Type) => void; + +export type TypeSpecProtobufDecorators = { + message: MessageDecorator; + field: FieldDecorator; + reserve: ReserveDecorator; + service: ServiceDecorator; + package: PackageDecorator; + stream: StreamDecorator; +}; diff --git a/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts-test.ts b/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts-test.ts index f6d84a81355..1fb5318aeeb 100644 --- a/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts-test.ts +++ b/packages/protobuf/generated-defs/TypeSpec.Protobuf.ts-test.ts @@ -1,29 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { $field, $message, $package, $reserve, $service, $stream } from "@typespec/protobuf"; -import type { - FieldDecorator, - MessageDecorator, - PackageDecorator, - ReserveDecorator, - ServiceDecorator, - StreamDecorator, -} from "./TypeSpec.Protobuf.js"; - -type Decorators = { - $message: MessageDecorator; - $field: FieldDecorator; - $reserve: ReserveDecorator; - $service: ServiceDecorator; - $package: PackageDecorator; - $stream: StreamDecorator; -}; - +import { $decorators } from "@typespec/protobuf"; +import type { TypeSpecProtobufDecorators } from "./TypeSpec.Protobuf.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $message, - $field, - $reserve, - $service, - $package, - $stream, -}; +const _: TypeSpecProtobufDecorators = $decorators["TypeSpec.Protobuf"]; diff --git a/packages/protobuf/lib/proto.tsp b/packages/protobuf/lib/proto.tsp index 33d1ce2288c..315037677d1 100644 --- a/packages/protobuf/lib/proto.tsp +++ b/packages/protobuf/lib/proto.tsp @@ -1,4 +1,4 @@ -import "../dist/src/index.js"; +import "../dist/src/tsp-index.js"; namespace TypeSpec.Protobuf; @@ -25,7 +25,7 @@ namespace TypeSpec.Protobuf; * model Widget is Extern<"path/to/test.proto", "test.Widget">; * ``` */ -@externRef(Path, Name) +@Private.externRef(Path, Name) model Extern { // This _extern property is needed so that getEffectiveModelType will have something to look up. Without it, if an // Extern model is spread into the parameter of an operation, the resulting model is empty and carries no information @@ -135,7 +135,7 @@ alias integral = int32 | int64 | uint32 | uint64 | boolean; * @template Key the key type (any integral type or string) * @template Value the value type (any type other than another map) */ -@_map +@Private._map model Map {} /** @@ -310,3 +310,8 @@ enum StreamMode { * ``` */ extern dec stream(target: TypeSpec.Reflection.Operation, mode: StreamMode); + +namespace Private { + extern dec externRef(target: Reflection.Model, path: string, name: string); + extern dec _map(target: Reflection.Model); +} diff --git a/packages/protobuf/src/index.ts b/packages/protobuf/src/index.ts index 0dd36fbcc21..adbb3c913b6 100644 --- a/packages/protobuf/src/index.ts +++ b/packages/protobuf/src/index.ts @@ -5,3 +5,5 @@ export const namespace = "TypeSpec.Protobuf"; export * from "./proto.js"; export const $lib = TypeSpecProtobufLibrary; +/** @internal */ +export { $decorators } from "./tsp-index.js"; diff --git a/packages/protobuf/src/proto.ts b/packages/protobuf/src/proto.ts index 0f56edec306..aa1d3260508 100644 --- a/packages/protobuf/src/proto.ts +++ b/packages/protobuf/src/proto.ts @@ -23,6 +23,7 @@ import { ReserveDecorator, StreamDecorator, } from "../generated-defs/TypeSpec.Protobuf.js"; +import { ExternRefDecorator } from "../generated-defs/TypeSpec.Protobuf.Private.js"; import { StreamingMode } from "./ast.js"; import { ProtobufEmitterOptions, reportDiagnostic, state, TypeSpecProtobufLibrary } from "./lib.js"; import { createProtobufEmitter } from "./transform/index.js"; @@ -100,14 +101,16 @@ export function $_map(ctx: DecoratorContext, target: Model) { ctx.program.stateSet(state._map).add(target); } -export function $externRef( +export const $externRef: ExternRefDecorator = ( ctx: DecoratorContext, target: Model, - path: StringLiteral, - name: StringLiteral -) { - ctx.program.stateMap(state.externRef).set(target, [path.value, name.value]); -} + path: Type, + name: Type +) => { + ctx.program + .stateMap(state.externRef) + .set(target, [(path as StringLiteral).value, (name as StringLiteral).value]); +}; export const $stream: StreamDecorator = (ctx: DecoratorContext, target: Operation, mode: Type) => { const emitStreamingMode = { diff --git a/packages/protobuf/src/tsp-index.ts b/packages/protobuf/src/tsp-index.ts new file mode 100644 index 00000000000..06aea2a6063 --- /dev/null +++ b/packages/protobuf/src/tsp-index.ts @@ -0,0 +1,29 @@ +import { TypeSpecProtobufDecorators } from "../generated-defs/TypeSpec.Protobuf.js"; +import { TypeSpecProtobufPrivateDecorators } from "../generated-defs/TypeSpec.Protobuf.Private.js"; +import { + $_map, + $externRef, + $field, + $message, + $package, + $reserve, + $service, + $stream, +} from "./proto.js"; + +export { TypeSpecProtobufLibrary as $lib } from "./lib.js"; +/** @internal */ +export const $decorators = { + "TypeSpec.Protobuf": { + message: $message, + field: $field, + reserve: $reserve, + service: $service, + package: $package, + stream: $stream, + } satisfies TypeSpecProtobufDecorators, + "TypeSpec.Protobuf.Private": { + externRef: $externRef, + _map: $_map, + } satisfies TypeSpecProtobufPrivateDecorators, +}; diff --git a/packages/rest/generated-defs/TypeSpec.Rest.Private.ts b/packages/rest/generated-defs/TypeSpec.Rest.Private.ts index e42e08fd7c0..3b33a6f8a56 100644 --- a/packages/rest/generated-defs/TypeSpec.Rest.Private.ts +++ b/packages/rest/generated-defs/TypeSpec.Rest.Private.ts @@ -36,3 +36,11 @@ export type ResourceTypeForKeyParamDecorator = ( entity: ModelProperty, resourceType: Model ) => void; + +export type TypeSpecRestPrivateDecorators = { + resourceLocation: ResourceLocationDecorator; + validateHasKey: ValidateHasKeyDecorator; + validateIsError: ValidateIsErrorDecorator; + actionSegment: ActionSegmentDecorator; + resourceTypeForKeyParam: ResourceTypeForKeyParamDecorator; +}; diff --git a/packages/rest/generated-defs/TypeSpec.Rest.ts b/packages/rest/generated-defs/TypeSpec.Rest.ts index a4e7f920b3b..93d0db32ab9 100644 --- a/packages/rest/generated-defs/TypeSpec.Rest.ts +++ b/packages/rest/generated-defs/TypeSpec.Rest.ts @@ -215,3 +215,22 @@ export type CopyResourceKeyParametersDecorator = ( target: Model, filter?: string ) => void; + +export type TypeSpecRestDecorators = { + autoRoute: AutoRouteDecorator; + segment: SegmentDecorator; + segmentOf: SegmentOfDecorator; + actionSeparator: ActionSeparatorDecorator; + resource: ResourceDecorator; + parentResource: ParentResourceDecorator; + readsResource: ReadsResourceDecorator; + createsResource: CreatesResourceDecorator; + createsOrReplacesResource: CreatesOrReplacesResourceDecorator; + createsOrUpdatesResource: CreatesOrUpdatesResourceDecorator; + updatesResource: UpdatesResourceDecorator; + deletesResource: DeletesResourceDecorator; + listsResource: ListsResourceDecorator; + action: ActionDecorator; + collectionAction: CollectionActionDecorator; + copyResourceKeyParameters: CopyResourceKeyParametersDecorator; +}; diff --git a/packages/rest/generated-defs/TypeSpec.Rest.ts-test.ts b/packages/rest/generated-defs/TypeSpec.Rest.ts-test.ts index d2e7e0bcc70..a753ee020f5 100644 --- a/packages/rest/generated-defs/TypeSpec.Rest.ts-test.ts +++ b/packages/rest/generated-defs/TypeSpec.Rest.ts-test.ts @@ -1,76 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { - $action, - $actionSeparator, - $autoRoute, - $collectionAction, - $copyResourceKeyParameters, - $createsOrReplacesResource, - $createsOrUpdatesResource, - $createsResource, - $deletesResource, - $listsResource, - $parentResource, - $readsResource, - $resource, - $segment, - $segmentOf, - $updatesResource, -} from "@typespec/rest"; -import type { - ActionDecorator, - ActionSeparatorDecorator, - AutoRouteDecorator, - CollectionActionDecorator, - CopyResourceKeyParametersDecorator, - CreatesOrReplacesResourceDecorator, - CreatesOrUpdatesResourceDecorator, - CreatesResourceDecorator, - DeletesResourceDecorator, - ListsResourceDecorator, - ParentResourceDecorator, - ReadsResourceDecorator, - ResourceDecorator, - SegmentDecorator, - SegmentOfDecorator, - UpdatesResourceDecorator, -} from "./TypeSpec.Rest.js"; - -type Decorators = { - $autoRoute: AutoRouteDecorator; - $segment: SegmentDecorator; - $segmentOf: SegmentOfDecorator; - $actionSeparator: ActionSeparatorDecorator; - $resource: ResourceDecorator; - $parentResource: ParentResourceDecorator; - $readsResource: ReadsResourceDecorator; - $createsResource: CreatesResourceDecorator; - $createsOrReplacesResource: CreatesOrReplacesResourceDecorator; - $createsOrUpdatesResource: CreatesOrUpdatesResourceDecorator; - $updatesResource: UpdatesResourceDecorator; - $deletesResource: DeletesResourceDecorator; - $listsResource: ListsResourceDecorator; - $action: ActionDecorator; - $collectionAction: CollectionActionDecorator; - $copyResourceKeyParameters: CopyResourceKeyParametersDecorator; -}; - +import { $decorators } from "@typespec/rest"; +import type { TypeSpecRestDecorators } from "./TypeSpec.Rest.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $autoRoute, - $segment, - $segmentOf, - $actionSeparator, - $resource, - $parentResource, - $readsResource, - $createsResource, - $createsOrReplacesResource, - $createsOrUpdatesResource, - $updatesResource, - $deletesResource, - $listsResource, - $action, - $collectionAction, - $copyResourceKeyParameters, -}; +const _: TypeSpecRestDecorators = $decorators["TypeSpec.Rest"]; diff --git a/packages/rest/lib/resource.tsp b/packages/rest/lib/resource.tsp index 25f87ebd031..af2f6b89fe3 100644 --- a/packages/rest/lib/resource.tsp +++ b/packages/rest/lib/resource.tsp @@ -1,5 +1,4 @@ import "@typespec/http"; -import "../dist/src/index.js"; import "../dist/src/internal-decorators.js"; namespace TypeSpec.Rest.Resource; diff --git a/packages/rest/lib/rest.tsp b/packages/rest/lib/rest.tsp index dae696d37ae..bf0ebb831f1 100644 --- a/packages/rest/lib/rest.tsp +++ b/packages/rest/lib/rest.tsp @@ -1,6 +1,7 @@ import "@typespec/http"; import "./rest-decorators.tsp"; import "./resource.tsp"; +import "../dist/src/tsp-index.js"; namespace TypeSpec.Rest; diff --git a/packages/rest/src/index.ts b/packages/rest/src/index.ts index 560e61aa5b1..c5e3bf4c70a 100644 --- a/packages/rest/src/index.ts +++ b/packages/rest/src/index.ts @@ -1,4 +1,6 @@ -export const namespace = "TypeSpec.Rest"; +export { $lib } from "./lib.js"; export * from "./resource.js"; export * from "./rest.js"; -export * from "./validate.js"; + +/** @internal */ +export { $decorators } from "./tsp-index.js"; diff --git a/packages/rest/src/internal-decorators.ts b/packages/rest/src/internal-decorators.ts index 63dff4b4b49..df5ada4fbb2 100644 --- a/packages/rest/src/internal-decorators.ts +++ b/packages/rest/src/internal-decorators.ts @@ -1,16 +1,19 @@ import { DecoratorContext, getTypeName, isErrorModel, Type } from "@typespec/compiler"; import { + TypeSpecRestPrivateDecorators, ValidateHasKeyDecorator, ValidateIsErrorDecorator, } from "../generated-defs/TypeSpec.Rest.Private.js"; import { createStateSymbol, reportDiagnostic } from "./lib.js"; -import { getResourceTypeKey } from "./resource.js"; +import { $resourceTypeForKeyParam, getResourceTypeKey } from "./resource.js"; +import { $actionSegment, $resourceLocation } from "./rest.js"; +/** @internal */ export const namespace = "TypeSpec.Rest.Private"; const validatedMissingKey = createStateSymbol("validatedMissing"); // Workaround for the lack of template constraints https://github.com/microsoft/typespec/issues/377 -export const $validateHasKey: ValidateHasKeyDecorator = ( +const $validateHasKey: ValidateHasKeyDecorator = ( context: DecoratorContext, target: Type, value: Type @@ -31,7 +34,7 @@ export const $validateHasKey: ValidateHasKeyDecorator = ( const validatedErrorKey = createStateSymbol("validatedError"); // Workaround for the lack of template constraints https://github.com/microsoft/typespec/issues/377 -export const $validateIsError: ValidateIsErrorDecorator = ( +const $validateIsError: ValidateIsErrorDecorator = ( context: DecoratorContext, target: Type, value: Type @@ -49,3 +52,14 @@ export const $validateIsError: ValidateIsErrorDecorator = ( context.program.stateSet(validatedErrorKey).add(value); } }; + +/** @internal */ +export const $decorators = { + "TypeSpec.Rest.Private": { + actionSegment: $actionSegment, + resourceLocation: $resourceLocation, + resourceTypeForKeyParam: $resourceTypeForKeyParam, + validateHasKey: $validateHasKey, + validateIsError: $validateIsError, + } satisfies TypeSpecRestPrivateDecorators, +}; diff --git a/packages/rest/src/lib.ts b/packages/rest/src/lib.ts index 0db6bd32e88..e22853a84ee 100644 --- a/packages/rest/src/lib.ts +++ b/packages/rest/src/lib.ts @@ -1,6 +1,6 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler"; -const libDefinition = { +export const $lib = createTypeSpecLibrary({ name: "@typespec/rest", diagnostics: { "not-key-type": { @@ -46,9 +46,6 @@ const libDefinition = { }, }, }, -} as const; +}); -const restLib = createTypeSpecLibrary(libDefinition); -const { reportDiagnostic, createDiagnostic, createStateSymbol } = restLib; - -export { createDiagnostic, createStateSymbol, reportDiagnostic, restLib }; +export const { reportDiagnostic, createDiagnostic, createStateSymbol } = $lib; diff --git a/packages/rest/src/tsp-index.ts b/packages/rest/src/tsp-index.ts new file mode 100644 index 00000000000..4c050c9be33 --- /dev/null +++ b/packages/rest/src/tsp-index.ts @@ -0,0 +1,43 @@ +import { TypeSpecRestDecorators } from "../generated-defs/TypeSpec.Rest.js"; +import { $copyResourceKeyParameters, $parentResource } from "./resource.js"; +import { + $action, + $actionSeparator, + $autoRoute, + $collectionAction, + $createsOrReplacesResource, + $createsOrUpdatesResource, + $createsResource, + $deletesResource, + $listsResource, + $readsResource, + $resource, + $segment, + $segmentOf, + $updatesResource, +} from "./rest.js"; + +export { $lib } from "./lib.js"; +export { $onValidate } from "./validate.js"; + +/** @internal */ +export const $decorators = { + "TypeSpec.Rest": { + autoRoute: $autoRoute, + segment: $segment, + segmentOf: $segmentOf, + actionSeparator: $actionSeparator, + resource: $resource, + parentResource: $parentResource, + readsResource: $readsResource, + createsResource: $createsResource, + createsOrReplacesResource: $createsOrReplacesResource, + createsOrUpdatesResource: $createsOrUpdatesResource, + updatesResource: $updatesResource, + deletesResource: $deletesResource, + listsResource: $listsResource, + action: $action, + collectionAction: $collectionAction, + copyResourceKeyParameters: $copyResourceKeyParameters, + } satisfies TypeSpecRestDecorators, +}; diff --git a/packages/tspd/src/gen-extern-signatures/decorators-signatures.ts b/packages/tspd/src/gen-extern-signatures/decorators-signatures.ts index 2013c3ff36b..210ae9dbc30 100644 --- a/packages/tspd/src/gen-extern-signatures/decorators-signatures.ts +++ b/packages/tspd/src/gen-extern-signatures/decorators-signatures.ts @@ -17,57 +17,42 @@ import { DecoratorSignature } from "./types.js"; const line = "\n"; export function generateSignatureTests( + namespaceName: string, importName: string, decoratorSignatureImport: string, decorators: DecoratorSignature[] ): string { const content: Doc[] = []; + const decRecord = getDecoratorRecordForNamespaceName(namespaceName); content.push([ "/** An error here would mean that the decorator is not exported or doesn't have the right name. */", line, - "import {", - decorators.map((x) => x.jsName).join(","), - `} from "`, + `import { $decorators } from "`, importName, `";`, line, ]); - content.push([ - "import type {", - decorators.map((x) => x.typeName).join(","), - `} from "`, - decoratorSignatureImport, - `";`, - line, - ]); - content.push(line); - - content.push([ - "type Decorators = {", - line, - decorators.map((x) => renderDoc([x.jsName, ": ", x.typeName])).join(","), - - "};", - line, - ]); + content.push(`import type { ${decRecord} } from "${decoratorSignatureImport}";`); content.push(line); content.push([ "/** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */", line, - "const _: Decorators = {", - line, - decorators.map((x) => x.jsName).join(","), - - "};", - line, + `const _: ${decRecord} = $decorators["${namespaceName}"]`, ]); return renderDoc(content); } -export function generateSignatures(program: Program, decorators: DecoratorSignature[]): string { +function getDecoratorRecordForNamespaceName(namespaceName: string) { + return `${namespaceName.replaceAll(".", "")}Decorators`; +} +export function generateSignatures( + program: Program, + decorators: DecoratorSignature[], + namespaceName: string +): string { const compilerImports = new Set(); const localTypes = new Set(); const decoratorDeclarations: string[] = decorators.map((x) => getTSSignatureForDecorator(x)); @@ -86,8 +71,20 @@ export function generateSignatures(program: Program, decorators: DecoratorSignat line, line, decoratorDeclarations.join("\n\n"), + line, + line, ]; + content.push([ + `export type ${getDecoratorRecordForNamespaceName(namespaceName)} = {`, + line, + decorators.map((x) => renderDoc([x.name.slice(1), ": ", x.typeName])).join(","), + "};", + line, + ]); + + content.push(line); + return renderDoc(content); function useCompilerType(name: string) { @@ -342,16 +339,17 @@ function getDocComment(type: Type): string { : `@${tag.tagName.sv}`; for (const content of tag.content) { for (const line of content.text.split("\n")) { + const cleaned = sanitizeDocComment(line); if (first) { if (hasContentFirstLine) { - tagLines.push(`${tagStart} ${line}`); + tagLines.push(`${tagStart} ${cleaned}`); } else { - tagLines.push(tagStart, line); + tagLines.push(tagStart, cleaned); } first = false; } else { - tagLines.push(line); + tagLines.push(cleaned); } } } @@ -362,6 +360,11 @@ function getDocComment(type: Type): string { return "/**\n" + docLines.map((x) => `* ${x}`).join("\n") + "\n*/\n"; } +function sanitizeDocComment(doc: string): string { + // Issue to escape @internal and other tsdoc tags https://github.com/microsoft/TypeScript/issues/47679 + return doc.replaceAll("@internal", `@_internal`); +} + function checkIfTagHasDocOnSameLine(tag: DocTag): boolean { const start = tag.content[0]?.pos; const end = tag.content[0]?.end; diff --git a/packages/tspd/src/gen-extern-signatures/gen-extern-signatures.ts b/packages/tspd/src/gen-extern-signatures/gen-extern-signatures.ts index af240ebcc19..4d0e29ac801 100644 --- a/packages/tspd/src/gen-extern-signatures/gen-extern-signatures.ts +++ b/packages/tspd/src/gen-extern-signatures/gen-extern-signatures.ts @@ -93,10 +93,10 @@ export async function generateExternDecorators( for (const [ns, nsDecorators] of decorators.entries()) { const base = ns === "" ? "__global__" : ns; const file = `${base}.ts`; - files[file] = await format(generateSignatures(program, nsDecorators)); + files[file] = await format(generateSignatures(program, nsDecorators, ns)); if (!ns.includes(".Private")) { files[`${base}.ts-test.ts`] = await format( - generateSignatureTests(packageName, `./${base}.js`, nsDecorators) + generateSignatureTests(ns, packageName, `./${base}.js`, nsDecorators) ); } } diff --git a/packages/tspd/test/gen-extern-signature/decorators-signatures.test.ts b/packages/tspd/test/gen-extern-signature/decorators-signatures.test.ts index cddc32231a1..e6fcf2e6117 100644 --- a/packages/tspd/test/gen-extern-signature/decorators-signatures.test.ts +++ b/packages/tspd/test/gen-extern-signature/decorators-signatures.test.ts @@ -45,6 +45,10 @@ it("generate simple decorator with no parameters", async () => { ${importLine(["Type"])} export type SimpleDecorator = (context: DecoratorContext, target: Type) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -69,6 +73,10 @@ describe("generate target type", () => { ${importLine([expected])} export type SimpleDecorator = (context: DecoratorContext, target: ${expected}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -85,6 +93,10 @@ export type SimpleDecorator = (context: DecoratorContext, target: ${expected}) = ${importLine([...expected])} export type SimpleDecorator = (context: DecoratorContext, target: ${expected.join(" | ")}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -102,6 +114,10 @@ export type SimpleDecorator = (context: DecoratorContext, target: ${expected.joi ${importLine([expected])} export type SimpleDecorator = (context: DecoratorContext, target: ${expected}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -128,6 +144,10 @@ describe("generate parameter type", () => { ${importLine(["Type", expected])} export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: ${expected}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -144,6 +164,10 @@ export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: ${ ${importLine(["Type", ...expected])} export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: ${expected.join(" | ")}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -172,6 +196,10 @@ export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: ${ ${importLine(["Type", ...(expected === "Numeric" ? ["Numeric"] : [])])} export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: ${expected}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -190,6 +218,10 @@ export interface Info { } export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: Info) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -206,6 +238,10 @@ export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: In ${importLine(["Type", expected])} export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: ${expected}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -225,6 +261,10 @@ ${importLine(["Type", "Model"])} * Some doc comment */ export type SimpleDecorator = (context: DecoratorContext, target: Type, ...args: Model[]) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -249,6 +289,10 @@ export type SimpleDecorator = (context: DecoratorContext, target: Type, ...args: ${importLine(["Type", ...(expected === "Numeric[]" ? ["Numeric"] : [])])} export type SimpleDecorator = (context: DecoratorContext, target: Type, ...args: ${expected}) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -268,6 +312,10 @@ ${importLine(["Type"])} * Some doc comment */ export type SimpleDecorator = (context: DecoratorContext, target: Type) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); @@ -292,6 +340,10 @@ ${importLine(["Type"])} * @param arg2 This is the second argument */ export type SimpleDecorator = (context: DecoratorContext, target: Type, arg1: Type, arg2: Type) => void; + +export type Decorators = { + simple: SimpleDecorator; +}; `, }); }); diff --git a/packages/tspd/tsconfig.json b/packages/tspd/tsconfig.json index 29995c35cc2..c24f79fff2b 100644 --- a/packages/tspd/tsconfig.json +++ b/packages/tspd/tsconfig.json @@ -5,8 +5,7 @@ "outDir": "dist", "rootDir": ".", "tsBuildInfoFile": "temp/tsconfig.tsbuildinfo", - "skipLibCheck": true, - "lib": ["DOM"] + "skipLibCheck": true }, "include": ["src/**/*.ts", "test/**/*.ts"] } diff --git a/packages/versioning/generated-defs/TypeSpec.Versioning.ts b/packages/versioning/generated-defs/TypeSpec.Versioning.ts index 1c28cbba909..35b7e2f42d8 100644 --- a/packages/versioning/generated-defs/TypeSpec.Versioning.ts +++ b/packages/versioning/generated-defs/TypeSpec.Versioning.ts @@ -225,3 +225,15 @@ export type ReturnTypeChangedFromDecorator = ( version: EnumMember, oldType: Type ) => void; + +export type TypeSpecVersioningDecorators = { + versioned: VersionedDecorator; + useDependency: UseDependencyDecorator; + added: AddedDecorator; + removed: RemovedDecorator; + renamedFrom: RenamedFromDecorator; + madeOptional: MadeOptionalDecorator; + madeRequired: MadeRequiredDecorator; + typeChangedFrom: TypeChangedFromDecorator; + returnTypeChangedFrom: ReturnTypeChangedFromDecorator; +}; diff --git a/packages/versioning/generated-defs/TypeSpec.Versioning.ts-test.ts b/packages/versioning/generated-defs/TypeSpec.Versioning.ts-test.ts index c09f96075c1..eb4e861f436 100644 --- a/packages/versioning/generated-defs/TypeSpec.Versioning.ts-test.ts +++ b/packages/versioning/generated-defs/TypeSpec.Versioning.ts-test.ts @@ -1,48 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { - $added, - $madeOptional, - $madeRequired, - $removed, - $renamedFrom, - $returnTypeChangedFrom, - $typeChangedFrom, - $useDependency, - $versioned, -} from "@typespec/versioning"; -import type { - AddedDecorator, - MadeOptionalDecorator, - MadeRequiredDecorator, - RemovedDecorator, - RenamedFromDecorator, - ReturnTypeChangedFromDecorator, - TypeChangedFromDecorator, - UseDependencyDecorator, - VersionedDecorator, -} from "./TypeSpec.Versioning.js"; - -type Decorators = { - $versioned: VersionedDecorator; - $useDependency: UseDependencyDecorator; - $added: AddedDecorator; - $removed: RemovedDecorator; - $renamedFrom: RenamedFromDecorator; - $madeOptional: MadeOptionalDecorator; - $madeRequired: MadeRequiredDecorator; - $typeChangedFrom: TypeChangedFromDecorator; - $returnTypeChangedFrom: ReturnTypeChangedFromDecorator; -}; - +import { $decorators } from "@typespec/versioning"; +import type { TypeSpecVersioningDecorators } from "./TypeSpec.Versioning.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $versioned, - $useDependency, - $added, - $removed, - $renamedFrom, - $madeOptional, - $madeRequired, - $typeChangedFrom, - $returnTypeChangedFrom, -}; +const _: TypeSpecVersioningDecorators = $decorators["TypeSpec.Versioning"]; diff --git a/packages/versioning/src/index.ts b/packages/versioning/src/index.ts index 841f7bafa77..e8884b0dd6b 100644 --- a/packages/versioning/src/index.ts +++ b/packages/versioning/src/index.ts @@ -24,3 +24,6 @@ export { buildVersionProjections, type VersionProjections } from "./projection.j export * from "./types.js"; export * from "./validate.js"; export * from "./versioning.js"; + +/** @internal */ +export { $decorators } from "./tsp-index.js"; diff --git a/packages/versioning/src/lib.ts b/packages/versioning/src/lib.ts index 0470a4274f4..9128cccd158 100644 --- a/packages/versioning/src/lib.ts +++ b/packages/versioning/src/lib.ts @@ -1,10 +1,6 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler"; -export const { - reportDiagnostic, - createStateSymbol, - stateKeys: VersioningStateKeys, -} = createTypeSpecLibrary({ +export const $lib = createTypeSpecLibrary({ name: "@typespec/versioning", diagnostics: { "versioned-dependency-tuple": { @@ -121,3 +117,5 @@ export const { returnTypeChangedFrom: { description: "State for @returnTypeChangedFrom decorator" }, }, }); + +export const { reportDiagnostic, createStateSymbol, stateKeys: VersioningStateKeys } = $lib; diff --git a/packages/versioning/src/tsp-index.ts b/packages/versioning/src/tsp-index.ts new file mode 100644 index 00000000000..411ea199f6d --- /dev/null +++ b/packages/versioning/src/tsp-index.ts @@ -0,0 +1,28 @@ +import type { TypeSpecVersioningDecorators } from "../generated-defs/TypeSpec.Versioning.js"; +import { + $added, + $madeOptional, + $madeRequired, + $removed, + $renamedFrom, + $returnTypeChangedFrom, + $typeChangedFrom, + $useDependency, + $versioned, +} from "./decorators.js"; + +export { $lib } from "./lib.js"; +/** @internal */ +export const $decorators = { + "TypeSpec.Versioning": { + versioned: $versioned, + useDependency: $useDependency, + added: $added, + removed: $removed, + renamedFrom: $renamedFrom, + madeOptional: $madeOptional, + madeRequired: $madeRequired, + typeChangedFrom: $typeChangedFrom, + returnTypeChangedFrom: $returnTypeChangedFrom, + } satisfies TypeSpecVersioningDecorators, +}; diff --git a/packages/xml/generated-defs/TypeSpec.Xml.ts b/packages/xml/generated-defs/TypeSpec.Xml.ts index 2944a85814a..c1432a08f4e 100644 --- a/packages/xml/generated-defs/TypeSpec.Xml.ts +++ b/packages/xml/generated-defs/TypeSpec.Xml.ts @@ -170,3 +170,11 @@ export type NsDecorator = ( * Mark an enum as declaring XML namespaces. See `@ns` */ export type NsDeclarationsDecorator = (context: DecoratorContext, target: Enum) => void; + +export type TypeSpecXmlDecorators = { + name: NameDecorator; + attribute: AttributeDecorator; + unwrapped: UnwrappedDecorator; + ns: NsDecorator; + nsDeclarations: NsDeclarationsDecorator; +}; diff --git a/packages/xml/generated-defs/TypeSpec.Xml.ts-test.ts b/packages/xml/generated-defs/TypeSpec.Xml.ts-test.ts index 14f4e8532ee..e80ea8725f7 100644 --- a/packages/xml/generated-defs/TypeSpec.Xml.ts-test.ts +++ b/packages/xml/generated-defs/TypeSpec.Xml.ts-test.ts @@ -1,26 +1,5 @@ /** An error here would mean that the decorator is not exported or doesn't have the right name. */ -import { $attribute, $name, $ns, $nsDeclarations, $unwrapped } from "@typespec/xml"; -import type { - AttributeDecorator, - NameDecorator, - NsDeclarationsDecorator, - NsDecorator, - UnwrappedDecorator, -} from "./TypeSpec.Xml.js"; - -type Decorators = { - $name: NameDecorator; - $attribute: AttributeDecorator; - $unwrapped: UnwrappedDecorator; - $ns: NsDecorator; - $nsDeclarations: NsDeclarationsDecorator; -}; - +import { $decorators } from "@typespec/xml"; +import type { TypeSpecXmlDecorators } from "./TypeSpec.Xml.js"; /** An error here would mean that the exported decorator is not using the same signature. Make sure to have export const $decName: DecNameDecorator = (...) => ... */ -const _: Decorators = { - $name, - $attribute, - $unwrapped, - $ns, - $nsDeclarations, -}; +const _: TypeSpecXmlDecorators = $decorators["TypeSpec.Xml"]; diff --git a/packages/xml/src/decorators.ts b/packages/xml/src/decorators.ts index 65b3d0e0de9..08101617032 100644 --- a/packages/xml/src/decorators.ts +++ b/packages/xml/src/decorators.ts @@ -16,6 +16,7 @@ import type { import { XmlStateKeys, reportDiagnostic } from "./lib.js"; import type { XmlNamespace } from "./types.js"; +/** @internal */ export const namespace = "TypeSpec.Xml"; export const $name: NameDecorator = (context, target, name) => { diff --git a/packages/xml/src/index.ts b/packages/xml/src/index.ts index 1bd5b778822..14f3728a583 100644 --- a/packages/xml/src/index.ts +++ b/packages/xml/src/index.ts @@ -9,4 +9,8 @@ export { isUnwrapped, } from "./decorators.js"; export { getXmlEncoding } from "./encoding.js"; +export { $lib } from "./lib.js"; export type { XmlEncodeData, XmlEncoding, XmlNamespace } from "./types.js"; + +/** @internal */ +export { $decorators } from "./tsp-index.js"; diff --git a/packages/xml/src/lib.ts b/packages/xml/src/lib.ts index ec79f414f0d..d8381d7c1f4 100644 --- a/packages/xml/src/lib.ts +++ b/packages/xml/src/lib.ts @@ -1,10 +1,6 @@ import { createTypeSpecLibrary, paramMessage } from "@typespec/compiler"; -export const { - reportDiagnostic, - createStateSymbol, - stateKeys: XmlStateKeys, -} = createTypeSpecLibrary({ +export const $lib = createTypeSpecLibrary({ name: "@typespec/xml", diagnostics: { "ns-enum-not-declaration": { @@ -48,3 +44,5 @@ export const { nsDeclaration: { description: "Mark an enum that declares Xml Namespaces" }, }, } as const); + +export const { reportDiagnostic, createStateSymbol, stateKeys: XmlStateKeys } = $lib; diff --git a/packages/xml/src/tsp-index.ts b/packages/xml/src/tsp-index.ts new file mode 100644 index 00000000000..3033ec70256 --- /dev/null +++ b/packages/xml/src/tsp-index.ts @@ -0,0 +1,15 @@ +import type { TypeSpecXmlDecorators } from "../generated-defs/TypeSpec.Xml.js"; +import { $attribute, $name, $ns, $nsDeclarations, $unwrapped } from "./decorators.js"; + +export { $lib } from "./lib.js"; + +/** @internal */ +export const $decorators = { + "TypeSpec.Xml": { + attribute: $attribute, + name: $name, + ns: $ns, + nsDeclarations: $nsDeclarations, + unwrapped: $unwrapped, + } satisfies TypeSpecXmlDecorators, +}; From 1e3ccc24304894c0baa3ff436ee14a6eea9c81da Mon Sep 17 00:00:00 2001 From: Jorge Rangel <102122018+jorgerangel-msft@users.noreply.github.com> Date: Mon, 12 Aug 2024 15:32:40 -0500 Subject: [PATCH 2/2] Move model serialization ctor to model provider (#4143) fixes: https://github.com/microsoft/typespec/issues/3871 --- .../MrwSerializationTypeDefinition.cs | 139 +------------ .../src/ScmTypeFactory.cs | 6 +- .../JsonModelCoreTests.cs | 6 +- .../MrwSerializationConstructorTests.cs | 84 -------- .../MrwSerializationTypeDefinitionTests.cs | 42 +--- .../PersistableModelCoreTests.cs | 6 +- .../src/Providers/ModelFactoryProvider.cs | 22 ++- .../src/Providers/ModelProvider.cs | 187 +++++++++++++++--- .../test/Providers/ModelProviderTests.cs | 93 +++++++-- .../TypeProviderWriter_WriteModel.cs | 11 ++ .../TypeProviderWriter_WriteModelAsStruct.cs | 11 ++ .../AnonymousBodyRequest.Serialization.cs | 21 -- .../Generated/Models/AnonymousBodyRequest.cs | 21 ++ .../Generated/Models/Friend.Serialization.cs | 9 - .../src/Generated/Models/Friend.cs | 12 ++ .../FriendlyModelRequest.Serialization.cs | 9 - .../Generated/Models/FriendlyModelRequest.cs | 12 ++ ...equiredNullableProperties.Serialization.cs | 11 -- .../ModelWithRequiredNullableProperties.cs | 14 ++ .../Models/ProjectedModel.Serialization.cs | 9 - .../src/Generated/Models/ProjectedModel.cs | 12 ++ ...ProjectedNameModelRequest.Serialization.cs | 9 - .../Models/ProjectedNameModelRequest.cs | 12 ++ ...rnsAnonymousModelResponse.Serialization.cs | 8 - .../Models/ReturnsAnonymousModelResponse.cs | 11 ++ .../Models/RoundTripModel.Serialization.cs | 32 --- .../src/Generated/Models/RoundTripModel.cs | 32 +++ .../Generated/Models/Thing.Serialization.cs | 21 -- .../src/Generated/Models/Thing.cs | 21 ++ 29 files changed, 444 insertions(+), 439 deletions(-) delete mode 100644 packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationConstructorTests.cs diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs index 249dd6a1b13..ae2f057c709 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/Providers/MrwSerializationTypeDefinition.cs @@ -26,7 +26,6 @@ namespace Microsoft.Generator.CSharp.ClientModel.Providers /// internal class MrwSerializationTypeDefinition : TypeProvider { - private const string PrivateAdditionalPropertiesPropertyDescription = "Keeps track of any properties unknown to the library."; private const string PrivateAdditionalPropertiesPropertyName = "_serializedAdditionalRawData"; private const string JsonModelWriteCoreMethodName = "JsonModelWriteCore"; private const string JsonModelCreateCoreMethodName = "JsonModelCreateCore"; @@ -34,7 +33,6 @@ internal class MrwSerializationTypeDefinition : TypeProvider private const string PersistableModelCreateCoreMethodName = "PersistableModelCreateCore"; private const string WriteAction = "writing"; private const string ReadAction = "reading"; - private const string AdditionalRawDataVarName = "serializedAdditionalRawData"; private readonly ParameterProvider _utf8JsonWriterParameter = new("writer", $"The JSON writer.", typeof(Utf8JsonWriter)); private readonly ParameterProvider _utf8JsonReaderParameter = new("reader", $"The JSON reader.", typeof(Utf8JsonReader), isRef: true); private readonly ParameterProvider _serializationOptionsParameter = @@ -46,22 +44,19 @@ internal class MrwSerializationTypeDefinition : TypeProvider private readonly ScopedApi _mrwOptionsParameterSnippet; private readonly ScopedApi _jsonElementParameterSnippet; private readonly ScopedApi _isNotEqualToWireConditionSnippet; - private readonly CSharpType _privateAdditionalRawDataPropertyType = typeof(IDictionary); private readonly CSharpType _jsonModelTInterface; private readonly CSharpType? _jsonModelObjectInterface; private readonly CSharpType _persistableModelTInterface; private readonly CSharpType? _persistableModelObjectInterface; - private TypeProvider _model; + private readonly ModelProvider _model; private readonly InputModelType _inputModel; private readonly FieldProvider? _rawDataField; private readonly bool _isStruct; private ConstructorProvider? _serializationConstructor; // Flag to determine if the model should override the serialization methods private readonly bool _shouldOverrideMethods; - // TODO -- we should not be needing this if we resolve https://github.com/microsoft/typespec/issues/3796 - private readonly MrwSerializationTypeDefinition? _baseSerializationProvider; - public MrwSerializationTypeDefinition(InputModelType inputModel, TypeProvider modelProvider) + public MrwSerializationTypeDefinition(InputModelType inputModel, ModelProvider modelProvider) { _model = modelProvider; _inputModel = inputModel; @@ -71,11 +66,7 @@ public MrwSerializationTypeDefinition(InputModelType inputModel, TypeProvider mo _jsonModelObjectInterface = _isStruct ? (CSharpType)typeof(IJsonModel) : null; _persistableModelTInterface = new CSharpType(typeof(IPersistableModel<>), _model.Type); _persistableModelObjectInterface = _isStruct ? (CSharpType)typeof(IPersistableModel) : null; - - if (inputModel.BaseModel is not null) - _baseSerializationProvider = ClientModelPlugin.Instance.TypeFactory.CreateSerializations(inputModel.BaseModel, modelProvider).OfType().FirstOrDefault(); - - _rawDataField = BuildRawDataField(); + _rawDataField = _model.Fields.FirstOrDefault(f => f.Name == PrivateAdditionalPropertiesPropertyName); _shouldOverrideMethods = _model.Type.BaseType != null && _model.Type.BaseType is { IsFrameworkType: false }; _utf8JsonWriterSnippet = _utf8JsonWriterParameter.As(); _mrwOptionsParameterSnippet = _serializationOptionsParameter.As(); @@ -86,25 +77,15 @@ public MrwSerializationTypeDefinition(InputModelType inputModel, TypeProvider mo protected override string GetNamespace() => _model.Type.Namespace; protected override TypeSignatureModifiers GetDeclarationModifiers() => _model.DeclarationModifiers; - private ConstructorProvider SerializationConstructor => _serializationConstructor ??= BuildSerializationConstructor(); + private ConstructorProvider SerializationConstructor => _serializationConstructor ??= _model.FullConstructor; protected override string BuildRelativeFilePath() => Path.Combine("src", "Generated", "Models", $"{Name}.Serialization.cs"); protected override string BuildName() => _model.Name; - /// - /// Builds the fields for the model by adding the raw data field for serialization. - /// - /// The list of for the model. - protected override FieldProvider[] BuildFields() - { - return _rawDataField != null ? [_rawDataField] : Array.Empty(); - } - protected override ConstructorProvider[] BuildConstructors() { List constructors = new(); - bool serializationCtorParamsMatch = false; bool ctorWithNoParamsExist = false; foreach (var ctor in _model.Constructors) @@ -116,21 +97,6 @@ protected override ConstructorProvider[] BuildConstructors() { ctorWithNoParamsExist = true; } - - if (!serializationCtorParamsMatch) - { - // Check if the model constructor parameters match the serialization constructor parameters - if (initializationCtorParams.SequenceEqual(SerializationConstructor.Signature.Parameters)) - { - serializationCtorParamsMatch = true; - } - } - } - - // Add the serialization constructor if it doesn't match any of the existing constructors - if (!serializationCtorParamsMatch) - { - constructors.Add(SerializationConstructor); } // Add an empty constructor if the model doesn't have one @@ -139,39 +105,7 @@ protected override ConstructorProvider[] BuildConstructors() constructors.Add(BuildEmptyConstructor()); } - return constructors.ToArray(); - } - - /// - /// Builds the raw data field for the model to be used for serialization. - /// - /// The constructed if the model should generate the field. - private FieldProvider? BuildRawDataField() - { - if (_isStruct) - { - return null; - } - - // check if there is a raw data field on my base, if so, we do not have to have one here - if (_baseSerializationProvider?._rawDataField != null) - { - return null; - } - - var modifiers = FieldModifiers.Private; - if (!_model.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Sealed)) - { - modifiers |= FieldModifiers.Protected; - } - - var rawDataField = new FieldProvider( - modifiers: modifiers, - type: _privateAdditionalRawDataPropertyType, - description: FormattableStringHelpers.FromString(PrivateAdditionalPropertiesPropertyDescription), - name: PrivateAdditionalPropertiesPropertyName); - - return rawDataField; + return [.. constructors]; } /// @@ -527,28 +461,6 @@ internal MethodProvider BuildPersistableModelGetFormatFromOptionsObjectDeclarati ); } - /// - /// Builds the serialization constructor for the model. - /// - /// The constructed serialization constructor. - internal ConstructorProvider BuildSerializationConstructor() - { - var (serializationCtorParameters, serializationCtorInitializer) = BuildSerializationConstructorParameters(); - - return new ConstructorProvider( - signature: new ConstructorSignature( - Type, - $"Initializes a new instance of {Type:C}", - MethodSignatureModifiers.Internal, - serializationCtorParameters, - Initializer: serializationCtorInitializer), - bodyStatements: new MethodBodyStatement[] - { - GetPropertyInitializers() - }, - this); - } - private MethodBodyStatement[] BuildJsonModelWriteMethodBody(MethodProvider jsonModelWriteCoreMethod) { var coreMethodSignature = jsonModelWriteCoreMethod.Signature; @@ -699,7 +611,6 @@ private ValueExpression[] GetSerializationCtorParameterValues() { var parameters = SerializationConstructor.Signature.Parameters; ValueExpression[] serializationCtorParameters = new ValueExpression[parameters.Count]; - var serializationCtorParameterValues = new Dictionary(parameters.Count); // Map property variable names to their corresponding parameter values for (int i = 0; i < parameters.Count; i++) @@ -771,7 +682,7 @@ private List BuildDeserializePropertiesStatements(ScopedApi // deserialize the raw data properties if (_rawDataField != null) { - var elementType = _privateAdditionalRawDataPropertyType.Arguments[1].FrameworkType; + var elementType = _rawDataField.Type.Arguments[1].FrameworkType; var rawDataDeserializationValue = GetValueTypeDeserializationExpression(elementType, jsonProperty.Value(), SerializationFormat.Default); propertyDeserializationStatements.Add(new IfStatement(_isNotEqualToWireConditionSnippet) { @@ -959,44 +870,6 @@ private static MethodBodyStatement NullCheckCollectionItemIfRequired( ? new IfElseStatement(arrayItemVar.ValueKindEqualsNull(), assignNull, deserializeValue) : deserializeValue; - /// - /// Builds the parameters for the serialization constructor by iterating through the input model properties. - /// It then adds raw data field to the constructor if it doesn't already exist in the list of constructed parameters. - /// - /// The list of parameters for the serialization parameter. - private (IReadOnlyList Parameters, ConstructorInitializer? Initializer) BuildSerializationConstructorParameters() - { - var baseConstructor = _baseSerializationProvider?.SerializationConstructor.Signature; - var baseParameters = baseConstructor?.Parameters ?? []; - var parameterCapacity = baseParameters.Count + _inputModel.Properties.Count; - var parameterNames = baseParameters.Select(p => p.Name).ToHashSet(); - var constructorParameters = new List(parameterCapacity); - - // add the base parameters - constructorParameters.AddRange(baseParameters); - - // construct the initializer using the parameters from base signature - var constructorInitializer = new ConstructorInitializer(true, baseParameters); - - foreach (var property in _model.Properties) - { - // skip those non-spec properties - if (property.WireInfo == null) - { - continue; - } - constructorParameters.Add(property.AsParameter); - } - - // Append the raw data field if it doesn't already exist in the constructor parameters - if (_rawDataField != null) - { - constructorParameters.Add(_rawDataField.AsParameter); - } - - return (constructorParameters, constructorInitializer); - } - private ConstructorProvider BuildEmptyConstructor() { var accessibility = _isStruct ? MethodSignatureModifiers.Public : MethodSignatureModifiers.Internal; diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ScmTypeFactory.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ScmTypeFactory.cs index 65b5ed3fa84..48c0cbed76f 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ScmTypeFactory.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/src/ScmTypeFactory.cs @@ -32,7 +32,11 @@ protected override IReadOnlyList CreateSerializationsCore(InputTyp switch (inputType) { case InputModelType inputModel when inputModel.Usage.HasFlag(InputModelTypeUsage.Json): - return [new MrwSerializationTypeDefinition(inputModel, typeProvider)]; + if (typeProvider is ModelProvider modelProvider) + { + return [new MrwSerializationTypeDefinition(inputModel, modelProvider)]; + } + return []; case InputEnumType { IsExtensible: true } inputEnumType: if (ClientModelPlugin.Instance.TypeFactory.CreateCSharpType(inputEnumType)?.UnderlyingEnumType.Equals(typeof(string)) == true) { diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/JsonModelCoreTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/JsonModelCoreTests.cs index 059503adf90..2cd534cbe9a 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/JsonModelCoreTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/JsonModelCoreTests.cs @@ -16,13 +16,13 @@ public class JsonModelCoreTests public JsonModelCoreTests() { MockHelpers.LoadMockPlugin(createSerializationsCore: (inputType, typeProvider) - => inputType is InputModelType modeltype ? [new MockMrwProvider(modeltype, typeProvider)] : []); + => inputType is InputModelType modeltype ? [new MockMrwProvider(modeltype, (typeProvider as ModelProvider)!)] : []); } private class MockMrwProvider : MrwSerializationTypeDefinition { - public MockMrwProvider(InputModelType inputModel, TypeProvider typeProvider) - : base(inputModel, typeProvider) + public MockMrwProvider(InputModelType inputModel, ModelProvider modelProvider) + : base(inputModel, modelProvider) { } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationConstructorTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationConstructorTests.cs deleted file mode 100644 index 0fefcd0ed6f..00000000000 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationConstructorTests.cs +++ /dev/null @@ -1,84 +0,0 @@ -// Copyright (c) Microsoft Corporation. All rights reserved. -// Licensed under the MIT License. - -using System; -using System.Collections.Generic; -using Microsoft.Generator.CSharp.ClientModel.Providers; -using Microsoft.Generator.CSharp.Input; -using Microsoft.Generator.CSharp.Primitives; -using Microsoft.Generator.CSharp.Providers; -using NUnit.Framework; - -namespace Microsoft.Generator.CSharp.ClientModel.Tests.Providers.MrwSerializationTypeDefinitions -{ - public class MrwSerializationConstructorTests - { - public MrwSerializationConstructorTests() - { - MockHelpers.LoadMockPlugin(createSerializationsCore: (inputType, typeProvider) - => inputType is InputModelType modeltype ? [new MockMrwProvider(modeltype, typeProvider)] : []); - } - - private class MockMrwProvider : MrwSerializationTypeDefinition - { - public MockMrwProvider(InputModelType inputModel, TypeProvider typeProvider) - : base(inputModel, typeProvider) - { - } - - protected override MethodProvider[] BuildMethods() => []; - - protected override FieldProvider[] BuildFields() => []; - } - - [Test] - public void TestBuildConstructors() - { - var baseProperties = new List - { - new InputModelProperty("prop1", "prop1", string.Empty, InputPrimitiveType.String, true, false, false), - new InputModelProperty("prop2", "prop2", string.Empty, InputPrimitiveType.String, false, false, false), - }; - var derivedProperties = new List - { - new InputModelProperty("prop3", "prop3", string.Empty, InputPrimitiveType.String, true, false, false), - new InputModelProperty("prop4", "prop4", string.Empty, InputPrimitiveType.String, false, false, false), - }; - var inputBase = new InputModelType("baseModel", "baseModel", null, null, null, InputModelTypeUsage.Input, baseProperties, null, new List(), null, null, new Dictionary(), null, false); - var inputDerived = new InputModelType("derivedModel", "derivedModel", null, null, null, InputModelTypeUsage.Input, derivedProperties, inputBase, new List(), null, null, new Dictionary(), null, false); - ((List)inputBase.DerivedModels).Add(inputDerived); - - var (baseModel, baseSerialization) = MrwSerializationTypeDefinitionTests.CreateModelAndSerialization(inputBase); - var (derivedModel, derivedSerialization) = MrwSerializationTypeDefinitionTests.CreateModelAndSerialization(inputDerived); - - var baseCtors = baseSerialization.Constructors; - var derivedCtors = derivedSerialization.Constructors; - Assert.AreEqual(2, baseCtors.Count); - Assert.AreEqual(2, derivedCtors.Count); - // the second ctor of the ctors should be parameterless - Assert.AreEqual(0, baseCtors[1].Signature.Parameters.Count); - Assert.AreEqual(0, derivedCtors[1].Signature.Parameters.Count); - // the first ctor should contain certain number of parameters - var baseParameters = baseCtors[0].Signature.Parameters; - var derivedParameters = derivedCtors[0].Signature.Parameters; - Assert.AreEqual(3, baseParameters.Count); // 2 properties + raw data - Assert.AreEqual(5, derivedParameters.Count); // 4 properties + raw data - Assert.AreEqual("prop1", baseParameters[0].Name); - Assert.AreEqual(new CSharpType(typeof(string)), baseParameters[0].Type); - Assert.AreEqual("prop2", baseParameters[1].Name); - Assert.AreEqual(new CSharpType(typeof(string), true), baseParameters[1].Type); - Assert.AreEqual("serializedAdditionalRawData", baseParameters[2].Name); - Assert.AreEqual(new CSharpType(typeof(IDictionary)), baseParameters[2].Type); - Assert.AreEqual("prop1", baseParameters[0].Name); - Assert.AreEqual(new CSharpType(typeof(string)), derivedParameters[0].Type); - Assert.AreEqual("prop2", baseParameters[1].Name); - Assert.AreEqual(new CSharpType(typeof(string), true), derivedParameters[1].Type); - Assert.AreEqual("serializedAdditionalRawData", derivedParameters[2].Name); - Assert.AreEqual(new CSharpType(typeof(IDictionary)), derivedParameters[2].Type); - Assert.AreEqual("prop3", derivedParameters[3].Name); - Assert.AreEqual(new CSharpType(typeof(string)), derivedParameters[3].Type); - Assert.AreEqual("prop4", derivedParameters[4].Name); - Assert.AreEqual(new CSharpType(typeof(string), true), derivedParameters[4].Type); - } - } -} diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs index 759151f6167..0ffd7305f24 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/MrwSerializationTypeDefinitionTests.cs @@ -22,10 +22,10 @@ internal class MrwSerializationTypeDefinitionTests public MrwSerializationTypeDefinitionTests() { MockHelpers.LoadMockPlugin(createSerializationsCore: (inputType, typeProvider) => - inputType is InputModelType modelType ? [new MrwSerializationTypeDefinition(modelType, typeProvider)] : []); + inputType is InputModelType modelType ? [new MrwSerializationTypeDefinition(modelType, (typeProvider as ModelProvider)!)] : []); } - internal static (TypeProvider Model, MrwSerializationTypeDefinition Serialization) CreateModelAndSerialization(InputModelType inputModel) + internal static (ModelProvider Model, MrwSerializationTypeDefinition Serialization) CreateModelAndSerialization(InputModelType inputModel) { var model = ClientModelPlugin.Instance.TypeFactory.CreateModel(inputModel); var serializations = model!.SerializationProviders; @@ -33,7 +33,7 @@ internal static (TypeProvider Model, MrwSerializationTypeDefinition Serializatio Assert.AreEqual(1, serializations.Count); Assert.IsInstanceOf(serializations[0]); - return (model, (MrwSerializationTypeDefinition)serializations[0]); + return ((model as ModelProvider)!, (MrwSerializationTypeDefinition)serializations[0]); } [Test] @@ -471,37 +471,15 @@ public void TestBuildPersistableModelGetFormatMethodObjectDeclaration() Assert.AreEqual(1, bodyExpression?.Arguments.Count); } - [Test] - public void TestBuildSerializationConstructor() - { - var inputModel = new InputModelType("mockInputModel", "mockNamespace", "public", null, null, InputModelTypeUsage.Input | InputModelTypeUsage.Output, Array.Empty(), null, new List(), null, null, new Dictionary(), null, false); - var (model, serialization) = CreateModelAndSerialization(inputModel); - var constructor = serialization.BuildSerializationConstructor(); - - Assert.IsNotNull(constructor); - var constructorSignature = constructor?.Signature; - Assert.IsNotNull(constructorSignature); - Assert.AreEqual(1, constructorSignature?.Parameters.Count); - - var param = constructorSignature?.Parameters[0]; - Assert.IsNotNull(param); - Assert.AreEqual("serializedAdditionalRawData", param?.Name); - } - [Test] public void TestBuildFields() { var inputModel = new InputModelType("mockInputModel", "mockNamespace", "public", null, null, InputModelTypeUsage.Input | InputModelTypeUsage.Output, Array.Empty(), null, new List(), null, null, new Dictionary(), null, false); - var (model, serialization) = CreateModelAndSerialization(inputModel); + var (_, serialization) = CreateModelAndSerialization(inputModel); var fields = serialization.Fields; - // Assert Assert.IsNotNull(fields); - Assert.AreEqual(1, fields.Count); - Assert.AreEqual("_serializedAdditionalRawData", fields[0].Name); - - var type = fields[0].Type; - Assert.IsTrue(type.IsCollection); + Assert.AreEqual(0, fields.Count); } [Test] @@ -517,17 +495,13 @@ public void TestBuildConstructor_ValidateConstructors() var inputModel = new InputModelType("TestModel", "TestModel", "public", null, "Test model.", InputModelTypeUsage.Input | InputModelTypeUsage.Output, properties, null, Array.Empty(), null, null, new Dictionary(), null, false); - var (model, serialization) = CreateModelAndSerialization(inputModel); + var (_, serialization) = CreateModelAndSerialization(inputModel); var ctors = serialization.Constructors; Assert.IsNotNull(ctors); - Assert.AreEqual(2, ctors.Count); - - var serializationCtor = ctors[0]; - Assert.AreEqual(MethodSignatureModifiers.Internal, serializationCtor.Signature.Modifiers); - Assert.AreEqual(5, serializationCtor.Signature.Parameters.Count); + Assert.AreEqual(1, ctors.Count); - var emptyCtor = ctors[1]; + var emptyCtor = ctors[0]; Assert.AreEqual(MethodSignatureModifiers.Internal, emptyCtor.Signature.Modifiers); Assert.AreEqual(0, emptyCtor.Signature.Parameters.Count); } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/PersistableModelCoreTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/PersistableModelCoreTests.cs index 9f5464b422a..7f95019d948 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/PersistableModelCoreTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp.ClientModel/test/Providers/MrwSerializationTypeDefinitions/PersistableModelCoreTests.cs @@ -16,13 +16,13 @@ public class PersistableModelCoreTests public PersistableModelCoreTests() { MockHelpers.LoadMockPlugin(createSerializationsCore: (inputType, typeProvider) - => inputType is InputModelType modeltype ? [new MockMrwProvider(modeltype, typeProvider)] : []); + => inputType is InputModelType modeltype ? [new MockMrwProvider(modeltype, (typeProvider as ModelProvider)!)] : []); } private class MockMrwProvider : MrwSerializationTypeDefinition { - public MockMrwProvider(InputModelType inputModel, TypeProvider typeProvider) - : base(inputModel, typeProvider) + public MockMrwProvider(InputModelType inputModel, ModelProvider modelProvider) + : base(inputModel, modelProvider) { } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelFactoryProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelFactoryProvider.cs index 8fff3afcd54..d0a98fd7edf 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelFactoryProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelFactoryProvider.cs @@ -17,6 +17,7 @@ namespace Microsoft.Generator.CSharp.Providers internal class ModelFactoryProvider : TypeProvider { private const string ModelFactorySuffix = "ModelFactory"; + private const string AdditionalRawDataParameterName = "serializedAdditionalRawData"; private readonly IEnumerable _models; @@ -66,17 +67,18 @@ protected override MethodProvider[] BuildMethods() var methods = new List(_models.Count()); foreach (var model in _models) { - var modelProvider = CodeModelPlugin.Instance.TypeFactory.CreateModel(model); + var modelProvider = CodeModelPlugin.Instance.TypeFactory.CreateModel(model) as ModelProvider; if (modelProvider is null || modelProvider.DeclarationModifiers.HasFlag(TypeSignatureModifiers.Internal)) continue; + var modelCtor = modelProvider.FullConstructor; var signature = new MethodSignature( modelProvider.Name, null, MethodSignatureModifiers.Static | MethodSignatureModifiers.Public, modelProvider.Type, $"A new {modelProvider.Type:C} instance for mocking.", - GetParameters(modelProvider)); + GetParameters(modelCtor)); var docs = new XmlDocProvider(); docs.Summary = modelProvider.XmlDocs?.Summary; @@ -98,7 +100,7 @@ .. GetCollectionInitialization(signature), return [.. methods]; } - private IReadOnlyList GetCtorParams(MethodSignature signature) + private static IReadOnlyList GetCtorParams(MethodSignature signature) { var expressions = new List(signature.Parameters.Count); foreach (var param in signature.Parameters) @@ -112,6 +114,7 @@ private IReadOnlyList GetCtorParams(MethodSignature signature) expressions.Add(param); } } + expressions.Add(Null); return [.. expressions]; } @@ -133,20 +136,21 @@ private IReadOnlyList GetCollectionInitialization(MethodSig return [.. statements]; } - private IReadOnlyList GetParameters(TypeProvider modelProvider) + private static IReadOnlyList GetParameters(ConstructorProvider modelFullConstructor) { - var parameters = new List(modelProvider.Properties.Count); - foreach (var property in modelProvider.Properties) + var modelCtorParams = modelFullConstructor.Signature.Parameters; + var parameters = new List(modelCtorParams.Count); + foreach (var param in modelCtorParams) { - if (property.Modifiers.HasFlag(MethodSignatureModifiers.Internal)) + if (param.Name.Equals(AdditionalRawDataParameterName)) continue; - parameters.Add(GetModelFactoryParam(property.AsParameter)); + parameters.Add(GetModelFactoryParam(param)); } return [.. parameters]; } - private ParameterProvider GetModelFactoryParam(ParameterProvider parameter) + private static ParameterProvider GetModelFactoryParam(ParameterProvider parameter) { return new ParameterProvider( parameter.Name, diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs index 88591d9c2a9..9a9c6fbb08b 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/src/Providers/ModelProvider.cs @@ -16,12 +16,19 @@ namespace Microsoft.Generator.CSharp.Providers { public sealed class ModelProvider : TypeProvider { + private const string PrivateAdditionalPropertiesPropertyDescription = "Keeps track of any properties unknown to the library."; + private const string PrivateAdditionalPropertiesPropertyName = "_serializedAdditionalRawData"; private readonly InputModelType _inputModel; protected override FormattableString Description { get; } private readonly bool _isStruct; private readonly TypeSignatureModifiers _declarationModifiers; + private readonly CSharpType _privateAdditionalRawDataPropertyType = typeof(IDictionary); + private readonly Lazy? _baseTypeProvider; + private FieldProvider? _rawDataField; + private ModelProvider? _baseModelProvider; + private ConstructorProvider? _fullConstructor; public ModelProvider(InputModelType inputModel) { @@ -29,6 +36,7 @@ public ModelProvider(InputModelType inputModel) Description = inputModel.Description != null ? FormattableStringHelpers.FromString(inputModel.Description) : $"The {Name}."; _declarationModifiers = TypeSignatureModifiers.Partial | (inputModel.ModelAsStruct ? TypeSignatureModifiers.ReadOnly | TypeSignatureModifiers.Struct : TypeSignatureModifiers.Class); + if (inputModel.Access == "internal") { _declarationModifiers |= TypeSignatureModifiers.Internal; @@ -40,17 +48,25 @@ public ModelProvider(InputModelType inputModel) _declarationModifiers |= TypeSignatureModifiers.Abstract; } + if (inputModel.BaseModel is not null) + { + _baseTypeProvider = new(() => CodeModelPlugin.Instance.TypeFactory.CreateModel(inputModel.BaseModel)); + } + _isStruct = inputModel.ModelAsStruct; } + private ModelProvider? BaseModelProvider + => _baseModelProvider ??= (_baseTypeProvider?.Value is ModelProvider baseModelProvider ? baseModelProvider : null); + private FieldProvider? RawDataField => _rawDataField ??= BuildRawDataField(); + + public ConstructorProvider FullConstructor => _fullConstructor ??= BuildFullConstructor(); + protected override string GetNamespace() => CodeModelPlugin.Instance.Configuration.ModelNamespace; protected override CSharpType? GetBaseType() { - if (_inputModel.BaseModel == null) - return null; - - return CodeModelPlugin.Instance.TypeFactory.CreateModel(_inputModel.BaseModel)?.Type; + return BaseModelProvider?.Type; } protected override TypeProvider[] BuildSerializationProviders() @@ -64,6 +80,15 @@ protected override TypeProvider[] BuildSerializationProviders() protected override TypeSignatureModifiers GetDeclarationModifiers() => _declarationModifiers; + /// + /// Builds the fields for the model by adding the raw data field. + /// + /// The list of for the model. + protected override FieldProvider[] BuildFields() + { + return RawDataField != null ? [RawDataField] : []; + } + protected override PropertyProvider[] BuildProperties() { var propertiesCount = _inputModel.Properties.Count; @@ -86,7 +111,7 @@ protected override ConstructorProvider[] BuildConstructors() { if (_inputModel.IsUnknownDiscriminatorModel) { - return []; + return [FullConstructor]; } // Build the initialization constructor @@ -95,7 +120,7 @@ protected override ConstructorProvider[] BuildConstructors() : _inputModel.Usage.HasFlag(InputModelTypeUsage.Input) ? MethodSignatureModifiers.Public : MethodSignatureModifiers.Internal; - var (constructorParameters, constructorInitializer) = BuildConstructorParameters(); + var (constructorParameters, constructorInitializer) = BuildConstructorParameters(true); var constructor = new ConstructorProvider( signature: new ConstructorSignature( @@ -106,28 +131,64 @@ protected override ConstructorProvider[] BuildConstructors() Initializer: constructorInitializer), bodyStatements: new MethodBodyStatement[] { - GetPropertyInitializers(constructorParameters) + GetPropertyInitializers(true, parameters: constructorParameters) }, this); + if (!constructorParameters.SequenceEqual(FullConstructor.Signature.Parameters)) + { + return [constructor, FullConstructor]; + } + return [constructor]; } - // TODO -- figure out how to reuse this piece of code because similar code exists in MrwSerializationDefinition as well https://github.com/microsoft/typespec/issues/3871 - private (IReadOnlyList Parameters, ConstructorInitializer? Initializer) BuildConstructorParameters() + /// + /// Builds the internal constructor for the model which contains all public properties + /// as parameters. + /// + private ConstructorProvider BuildFullConstructor() { - // we need to find all the properties on our base model, we add the reverse because our convention is to have the properties from base model first. - var baseProperties = _inputModel.GetAllBaseModels().Reverse().SelectMany(model => CodeModelPlugin.Instance.TypeFactory.CreateModel(model)?.Properties ?? []); - var basePropertyCount = baseProperties.Count(); - var parameterCapacity = basePropertyCount + Properties.Count; - var baseParameters = new List(basePropertyCount); - var constructorParameters = new List(parameterCapacity); - - // add the base parameters + var (ctorParameters, ctorInitializer) = BuildConstructorParameters(false); + + return new ConstructorProvider( + signature: new ConstructorSignature( + Type, + $"Initializes a new instance of {Type:C}", + MethodSignatureModifiers.Internal, + ctorParameters, + Initializer: ctorInitializer), + bodyStatements: new MethodBodyStatement[] + { + GetPropertyInitializers(false) + }, + this); + } + + private (IReadOnlyList Parameters, ConstructorInitializer? Initializer) BuildConstructorParameters( + bool isPrimaryConstructor) + { + var baseParameters = new List(); + var constructorParameters = new List(); + IEnumerable baseProperties = []; + + if (isPrimaryConstructor) + { + baseProperties = _inputModel.GetAllBaseModels() + .Reverse() + .SelectMany(model => CodeModelPlugin.Instance.TypeFactory.CreateModel(model)?.Properties ?? []); + } + else if (BaseModelProvider?.FullConstructor.Signature != null) + { + baseParameters.AddRange(BaseModelProvider.FullConstructor.Signature.Parameters); + } + + // add the base parameters, if any foreach (var property in baseProperties) { - AddInitializationParameter(baseParameters, property, _isStruct); + AddInitializationParameterForCtor(baseParameters, property, _isStruct, isPrimaryConstructor); } + constructorParameters.AddRange(baseParameters); // construct the initializer using the parameters from base signature @@ -135,19 +196,32 @@ protected override ConstructorProvider[] BuildConstructors() foreach (var property in Properties) { - AddInitializationParameter(constructorParameters, property, _isStruct); + AddInitializationParameterForCtor(constructorParameters, property, _isStruct, isPrimaryConstructor); + } + + if (RawDataField != null && !isPrimaryConstructor) + { + constructorParameters.Add(RawDataField.AsParameter); } return (constructorParameters, constructorInitializer); + } - static void AddInitializationParameter(List parameters, PropertyProvider property, bool isStruct) + private static void AddInitializationParameterForCtor( + List parameters, + PropertyProvider property, + bool isStruct, + bool isPrimaryConstructor) + { + // We only add those properties with wire info indicating they are coming from specs. + if (property.WireInfo is not { } wireInfo) { - // we only add those properties with wire info indicating they are coming from specs. - if (property.WireInfo is not { } wireInfo) - { - return; - } - if (isStruct || (wireInfo is { IsRequired: true, IsDiscriminator: false } && !property.Type.IsLiteral)) + return; + } + + if (isPrimaryConstructor) + { + if (isStruct || (wireInfo.IsRequired && !wireInfo.IsDiscriminator && !property.Type.IsLiteral)) { if (!wireInfo.IsReadOnly) { @@ -155,18 +229,35 @@ static void AddInitializationParameter(List parameters, Prope } } } + else + { + // For the serialization constructor, we always add the property as a parameter + parameters.Add(property.AsParameter); + } } - private MethodBodyStatement GetPropertyInitializers(IReadOnlyList parameters) + private MethodBodyStatement GetPropertyInitializers( + bool isPrimaryConstructor, + IReadOnlyList? parameters = null) { - List methodBodyStatements = new(); - - Dictionary parameterMap = parameters.ToDictionary( - parameter => parameter.Name, - parameter => parameter); + List methodBodyStatements = new(Properties.Count + 1); + Dictionary parameterMap = parameters?.ToDictionary(p => p.Name) ?? []; foreach (var property in Properties) { + // skip those non-spec properties + if (property.WireInfo == null) + { + continue; + } + + if (!isPrimaryConstructor) + { + // always add the property for the serialization constructor + methodBodyStatements.Add(property.Assign(property.AsParameter).Terminate()); + continue; + } + ValueExpression? initializationValue = null; if (parameterMap.TryGetValue(property.AsParameter.Name, out var parameter) || _isStruct) @@ -194,7 +285,39 @@ private MethodBodyStatement GetPropertyInitializers(IReadOnlyList + /// Builds the raw data field for the model to be used for serialization. + /// + /// The constructed if the model should generate the field. + private FieldProvider? BuildRawDataField() + { + // check if there is a raw data field on my base, if so, we do not have to have one here + if (BaseModelProvider?.RawDataField != null) + { + return null; + } + + var modifiers = FieldModifiers.Private; + if (!DeclarationModifiers.HasFlag(TypeSignatureModifiers.Sealed)) + { + modifiers |= FieldModifiers.Protected; + } + + var rawDataField = new FieldProvider( + modifiers: modifiers, + type: _privateAdditionalRawDataPropertyType, + description: FormattableStringHelpers.FromString(PrivateAdditionalPropertiesPropertyDescription), + name: PrivateAdditionalPropertiesPropertyName); + + return rawDataField; + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviderTests.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviderTests.cs index 9be72c0d4b5..4384435efc7 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviderTests.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Providers/ModelProviderTests.cs @@ -102,7 +102,7 @@ public static IEnumerable BuildProperties_ValidatePropertySettersT }; [Test] - public void BuildConstructor_ValidateConstructors() + public void TestBuildConstructor_ValidateConstructors() { var properties = new List{ new InputModelProperty("requiredString", "requiredString", "", InputPrimitiveType.String, true, false, false), @@ -132,15 +132,20 @@ public void BuildConstructor_ValidateConstructors() var ctors = modelTypeProvider.Constructors; Assert.IsNotNull(ctors); - Assert.AreEqual(1, ctors.Count); + Assert.AreEqual(2, ctors.Count); - var initializationCtor = ctors[0]; - Assert.AreEqual(MethodSignatureModifiers.Public, initializationCtor.Signature.Modifiers); + var initializationCtor = ctors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); + Assert.IsNotNull(initializationCtor); + Assert.AreEqual(MethodSignatureModifiers.Public, initializationCtor!.Signature.Modifiers); Assert.AreEqual(3, initializationCtor.Signature.Parameters.Count); + + var secondaryCtor = ctors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal)); + Assert.IsNotNull(secondaryCtor); + Assert.AreEqual(6, secondaryCtor!.Signature.Parameters.Count); } [Test] - public void BuildConstructor_ValidateConstructorsInDerivedModel() + public void TestBuildConstructor_ValidateConstructorsInDerivedModel() { var baseProperties = new List { @@ -163,15 +168,18 @@ public void BuildConstructor_ValidateConstructorsInDerivedModel() Assert.NotNull(baseModel); var baseCtors = baseModel!.Constructors; - Assert.AreEqual(1, baseCtors.Count); + Assert.AreEqual(2, baseCtors.Count); Assert.NotNull(derivedModel); var derivedCtors = derivedModel!.Constructors; - Assert.AreEqual(1, derivedCtors.Count); + Assert.AreEqual(2, derivedCtors.Count); + + var baseCtor = baseCtors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); + var derivedCtor = derivedCtors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Public)); + Assert.NotNull(baseCtor); + Assert.NotNull(derivedCtor); - var baseCtor = baseCtors[0]; - var derivedCtor = derivedCtors[0]; - var baseParameters = baseCtor.Signature.Parameters; - var derivedParameters = derivedCtor.Signature.Parameters; + var baseParameters = baseCtor!.Signature.Parameters; + var derivedParameters = derivedCtor!.Signature.Parameters; Assert.AreEqual(1, baseParameters.Count); Assert.AreEqual("prop1", baseParameters[0].Name); Assert.AreEqual(new CSharpType(typeof(string)), baseParameters[0].Type); @@ -180,6 +188,53 @@ public void BuildConstructor_ValidateConstructorsInDerivedModel() Assert.AreEqual(new CSharpType(typeof(string)), derivedParameters[0].Type); Assert.AreEqual("prop3", derivedParameters[1].Name); Assert.AreEqual(new CSharpType(typeof(string)), derivedParameters[1].Type); + + // validate the secondary constructor + var secondaryCtor = baseCtors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal)); + var derivedSecondaryCtor = derivedCtors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal)); + Assert.NotNull(secondaryCtor); + Assert.NotNull(derivedSecondaryCtor); + + var secondaryCtorParameters = secondaryCtor!.Signature.Parameters; + var derivedSecondaryCtorParams = derivedSecondaryCtor!.Signature.Parameters; + + // validate secondary constructor + Assert.AreEqual(3, secondaryCtorParameters.Count); // 2 properties + 1 additionalRawData + Assert.AreEqual("prop1", secondaryCtorParameters[0].Name); + Assert.AreEqual(new CSharpType(typeof(string)), secondaryCtorParameters[0].Type); + Assert.AreEqual("prop2", secondaryCtorParameters[1].Name); + Assert.AreEqual(new CSharpType(typeof(string), true), secondaryCtorParameters[1].Type); + Assert.AreEqual("serializedAdditionalRawData", secondaryCtorParameters[2].Name); + Assert.AreEqual(new CSharpType(typeof(IDictionary)), secondaryCtorParameters[2].Type); + // validate derived secondary constructor + Assert.AreEqual(5, derivedSecondaryCtorParams.Count); // all base props + 2 properties + 1 additionalRawData + Assert.AreEqual("prop1", derivedSecondaryCtorParams[0].Name); + Assert.AreEqual(new CSharpType(typeof(string)), derivedSecondaryCtorParams[0].Type); + Assert.AreEqual("prop2", derivedSecondaryCtorParams[1].Name); + Assert.AreEqual(new CSharpType(typeof(string), true), derivedSecondaryCtorParams[1].Type); + Assert.AreEqual("serializedAdditionalRawData", derivedSecondaryCtorParams[2].Name); + Assert.AreEqual(new CSharpType(typeof(IDictionary)), derivedSecondaryCtorParams[2].Type); + Assert.AreEqual("prop3", derivedSecondaryCtorParams[3].Name); + Assert.AreEqual(new CSharpType(typeof(string)), derivedSecondaryCtorParams[3].Type); + Assert.AreEqual("prop4", derivedSecondaryCtorParams[4].Name); + Assert.AreEqual(new CSharpType(typeof(string), true), derivedSecondaryCtorParams[4].Type); + } + + [Test] + public void TestBuildSecondaryConstructor() + { + var inputModel = new InputModelType("TestModel", "TestModel", "public", null, "Test model.", InputModelTypeUsage.Input | InputModelTypeUsage.Output, [], null, Array.Empty(), null, null, new Dictionary(), null, false); + var modelTypeProvider = new ModelProvider(inputModel); + var secondaryConstructor = modelTypeProvider.Constructors.FirstOrDefault(c => c.Signature.Modifiers.HasFlag(MethodSignatureModifiers.Internal)); + + Assert.IsNotNull(secondaryConstructor); + var constructorSignature = secondaryConstructor?.Signature; + Assert.IsNotNull(constructorSignature); + Assert.AreEqual(1, constructorSignature?.Parameters.Count); + + var param = constructorSignature?.Parameters[0]; + Assert.IsNotNull(param); + Assert.AreEqual("serializedAdditionalRawData", param?.Name); } [Test] @@ -225,5 +280,21 @@ public void BuildModelAsStruct() var modelTypeProvider = new ModelProvider(inputModel); Assert.AreEqual(TypeSignatureModifiers.Public | TypeSignatureModifiers.Struct | TypeSignatureModifiers.Partial | TypeSignatureModifiers.ReadOnly, modelTypeProvider.DeclarationModifiers); } + + [Test] + public void TestBuildFields() + { + var inputModel = new InputModelType("TestModel", "TestModel", "public", null, "Test model.", InputModelTypeUsage.Input | InputModelTypeUsage.Output, [], null, Array.Empty(), null, null, new Dictionary(), null, false); + var modelTypeProvider = new ModelProvider(inputModel); + var fields = modelTypeProvider.Fields; + + // Assert + Assert.IsNotNull(fields); + Assert.AreEqual(1, fields.Count); + Assert.AreEqual("_serializedAdditionalRawData", fields[0].Name); + + var type = fields[0].Type; + Assert.IsTrue(type.IsCollection); + } } } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModel.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModel.cs index 135ea17a8e7..d73f1b3e763 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModel.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModel.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.Collections.Generic; using Sample; namespace Sample.Models @@ -10,6 +11,9 @@ namespace Sample.Models /// Test model. public partial class TestModel { + /// Keeps track of any properties unknown to the library. + private global::System.Collections.Generic.IDictionary _serializedAdditionalRawData; + /// Initializes a new instance of . /// Required string, illustrating a reference type property. /// Required int, illustrating a value type property. @@ -22,6 +26,13 @@ public TestModel(string requiredString, int requiredInt) RequiredInt = requiredInt; } + internal TestModel(string requiredString, int requiredInt, global::System.Collections.Generic.IDictionary serializedAdditionalRawData) + { + RequiredString = requiredString; + RequiredInt = requiredInt; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// Required string, illustrating a reference type property. public string RequiredString { get; set; } diff --git a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModelAsStruct.cs b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModelAsStruct.cs index cefc259e49e..5947efd6e6e 100644 --- a/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModelAsStruct.cs +++ b/packages/http-client-csharp/generator/Microsoft.Generator.CSharp/test/Writers/TestData/TypeProviderWriterTests/TypeProviderWriter_WriteModelAsStruct.cs @@ -3,6 +3,7 @@ #nullable disable using System; +using System.Collections.Generic; using Sample; namespace Sample.Models @@ -10,6 +11,9 @@ namespace Sample.Models /// Test model. public readonly partial struct TestModel { + /// Keeps track of any properties unknown to the library. + private global::System.Collections.Generic.IDictionary _serializedAdditionalRawData; + /// Initializes a new instance of . /// Required string, illustrating a reference type property. /// Required int, illustrating a value type property. @@ -22,6 +26,13 @@ public TestModel(string requiredString, int requiredInt) RequiredInt = requiredInt; } + internal TestModel(string requiredString, int requiredInt, global::System.Collections.Generic.IDictionary serializedAdditionalRawData) + { + RequiredString = requiredString; + RequiredInt = requiredInt; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// Required string, illustrating a reference type property. public string RequiredString { get; set; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.Serialization.cs index e8f5bf92130..ae451492ff2 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.Serialization.cs @@ -14,27 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class AnonymousBodyRequest : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal AnonymousBodyRequest(string name, BinaryData requiredUnion, AnonymousBodyRequestRequiredLiteralString requiredLiteralString, AnonymousBodyRequestRequiredLiteralInt requiredLiteralInt, AnonymousBodyRequestRequiredLiteralFloat requiredLiteralFloat, bool requiredLiteralBool, AnonymousBodyRequestOptionalLiteralString? optionalLiteralString, AnonymousBodyRequestOptionalLiteralInt? optionalLiteralInt, AnonymousBodyRequestOptionalLiteralFloat? optionalLiteralFloat, bool? optionalLiteralBool, string requiredBadDescription, IList optionalNullableList, IList requiredNullableList, IDictionary serializedAdditionalRawData) - { - Name = name; - RequiredUnion = requiredUnion; - RequiredLiteralString = requiredLiteralString; - RequiredLiteralInt = requiredLiteralInt; - RequiredLiteralFloat = requiredLiteralFloat; - RequiredLiteralBool = requiredLiteralBool; - OptionalLiteralString = optionalLiteralString; - OptionalLiteralInt = optionalLiteralInt; - OptionalLiteralFloat = optionalLiteralFloat; - OptionalLiteralBool = optionalLiteralBool; - RequiredBadDescription = requiredBadDescription; - OptionalNullableList = optionalNullableList; - RequiredNullableList = requiredNullableList; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal AnonymousBodyRequest() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.cs index f27d4508cd4..f3990e0ab41 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/AnonymousBodyRequest.cs @@ -13,6 +13,9 @@ namespace UnbrandedTypeSpec.Models /// The AnonymousBodyRequest. public partial class AnonymousBodyRequest { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + internal AnonymousBodyRequest(string name, BinaryData requiredUnion, string requiredBadDescription, IEnumerable requiredNullableList) { Name = name; @@ -22,6 +25,24 @@ internal AnonymousBodyRequest(string name, BinaryData requiredUnion, string requ RequiredNullableList = requiredNullableList?.ToList(); } + internal AnonymousBodyRequest(string name, BinaryData requiredUnion, AnonymousBodyRequestRequiredLiteralString requiredLiteralString, AnonymousBodyRequestRequiredLiteralInt requiredLiteralInt, AnonymousBodyRequestRequiredLiteralFloat requiredLiteralFloat, bool requiredLiteralBool, AnonymousBodyRequestOptionalLiteralString? optionalLiteralString, AnonymousBodyRequestOptionalLiteralInt? optionalLiteralInt, AnonymousBodyRequestOptionalLiteralFloat? optionalLiteralFloat, bool? optionalLiteralBool, string requiredBadDescription, IList optionalNullableList, IList requiredNullableList, IDictionary serializedAdditionalRawData) + { + Name = name; + RequiredUnion = requiredUnion; + RequiredLiteralString = requiredLiteralString; + RequiredLiteralInt = requiredLiteralInt; + RequiredLiteralFloat = requiredLiteralFloat; + RequiredLiteralBool = requiredLiteralBool; + OptionalLiteralString = optionalLiteralString; + OptionalLiteralInt = optionalLiteralInt; + OptionalLiteralFloat = optionalLiteralFloat; + OptionalLiteralBool = optionalLiteralBool; + RequiredBadDescription = requiredBadDescription; + OptionalNullableList = optionalNullableList; + RequiredNullableList = requiredNullableList; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// name of the Thing. public string Name { get; set; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs index aaa33a52caf..52185a7693f 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.Serialization.cs @@ -14,15 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class Friend : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal Friend(string name, IDictionary serializedAdditionalRawData) - { - Name = name; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal Friend() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.cs index c0c5278ef2f..57cf2e65c65 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Friend.cs @@ -2,16 +2,28 @@ #nullable disable +using System; +using System.Collections.Generic; + namespace UnbrandedTypeSpec.Models { /// this is not a friendly model but with a friendly name. public partial class Friend { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + internal Friend(string name) { Name = name; } + internal Friend(string name, IDictionary serializedAdditionalRawData) + { + Name = name; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// name of the NotFriend. public string Name { get; set; } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.Serialization.cs index 8d3368c78d7..bee8747b3c3 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.Serialization.cs @@ -14,15 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class FriendlyModelRequest : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal FriendlyModelRequest(string name, IDictionary serializedAdditionalRawData) - { - Name = name; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal FriendlyModelRequest() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.cs index f00cb7192a5..f48ffdeca89 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/FriendlyModelRequest.cs @@ -2,16 +2,28 @@ #nullable disable +using System; +using System.Collections.Generic; + namespace UnbrandedTypeSpec.Models { /// The FriendlyModelRequest. public partial class FriendlyModelRequest { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + internal FriendlyModelRequest(string name) { Name = name; } + internal FriendlyModelRequest(string name, IDictionary serializedAdditionalRawData) + { + Name = name; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// name of the NotFriend. public string Name { get; set; } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs index 96283583f4a..f89d12b6d7c 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.Serialization.cs @@ -14,17 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class ModelWithRequiredNullableProperties : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal ModelWithRequiredNullableProperties(int? requiredNullablePrimitive, StringExtensibleEnum? requiredExtensibleEnum, StringFixedEnum? requiredFixedEnum, IDictionary serializedAdditionalRawData) - { - RequiredNullablePrimitive = requiredNullablePrimitive; - RequiredExtensibleEnum = requiredExtensibleEnum; - RequiredFixedEnum = requiredFixedEnum; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal ModelWithRequiredNullableProperties() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.cs index e84593a20e9..cc3b5f7026b 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ModelWithRequiredNullableProperties.cs @@ -2,11 +2,17 @@ #nullable disable +using System; +using System.Collections.Generic; + namespace UnbrandedTypeSpec.Models { /// A model with a few required nullable properties. public partial class ModelWithRequiredNullableProperties { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + /// Initializes a new instance of . /// required nullable primitive type. /// required nullable extensible enum type. @@ -18,6 +24,14 @@ public ModelWithRequiredNullableProperties(int? requiredNullablePrimitive, Strin RequiredFixedEnum = requiredFixedEnum; } + internal ModelWithRequiredNullableProperties(int? requiredNullablePrimitive, StringExtensibleEnum? requiredExtensibleEnum, StringFixedEnum? requiredFixedEnum, IDictionary serializedAdditionalRawData) + { + RequiredNullablePrimitive = requiredNullablePrimitive; + RequiredExtensibleEnum = requiredExtensibleEnum; + RequiredFixedEnum = requiredFixedEnum; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// required nullable primitive type. public int? RequiredNullablePrimitive { get; set; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs index 0cd9837d553..edae0338742 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.Serialization.cs @@ -14,15 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class ProjectedModel : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal ProjectedModel(string name, IDictionary serializedAdditionalRawData) - { - Name = name; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal ProjectedModel() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.cs index df2d716f954..243219cc974 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedModel.cs @@ -2,16 +2,28 @@ #nullable disable +using System; +using System.Collections.Generic; + namespace UnbrandedTypeSpec.Models { /// this is a model with a projected name. public partial class ProjectedModel { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + internal ProjectedModel(string name) { Name = name; } + internal ProjectedModel(string name, IDictionary serializedAdditionalRawData) + { + Name = name; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// name of the ModelWithProjectedName. public string Name { get; set; } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.Serialization.cs index 619c0189d22..84ca8f84e3f 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.Serialization.cs @@ -14,15 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class ProjectedNameModelRequest : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal ProjectedNameModelRequest(string name, IDictionary serializedAdditionalRawData) - { - Name = name; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal ProjectedNameModelRequest() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.cs index a27f7d042f2..6c0c9696d56 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ProjectedNameModelRequest.cs @@ -2,16 +2,28 @@ #nullable disable +using System; +using System.Collections.Generic; + namespace UnbrandedTypeSpec.Models { /// The ProjectedNameModelRequest. public partial class ProjectedNameModelRequest { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + internal ProjectedNameModelRequest(string name) { Name = name; } + internal ProjectedNameModelRequest(string name, IDictionary serializedAdditionalRawData) + { + Name = name; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// name of the ModelWithProjectedName. public string Name { get; set; } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs index bc74620661e..6cef383a25e 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.Serialization.cs @@ -14,14 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class ReturnsAnonymousModelResponse : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal ReturnsAnonymousModelResponse(IDictionary serializedAdditionalRawData) - { - _serializedAdditionalRawData = serializedAdditionalRawData; - } - void IJsonModel.Write(Utf8JsonWriter writer, ModelReaderWriterOptions options) { writer.WriteStartObject(); diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs index d83a56f3465..d6210b21e66 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/ReturnsAnonymousModelResponse.cs @@ -2,13 +2,24 @@ #nullable disable +using System; +using System.Collections.Generic; + namespace UnbrandedTypeSpec.Models { /// The ReturnsAnonymousModelResponse. public partial class ReturnsAnonymousModelResponse { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + internal ReturnsAnonymousModelResponse() { } + + internal ReturnsAnonymousModelResponse(IDictionary serializedAdditionalRawData) + { + _serializedAdditionalRawData = serializedAdditionalRawData; + } } } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs index 8cdfca05185..dae9fe23026 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.Serialization.cs @@ -14,38 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class RoundTripModel : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal RoundTripModel(string requiredString, int requiredInt, IList requiredCollection, IDictionary requiredDictionary, Thing requiredModel, IntExtensibleEnum? intExtensibleEnum, IList intExtensibleEnumCollection, FloatExtensibleEnum? floatExtensibleEnum, FloatExtensibleEnumWithIntValue? floatExtensibleEnumWithIntValue, IList floatExtensibleEnumCollection, FloatFixedEnum? floatFixedEnum, FloatFixedEnumWithIntValue? floatFixedEnumWithIntValue, IList floatFixedEnumCollection, IntFixedEnum? intFixedEnum, IList intFixedEnumCollection, StringFixedEnum? stringFixedEnum, BinaryData requiredUnknown, BinaryData optionalUnknown, IDictionary requiredRecordUnknown, IDictionary optionalRecordUnknown, IReadOnlyDictionary readOnlyRequiredRecordUnknown, IReadOnlyDictionary readOnlyOptionalRecordUnknown, ModelWithRequiredNullableProperties modelWithRequiredNullable, BinaryData requiredBytes, IDictionary serializedAdditionalRawData) - { - RequiredString = requiredString; - RequiredInt = requiredInt; - RequiredCollection = requiredCollection; - RequiredDictionary = requiredDictionary; - RequiredModel = requiredModel; - IntExtensibleEnum = intExtensibleEnum; - IntExtensibleEnumCollection = intExtensibleEnumCollection; - FloatExtensibleEnum = floatExtensibleEnum; - FloatExtensibleEnumWithIntValue = floatExtensibleEnumWithIntValue; - FloatExtensibleEnumCollection = floatExtensibleEnumCollection; - FloatFixedEnum = floatFixedEnum; - FloatFixedEnumWithIntValue = floatFixedEnumWithIntValue; - FloatFixedEnumCollection = floatFixedEnumCollection; - IntFixedEnum = intFixedEnum; - IntFixedEnumCollection = intFixedEnumCollection; - StringFixedEnum = stringFixedEnum; - RequiredUnknown = requiredUnknown; - OptionalUnknown = optionalUnknown; - RequiredRecordUnknown = requiredRecordUnknown; - OptionalRecordUnknown = optionalRecordUnknown; - ReadOnlyRequiredRecordUnknown = readOnlyRequiredRecordUnknown; - ReadOnlyOptionalRecordUnknown = readOnlyOptionalRecordUnknown; - ModelWithRequiredNullable = modelWithRequiredNullable; - RequiredBytes = requiredBytes; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal RoundTripModel() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.cs index 676eba05dfb..7bd748f8af0 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/RoundTripModel.cs @@ -13,6 +13,9 @@ namespace UnbrandedTypeSpec.Models /// this is a roundtrip model. public partial class RoundTripModel { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + /// Initializes a new instance of . /// Required string, illustrating a reference type property. /// Required int, illustrating a value type property. @@ -53,6 +56,35 @@ public RoundTripModel(string requiredString, int requiredInt, IEnumerable requiredCollection, IDictionary requiredDictionary, Thing requiredModel, IntExtensibleEnum? intExtensibleEnum, IList intExtensibleEnumCollection, FloatExtensibleEnum? floatExtensibleEnum, FloatExtensibleEnumWithIntValue? floatExtensibleEnumWithIntValue, IList floatExtensibleEnumCollection, FloatFixedEnum? floatFixedEnum, FloatFixedEnumWithIntValue? floatFixedEnumWithIntValue, IList floatFixedEnumCollection, IntFixedEnum? intFixedEnum, IList intFixedEnumCollection, StringFixedEnum? stringFixedEnum, BinaryData requiredUnknown, BinaryData optionalUnknown, IDictionary requiredRecordUnknown, IDictionary optionalRecordUnknown, IReadOnlyDictionary readOnlyRequiredRecordUnknown, IReadOnlyDictionary readOnlyOptionalRecordUnknown, ModelWithRequiredNullableProperties modelWithRequiredNullable, BinaryData requiredBytes, IDictionary serializedAdditionalRawData) + { + RequiredString = requiredString; + RequiredInt = requiredInt; + RequiredCollection = requiredCollection; + RequiredDictionary = requiredDictionary; + RequiredModel = requiredModel; + IntExtensibleEnum = intExtensibleEnum; + IntExtensibleEnumCollection = intExtensibleEnumCollection; + FloatExtensibleEnum = floatExtensibleEnum; + FloatExtensibleEnumWithIntValue = floatExtensibleEnumWithIntValue; + FloatExtensibleEnumCollection = floatExtensibleEnumCollection; + FloatFixedEnum = floatFixedEnum; + FloatFixedEnumWithIntValue = floatFixedEnumWithIntValue; + FloatFixedEnumCollection = floatFixedEnumCollection; + IntFixedEnum = intFixedEnum; + IntFixedEnumCollection = intFixedEnumCollection; + StringFixedEnum = stringFixedEnum; + RequiredUnknown = requiredUnknown; + OptionalUnknown = optionalUnknown; + RequiredRecordUnknown = requiredRecordUnknown; + OptionalRecordUnknown = optionalRecordUnknown; + ReadOnlyRequiredRecordUnknown = readOnlyRequiredRecordUnknown; + ReadOnlyOptionalRecordUnknown = readOnlyOptionalRecordUnknown; + ModelWithRequiredNullable = modelWithRequiredNullable; + RequiredBytes = requiredBytes; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// Required string, illustrating a reference type property. public string RequiredString { get; set; } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs index effd4fc052a..323d56091b3 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.Serialization.cs @@ -14,27 +14,6 @@ namespace UnbrandedTypeSpec.Models /// public partial class Thing : IJsonModel { - /// Keeps track of any properties unknown to the library. - private IDictionary _serializedAdditionalRawData; - - internal Thing(string name, BinaryData requiredUnion, ThingRequiredLiteralString requiredLiteralString, ThingRequiredLiteralInt requiredLiteralInt, ThingRequiredLiteralFloat requiredLiteralFloat, bool requiredLiteralBool, ThingOptionalLiteralString? optionalLiteralString, ThingOptionalLiteralInt? optionalLiteralInt, ThingOptionalLiteralFloat? optionalLiteralFloat, bool? optionalLiteralBool, string requiredBadDescription, IList optionalNullableList, IList requiredNullableList, IDictionary serializedAdditionalRawData) - { - Name = name; - RequiredUnion = requiredUnion; - RequiredLiteralString = requiredLiteralString; - RequiredLiteralInt = requiredLiteralInt; - RequiredLiteralFloat = requiredLiteralFloat; - RequiredLiteralBool = requiredLiteralBool; - OptionalLiteralString = optionalLiteralString; - OptionalLiteralInt = optionalLiteralInt; - OptionalLiteralFloat = optionalLiteralFloat; - OptionalLiteralBool = optionalLiteralBool; - RequiredBadDescription = requiredBadDescription; - OptionalNullableList = optionalNullableList; - RequiredNullableList = requiredNullableList; - _serializedAdditionalRawData = serializedAdditionalRawData; - } - internal Thing() { } diff --git a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.cs b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.cs index 2b045c194d3..35c8e19739d 100644 --- a/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.cs +++ b/packages/http-client-csharp/generator/TestProjects/Local/Unbranded-TypeSpec/src/Generated/Models/Thing.cs @@ -13,6 +13,9 @@ namespace UnbrandedTypeSpec.Models /// A model with a few properties of literal types. public partial class Thing { + /// Keeps track of any properties unknown to the library. + private IDictionary _serializedAdditionalRawData; + /// Initializes a new instance of . /// name of the Thing. /// required Union. @@ -32,6 +35,24 @@ public Thing(string name, BinaryData requiredUnion, string requiredBadDescriptio RequiredNullableList = requiredNullableList?.ToList(); } + internal Thing(string name, BinaryData requiredUnion, ThingRequiredLiteralString requiredLiteralString, ThingRequiredLiteralInt requiredLiteralInt, ThingRequiredLiteralFloat requiredLiteralFloat, bool requiredLiteralBool, ThingOptionalLiteralString? optionalLiteralString, ThingOptionalLiteralInt? optionalLiteralInt, ThingOptionalLiteralFloat? optionalLiteralFloat, bool? optionalLiteralBool, string requiredBadDescription, IList optionalNullableList, IList requiredNullableList, IDictionary serializedAdditionalRawData) + { + Name = name; + RequiredUnion = requiredUnion; + RequiredLiteralString = requiredLiteralString; + RequiredLiteralInt = requiredLiteralInt; + RequiredLiteralFloat = requiredLiteralFloat; + RequiredLiteralBool = requiredLiteralBool; + OptionalLiteralString = optionalLiteralString; + OptionalLiteralInt = optionalLiteralInt; + OptionalLiteralFloat = optionalLiteralFloat; + OptionalLiteralBool = optionalLiteralBool; + RequiredBadDescription = requiredBadDescription; + OptionalNullableList = optionalNullableList; + RequiredNullableList = requiredNullableList; + _serializedAdditionalRawData = serializedAdditionalRawData; + } + /// name of the Thing. public string Name { get; set; }