diff --git a/.changeset/diagnostic-code-prefix.md b/.changeset/diagnostic-code-prefix.md index 6fa356c3b..2f14813f7 100644 --- a/.changeset/diagnostic-code-prefix.md +++ b/.changeset/diagnostic-code-prefix.md @@ -2,7 +2,7 @@ "solid-js": patch --- -Diagnostic messages now include their stable code identifier as a prefix (e.g. `[NO_OWNER_EFFECT] Effects created outside a reactive context will never be disposed`). Applied to all dev-mode diagnostics: `STRICT_READ_UNTRACKED`, `PENDING_ASYNC_UNTRACKED_READ`, `PENDING_ASYNC_FORBIDDEN_SCOPE`, `SIGNAL_WRITE_IN_OWNED_SCOPE`, `RUN_WITH_DISPOSED_OWNER`, `NO_OWNER_CLEANUP`, `CLEANUP_IN_FORBIDDEN_SCOPE`, `NO_OWNER_EFFECT`, `NO_OWNER_BOUNDARY`, `ASYNC_OUTSIDE_LOADING_BOUNDARY`, and `MISSING_EFFECT_FN`. +Diagnostic messages now include their stable code identifier as a prefix (e.g. `[NO_OWNER_EFFECT] Effects created outside a reactive context will never be disposed`). Applied to all dev-mode diagnostics: `STRICT_READ_UNTRACKED`, `PENDING_ASYNC_UNTRACKED_READ`, `PENDING_ASYNC_FORBIDDEN_SCOPE`, `SIGNAL_WRITE_IN_OWNED_SCOPE`, `RUN_WITH_DISPOSED_OWNER`, `NO_OWNER_CLEANUP`, `CLEANUP_IN_FORBIDDEN_SCOPE`, `NO_OWNER_EFFECT`, `NO_OWNER_BOUNDARY`, and `ASYNC_OUTSIDE_LOADING_BOUNDARY`. The previously bare `throw new Error("Cannot create reactive primitives inside createTrackedEffect or owner-backed onSettled")` (raised when creating a memo, effect, or owner inside `createTrackedEffect`/`onSettled`) is now also surfaced through the diagnostic system as `PRIMITIVE_IN_FORBIDDEN_SCOPE` (severity `error`, dev-only, throws after emitting). Existing tests that match the message substring continue to work. diff --git a/.changeset/missing-effect-fn-loud-failure.md b/.changeset/missing-effect-fn-loud-failure.md index aef0b56f1..b5c76fa36 100644 --- a/.changeset/missing-effect-fn-loud-failure.md +++ b/.changeset/missing-effect-fn-loud-failure.md @@ -2,11 +2,8 @@ "solid-js": patch --- -`createEffect(compute)` (single-argument form) is now a hard error. Solid 2.0's `createEffect` requires a separate effect callback as its second argument: `createEffect(() => signal(), value => doWork(value))`. +Remove the unsupported `createEffect(compute)` overload. Solid 2.0 requires a separate effect callback as its second argument: `createEffect(() => signal(), value => doWork(value))`. -Two layers now surface the misuse: - -- **TypeScript** — a deprecated overload `createEffect(compute): never` is added so editors render the call with strikethrough and surface the migration message on hover. -- **Runtime (dev)** — calling without an effect function now throws synchronously with a clear message and emits a new `MISSING_EFFECT_FN` diagnostic (replaces the previous opaque `TypeError: Cannot read properties of undefined`). +TypeScript now rejects single-argument calls instead of accepting them through a deprecated `never` overload. The dedicated development-only `MISSING_EFFECT_FN` diagnostic has also been removed; JavaScript callers receive the same runtime failure in development and production. If you want a derived value, use `createMemo`. If you want a one-shot side effect at construction time, just call the function directly. diff --git a/packages/signals/src/core/dev.ts b/packages/signals/src/core/dev.ts index d976a3f57..e7520d056 100644 --- a/packages/signals/src/core/dev.ts +++ b/packages/signals/src/core/dev.ts @@ -34,7 +34,6 @@ export type DiagnosticCode = | "ASYNC_OUTSIDE_LOADING_BOUNDARY" | "INVALID_REFRESH_TARGET" | "INVALID_AFFECTS_TARGET" - | "MISSING_EFFECT_FN" | "SYNC_NODE_RECEIVED_ASYNC" | "REACTIVITY_HALTED" | "INVARIANT_VIOLATION" diff --git a/packages/signals/src/signals.ts b/packages/signals/src/signals.ts index 875a10916..bc0fe4b7d 100644 --- a/packages/signals/src/signals.ts +++ b/packages/signals/src/signals.ts @@ -494,37 +494,7 @@ export function createEffect( compute: ComputeFunction, T>, effectFn: EffectFunction, T> | EffectBundle, T>, options?: EffectOptions -): void; -/** - * @deprecated `createEffect(compute)` (single argument) is no longer supported. - * Pass a separate effect function as the second argument: - * `createEffect(compute, effect)`. See [MISSING_EFFECT_FN]. - * - * - For a side effect that reacts to changes, split the work: - * `createEffect(() => signal(), value => doWork(value))`. - * - For a derived value, use `createMemo(() => signal())`. - * - For a one-shot side effect at construction time, just call the function. - */ -export function createEffect(compute: ComputeFunction, T>): never; -export function createEffect( - compute: ComputeFunction, T>, - effectFn?: EffectFunction, T> | EffectBundle, T>, - options?: EffectOptions ): void { - if (__DEV__ && effectFn === undefined) { - const message = - "[MISSING_EFFECT_FN] createEffect requires both a compute function and an effect function. " + - "Use `createEffect(() => signal(), value => doWork(value))`. " + - "If you want a derived value, use `createMemo`. " + - "If you want a one-shot side effect, just call the function directly."; - emitDiagnostic({ - code: "MISSING_EFFECT_FN", - kind: "lifecycle", - severity: "error", - message - }); - throw new Error(message); - } effect(compute as any, (effectFn as any).effect || effectFn, (effectFn as any).error, { user: true, ...(__DEV__ ? { ...options, name: options?.name ?? "effect" } : options) diff --git a/packages/signals/tests/diagnostics.test.ts b/packages/signals/tests/diagnostics.test.ts index 4d8e052e7..8353a4cbf 100644 --- a/packages/signals/tests/diagnostics.test.ts +++ b/packages/signals/tests/diagnostics.test.ts @@ -187,22 +187,6 @@ describe("diagnostics", () => { expect(events[0].kind).toBe("lifecycle"); }); - it("emits a diagnostic and throws when createEffect is called without an effect function", () => { - const capture = DEV!.diagnostics.capture(); - - createRoot(() => { - expect(() => createEffect(() => 1)).toThrow( - /createEffect requires both a compute function and an effect function/ - ); - }); - - const events = capture.stop(); - expect(events).toHaveLength(1); - expect(events[0].code).toBe("MISSING_EFFECT_FN"); - expect(events[0].severity).toBe("error"); - expect(events[0].kind).toBe("lifecycle"); - }); - it("emits a diagnostic before throwing on reactive primitive creation in a forbidden scope", () => { const capture = DEV!.diagnostics.capture(); diff --git a/packages/solid/skills/reactivity-diagnostics/SKILL.md b/packages/solid/skills/reactivity-diagnostics/SKILL.md index 65dd06e73..5e37082b0 100644 --- a/packages/solid/skills/reactivity-diagnostics/SKILL.md +++ b/packages/solid/skills/reactivity-diagnostics/SKILL.md @@ -89,12 +89,6 @@ stored past its lifetime — re-capture the owner at call time or guard with ## API misuse -### MISSING_EFFECT_FN - -`createEffect(compute)` with a single argument is not supported. Split the -work: `createEffect(() => signal(), value => doWork(value))`. For a derived -value use `createMemo`; for a one-shot side effect just call the function. - ### PRIMITIVE_IN_FORBIDDEN_SCOPE Reactive primitives cannot be created inside `createTrackedEffect` or diff --git a/packages/solid/test/signals.type-tests.ts b/packages/solid/test/signals.type-tests.ts index 9d981e735..d5d03c75f 100644 --- a/packages/solid/test/signals.type-tests.ts +++ b/packages/solid/test/signals.type-tests.ts @@ -76,7 +76,8 @@ createEffect( () => {} ); -const _effectDeprecatedReturn: never = createEffect(() => 1); +// @ts-expect-error createEffect requires a separate effect function +createEffect(() => 1); const _effectCanonicalReturn: void = createEffect( () => 1, () => {}