diff --git a/.chronus/changes/fix-allow-missing-properties-op-example-2024-8-23-21-3-46.md b/.chronus/changes/fix-allow-missing-properties-op-example-2024-8-23-21-3-46.md new file mode 100644 index 0000000000..801a2313f2 --- /dev/null +++ b/.chronus/changes/fix-allow-missing-properties-op-example-2024-8-23-21-3-46.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/compiler" +--- + +`@opExample` allow partial example to support visibility scenarios diff --git a/packages/compiler/src/core/type-relation-checker.ts b/packages/compiler/src/core/type-relation-checker.ts index 3e21da2023..90d33cbaf3 100644 --- a/packages/compiler/src/core/type-relation-checker.ts +++ b/packages/compiler/src/core/type-relation-checker.ts @@ -968,6 +968,9 @@ function wrapUnassignableErrors( errors: readonly TypeRelationError[], ): readonly TypeRelationError[] { const error = createUnassignableDiagnostic(source, target, source); + if (errors.length === 1) { + error.code = errors[0].code; + } error.children = errors; return [error]; } @@ -983,6 +986,9 @@ function wrapUnassignablePropertyErrors( }, skipIfFirst: true, }); + if (errors.length === 1) { + error.code = errors[0].code; + } error.children = errors; return [error]; } diff --git a/packages/compiler/src/lib/decorators.ts b/packages/compiler/src/lib/decorators.ts index 589d9076df..abc0653f3b 100644 --- a/packages/compiler/src/lib/decorators.ts +++ b/packages/compiler/src/lib/decorators.ts @@ -1510,7 +1510,7 @@ export const $opExample: OpExampleDecorator = ( context: DecoratorContext, target: Operation, _example: unknown, - options?: unknown, // TODO: change `options?: ExampleOptions` when tspd supports it + options?: ExampleOptions, ) => { const decorator = target.decorators.find( (d) => d.decorator === $opExample && d.node === context.decoratorTarget, @@ -1567,7 +1567,13 @@ function checkExampleValid( diagnosticTarget, ); if (!assignable) { - program.reportDiagnostics(diagnostics); + // We exclude missing-property diagnostic because some properties might not be present in certain protocol due to visibility. + const filtered = diagnostics.filter((x) => x.code !== "missing-property"); + if (filtered.length === 0) { + return true; + } else { + program.reportDiagnostics(filtered); + } } return assignable; } diff --git a/packages/compiler/test/decorators/examples.test.ts b/packages/compiler/test/decorators/examples.test.ts index dd2c0daa3d..0c23a25835 100644 --- a/packages/compiler/test/decorators/examples.test.ts +++ b/packages/compiler/test/decorators/examples.test.ts @@ -1,7 +1,7 @@ import { ok } from "assert"; import { describe, expect, it } from "vitest"; import { Operation, getExamples, getOpExamples, serializeValueAsJson } from "../../src/index.js"; -import { expectDiagnostics } from "../../src/testing/expect.js"; +import { expectDiagnosticEmpty, expectDiagnostics } from "../../src/testing/expect.js"; import { createTestRunner } from "../../src/testing/test-host.js"; async function getExamplesFor(code: string) { @@ -247,6 +247,25 @@ describe("@opExample", () => { code: "unassignable", }); }); + + it("allow missing properties", async () => { + const diagnostics = await diagnoseCode(` + model Pet { @visibility("create") password: string; name: string; } + @opExample( #{ returnType: #{ name: "Fluffy" } } ) + op read(): Pet; + `); + expectDiagnosticEmpty(diagnostics); + }); + + it("allow missing properties (nested)", async () => { + const diagnostics = await diagnoseCode(` + model Pet { nested: { @visibility("create") password: string; name: string; } } + const pet = #{ name: "Fluffy" }; + @opExample(#{ returnType: #{ nested: pet } }) + op read(): Pet; + `); + expectDiagnosticEmpty(diagnostics); + }); }); describe("json serialization of examples", () => {