Skip to content

Commit

Permalink
Allow recasting legacy visibility through explicitly calling $visibil…
Browse files Browse the repository at this point in the history
…ity from another decorator
  • Loading branch information
willmtemple committed Nov 7, 2024
1 parent 6f00136 commit e85bda6
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 28 deletions.
33 changes: 7 additions & 26 deletions packages/compiler/src/core/visibility/core.ts
Original file line number Diff line number Diff line change
Expand Up @@ -207,23 +207,7 @@ function groupModifiersByVisibilityClass(modifiers: EnumMember[]): Map<Enum, Set
const [getLegacyVisibility, setLegacyVisibilityModifiers, getLegacyVisibilityStateMap] =
useStateMap<ModelProperty, string[]>("legacyVisibility");

// const [getLegacyVisibilityIsExplicit, _setLegacyVisibilityIsExplicit] = useStateSet<ModelProperty>(
// "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.
Expand All @@ -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);
}

/**
Expand Down
7 changes: 7 additions & 0 deletions packages/compiler/src/lib/visibility.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import {
clearLegacyVisibility,
clearVisibilityModifiersForClass,
GeneratedVisibilityFilter,
getLegacyVisibility,
getVisibility,
isVisible,
removeVisibilityModifiers,
Expand Down Expand Up @@ -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);
}
};
Expand Down
23 changes: 21 additions & 2 deletions packages/compiler/test/decorators/visibility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<T>(a: Set<T>, b: Set<T>): void {
Expand Down Expand Up @@ -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"]);
});
});
});
47 changes: 47 additions & 0 deletions packages/compiler/test/visibility.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import {
$visibility,
addVisibilityModifiers,
clearVisibilityModifiersForClass,
DecoratorContext,
Enum,
getLifecycleVisibilityEnum,
getVisibilityForClass,
Expand Down Expand Up @@ -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", () => {
Expand Down

0 comments on commit e85bda6

Please sign in to comment.