diff --git a/packages/compiler/src/core/visibility/core.ts b/packages/compiler/src/core/visibility/core.ts index 2d62cc8bf4..796705a878 100644 --- a/packages/compiler/src/core/visibility/core.ts +++ b/packages/compiler/src/core/visibility/core.ts @@ -207,23 +207,7 @@ function groupModifiersByVisibilityClass(modifiers: EnumMember[]): Map("legacyVisibility"); -// const [getLegacyVisibilityIsExplicit, _setLegacyVisibilityIsExplicit] = useStateSet( -// "legacyVisibilityIsExplicit", -// ); - -// /** -// * Sets a flag indicating that legacy visibility modifiers should be considered "explicitly initialized." -// * -// * This is used to differentiate between legacy visibility cases where visibility is _not_ set, and legacy -// * `getVisibility` should return `undefined` and cases where visibility is set to an empty array, and legacy -// * visibility should return `[]`. -// * -// * @param context - the decorator context to use -// * @param property - the property to set the flag for -// */ -// export function setLegacyVisibilityIsExplicit(program: Program, property: ModelProperty) { -// _setLegacyVisibilityIsExplicit(program, property); -// } +export { getLegacyVisibility }; /** * Sets the legacy visibility modifiers for a property. @@ -243,26 +227,23 @@ export function setLegacyVisibility( visibilities: string[], ) { const { program } = context; - compilerAssert( - getLegacyVisibility(program, property) === undefined, - "Legacy visibility modifiers have already been set for this property.", - ); setLegacyVisibilityModifiers(program, property, visibilities); const lifecycleClass = getLifecycleVisibilityEnum(program); - if (visibilities.length === 0 || (visibilities.length === 1 && visibilities[0] === "none")) { - clearVisibilityModifiersForClass(program, property, lifecycleClass, context); - } else { + clearVisibilityModifiersForClass(program, property, lifecycleClass, context); + + const isEmpty = + visibilities.length === 0 || (visibilities.length === 1 && visibilities[0] === "none"); + + if (!isEmpty) { const lifecycleVisibilities = visibilities .map((v) => normalizeLegacyLifecycleVisibilityString(program, v)) .filter((v) => !!v); addVisibilityModifiers(program, property, lifecycleVisibilities); } - - sealVisibilityModifiers(program, property, lifecycleClass); } /** diff --git a/packages/compiler/src/lib/visibility.ts b/packages/compiler/src/lib/visibility.ts index 3fb13c2b07..b9821c6059 100644 --- a/packages/compiler/src/lib/visibility.ts +++ b/packages/compiler/src/lib/visibility.ts @@ -35,6 +35,7 @@ import { clearLegacyVisibility, clearVisibilityModifiersForClass, GeneratedVisibilityFilter, + getLegacyVisibility, getVisibility, isVisible, removeVisibilityModifiers, @@ -256,6 +257,12 @@ export const $visibility: VisibilityDecorator = ( // assertion will fail inside the legacy visibility management API. if (isUnique) setLegacyVisibility(context, target, legacyVisibilities); } else { + if (getLegacyVisibility(context.program, target)) { + reportDiagnostic(context.program, { + code: "visibility-mixed-legacy", + target: context.decoratorTarget, + }); + } addVisibilityModifiers(context.program, target, modifiers, context); } }; diff --git a/packages/compiler/test/decorators/visibility.test.ts b/packages/compiler/test/decorators/visibility.test.ts index 8f83793101..af06ede963 100644 --- a/packages/compiler/test/decorators/visibility.test.ts +++ b/packages/compiler/test/decorators/visibility.test.ts @@ -3,9 +3,9 @@ import { deepStrictEqual, ok, strictEqual } from "assert"; import { beforeEach, describe, it } from "vitest"; -import { Enum, Model, ModelProperty } from "../../src/core/types.js"; +import { DecoratorContext, Enum, Model, ModelProperty } from "../../src/core/types.js"; import { getVisibility, getVisibilityForClass } from "../../src/core/visibility/core.js"; -import { getLifecycleVisibilityEnum } from "../../src/index.js"; +import { $visibility, getLifecycleVisibilityEnum } from "../../src/index.js"; import { BasicTestRunner, createTestRunner } from "../../src/testing/index.js"; function assertSetsEqual(a: Set, b: Set): void { @@ -100,5 +100,24 @@ describe("visibility (legacy)", function () { "update", ]); }); + + it("allows overriding legacy visibility", async () => { + const { Example } = (await runner.compile(` + @test model Example { + @visibility("read") + name: string + } + `)) as { Example: Model }; + + const name = Example.properties.get("name")!; + + const decCtx = { + program: runner.program, + } as DecoratorContext; + + $visibility(decCtx, name, "create"); + + deepStrictEqual(getVisibility(runner.program, name), ["create"]); + }); }); }); diff --git a/packages/compiler/test/visibility.test.ts b/packages/compiler/test/visibility.test.ts index 031eaa8a95..837464c1fe 100644 --- a/packages/compiler/test/visibility.test.ts +++ b/packages/compiler/test/visibility.test.ts @@ -8,6 +8,7 @@ import { $visibility, addVisibilityModifiers, clearVisibilityModifiersForClass, + DecoratorContext, Enum, getLifecycleVisibilityEnum, getVisibilityForClass, @@ -668,6 +669,52 @@ describe("compiler: visibility core", () => { deepStrictEqual(legacyVisibility, []); }); + + it("correctly coerces visibility modifiers after rewriting", async () => { + const { Example } = (await runner.compile(` + @test model Example { + @visibility("create") + x: string; + } + `)) as { Example: Model }; + + const x = Example.properties.get("x")!; + + const LifecycleEnum = getLifecycleVisibilityEnum(runner.program); + + const visibility = getVisibilityForClass(runner.program, x, LifecycleEnum); + + strictEqual(visibility.size, 1); + + const Lifecycle = { + Create: LifecycleEnum.members.get("Create")!, + Read: LifecycleEnum.members.get("Read")!, + Update: LifecycleEnum.members.get("Update")!, + }; + + ok(visibility.has(Lifecycle.Create)); + ok(!visibility.has(Lifecycle.Read)); + ok(!visibility.has(Lifecycle.Update)); + + const legacyVisibility = getVisibility(runner.program, x); + + deepStrictEqual(legacyVisibility, ["create"]); + + // Now change the visibility imperatively using the legacy API + $visibility({ program: runner.program } as DecoratorContext, x, "read"); + + const updatedVisibility = getVisibilityForClass(runner.program, x, LifecycleEnum); + + strictEqual(updatedVisibility.size, 1); + + ok(!updatedVisibility.has(Lifecycle.Create)); + ok(updatedVisibility.has(Lifecycle.Read)); + ok(!updatedVisibility.has(Lifecycle.Update)); + + const updatedLegacyVisibility = getVisibility(runner.program, x); + + deepStrictEqual(updatedLegacyVisibility, ["read"]); + }); }); describe("lifecycle transforms", () => {