Repository navigation
@pureOrBreakMyCode
@pureOrBreakMyCode is a JSDoc annotation placed on a call expression that
tells the Closure Compiler:
Evaluating this expression has no observable side effects and does not depend on mutable global state. You may delete it if its result is unused, and you may run it later than written. If that is not true, my code breaks and that is my fault.
The compiler performs no verification of this claim.
Who should use this? It exists for tools that generate JavaScript and can guarantee the contract by construction (the Angular compiler, Soy's JS backend, and the jspb protobuf code generator all emit it). Using it in hand-written code is discouraged: a mistake produces compiled output that is subtly wrong rather than a compile error. If you want the compiler to treat a function as side-effect free, see
@nosideeffectsinstead.
Write the annotation immediately before the call, and wrap the call in parentheses:
const value = /** @pureOrBreakMyCode */ (compute(arg));
Klass.prop = /** @pureOrBreakMyCode */ (defineThing({...}));
class K { static field = /** @pureOrBreakMyCode */ (init()); }The parentheses are not strictly required (/** @pureOrBreakMyCode */ compute(arg)
also works), but they make the annotated extent unambiguous and avoid the
pitfalls listed below.
The JSDoc comment must end up attached to the CALL node for unused-code
removal to see it. In particular:
| Form | Unused-code removal | Cross-chunk motion |
|---|---|---|
x = /** @pureOrBreakMyCode */ (f()); |
✅ | ✅ |
x = /** @pureOrBreakMyCode */ f(); |
✅ | ✅ |
x = /** @pureOrBreakMyCode */ ns.a.f(); |
✅ | ✅ |
/** @pureOrBreakMyCode */ const x = f(); (on the declaration) |
❌ | ✅ |
x = /** @pureOrBreakMyCode @type {T} */ (f()); (combined with a cast) |
❌ | ✅ |
x = /** @pureOrBreakMyCode */ (/** @type {T} */ (f())); (wrapping a cast) |
❌ | ✅ |
x = /** @type {T} */ (/** @pureOrBreakMyCode */ (f())); (inside the cast) |
✅ | ✅ |
x = /** @pureOrBreakMyCode */ (f)(1); (parenthesized callee) |
❌ | ✅ |
x = /** @pureOrBreakMyCode */ (new F()); |
❌ | ✅ |
x = /** @pureOrBreakMyCode */ (f?.()); |
❌ | ✅ |
x = /** @pureOrBreakMyCode */ (c ? f() : 0);, (f() + 1), (tag`x`)
|
❌ | ✅ |
Rule of thumb: for unused-code removal the annotation must be the innermost
JSDoc on a plain (non-new, non-optional) call; cross-chunk motion is more
lenient and accepts the annotation on any value or on its enclosing
declaration.
Normally, when the compiler removes an unused variable or property whose initializer is a function call, it must keep the call for its possible side effects:
const unused = compute(); // ⟶ compute();With the annotation the whole initializer disappears:
const unused = /** @pureOrBreakMyCode */ (compute()); // ⟶ (nothing)This applies to global and local variables, static properties
(C.prop = …), prototype properties (C.prototype.p = …), instance
properties assigned in constructors (this.p = …), and class static fields.
Global removals require ADVANCED_OPTIMIZATIONS; unused local variables are
also removed in SIMPLE_OPTIMIZATIONS.
The arguments are deleted too. The annotation asserts purity of the entire call expression:
const unused = /** @pureOrBreakMyCode */ (wrap(logAndReturn(1)));
// ⟶ nothing is kept, including logAndReturn(1)The annotation has no effect on a call whose result is used, nor on a bare
expression statement such as /** @pureOrBreakMyCode */ (f()); — in both
cases the call stays.
When compiling with chunks (--chunk), the compiler tries to move top-level
definitions out of early chunks and into the chunk where they are first used.
It only moves values it knows are safe to evaluate later: literals, functions,
classes, and literals composed of those. A function call pins the definition
in place — and, for classes, pins the whole class and everything attached to
it:
// chunk "core"
class Widget {}
Widget.metadata = defineMetadata({...}); // pins Widget in "core"With the annotation the compiler treats the call as movable, so Widget and
its static property can migrate to the chunk that actually uses Widget:
class Widget {}
Widget.metadata = /** @pureOrBreakMyCode */ (defineMetadata({...}));This is the original motivation for the annotation (Angular Ivy components define their metadata through such top-level calls). Note the stronger contract here: the call may be executed later than written, after arbitrary other code has run, or never, if nothing in the program ends up needing the value.
The annotation does not:
- make the compiler evaluate the call at compile time or constant-fold through it;
- affect the compiler's side-effect analysis of the callee (other calls to the same function are unaffected);
- influence inlining, property collapsing, or type checking.
@pureOrBreakMyCode |
@nosideeffects |
/* @__PURE__ */ |
|
|---|---|---|---|
| Attached to | the call expression | the function definition (externs or source) | the call expression |
| Verified by the compiler | no | no | n/a |
| Meaning | this particular evaluation is pure and position-independent | every call of this function is free of side effects (it may still read global state) | Terser / esbuild / Rollup equivalent; ignored by Closure Compiler |
Tools targeting both Closure Compiler and other minifiers may emit both:
/** @pureOrBreakMyCode */ /* @__PURE__ */ defineInjectable({...}).
Can I put @pureOrBreakMyCode on a function declaration to mark it pure?
No. It is only read at the call site; on a function definition it is ignored.
To declare that a function (in externs or in source) has no side effects, use
@nosideeffects on the function instead.
Can it replace goog.reflect.cache? Only at each call site. Annotating
the body of a caching helper does nothing; instead annotate the expression
whose result you want the compiler to be free to drop:
const v = /** @pureOrBreakMyCode */ (expensiveButPure());.
Why doesn't the compiler check the claim? Proving that an arbitrary call is pure and independent of global state is infeasible in general; the annotation exists precisely to let a code generator supply knowledge the compiler cannot derive. That is also why misuse is dangerous and why the name is what it is.