What
docs/plans/issue-2088.md's subtreeContainsReassignmentOf (WU-2, condition 4's identifier-resolution chain) detects a write to a module-level name via three AST shapes: a plain/augmented assignment or update expression whose target binds the bare identifier name (as of round 14, resolved through patternBindsName for a destructuring target too — see #2630 for the one shape, a parenthesized target, it still cannot see), and a declaration-less for...in/for...of loop-head target.
None of these shapes covers a write that reaches the same binding through the global object instead of through the identifier directly: in a non-module (classic script) file, a top-level var name = …; also creates a property on globalThis (var bindings at script scope are exposed as globalThis.name; let/const are not). globalThis.name = function () { return this.alpha(); }; (or global.name = … / self.name = … / window.name = … in the appropriate host) elsewhere in the same file reassigns the SAME binding findTopLevelFunctionNodeByName resolves, without ever writing to a bare identifier node at all — it is a member_expression assignment target, a shape subtreeContainsReassignmentOf does not look at, and one patternBindsName does not apply to either (member expressions are not a binding pattern).
This is a decidable, enumerable AST shape (an assignment_expression whose left is a member_expression with object globalThis/global/self/window and property name) — unlike eval/with, which are universal static-analysis limitations no tool can see through — so it sits closer to this design's other accepted, filed recall trade-offs (#2625, #2631, and the round-14 duplicate-declaration fail-safe) than to a Category-F "standard formula/platform limit" the plan already treats MAX_WALK_DEPTH truncation as.
Why this wasn't fixed inline
Recognising a globalThis/global/self/window-qualified write as a reassignment of the SAME module-level var binding requires threading in knowledge of which of several equivalent global-object aliases is in play (all resolve to the same object in a non-strict, non-module script; only globalThis is universally available across hosts and module types) and confirming the resolved declaration is even script-scoped in the first place (a let/const binding is never exposed this way, so the check would need to be conditioned on findTopLevelFunctionNodeByName having resolved through a variable_declaration, not a lexical_declaration) — additional scope for a resolution chain this plan has already found and fixed bugs in across seven rounds, for a pattern with no observed instance anywhere in this codebase today.
Suggested fix shape (not binding — decide at execute/fix time)
Add a fourth disjunct to subtreeContainsReassignmentOf, gated on the resolved declaration having come from a variable_declaration (var) specifically: an assignment_expression whose left is a member_expression with object.text in {'globalThis', 'global', 'self', 'window'} and property.text === name. Add a WU-10 escape-fallback case proving this shape is caught once implemented.
Where
What
docs/plans/issue-2088.md'ssubtreeContainsReassignmentOf(WU-2, condition 4's identifier-resolution chain) detects a write to a module-levelnamevia three AST shapes: a plain/augmented assignment or update expression whose target binds the bare identifiername(as of round 14, resolved throughpatternBindsNamefor a destructuring target too — see #2630 for the one shape, a parenthesized target, it still cannot see), and a declaration-lessfor...in/for...ofloop-head target.None of these shapes covers a write that reaches the same binding through the global object instead of through the identifier directly: in a non-module (classic script) file, a top-level
var name = …;also creates a property onglobalThis(varbindings at script scope are exposed asglobalThis.name;let/constare not).globalThis.name = function () { return this.alpha(); };(orglobal.name = …/self.name = …/window.name = …in the appropriate host) elsewhere in the same file reassigns the SAME bindingfindTopLevelFunctionNodeByNameresolves, without ever writing to a bareidentifiernode at all — it is amember_expressionassignment target, a shapesubtreeContainsReassignmentOfdoes not look at, and onepatternBindsNamedoes not apply to either (member expressions are not a binding pattern).This is a decidable, enumerable AST shape (an
assignment_expressionwhoseleftis amember_expressionwith objectglobalThis/global/self/windowand propertyname) — unlikeeval/with, which are universal static-analysis limitations no tool can see through — so it sits closer to this design's other accepted, filed recall trade-offs (#2625, #2631, and the round-14 duplicate-declaration fail-safe) than to a Category-F "standard formula/platform limit" the plan already treatsMAX_WALK_DEPTHtruncation as.Why this wasn't fixed inline
Recognising a
globalThis/global/self/window-qualified write as a reassignment of the SAME module-levelvarbinding requires threading in knowledge of which of several equivalent global-object aliases is in play (all resolve to the same object in a non-strict, non-module script; onlyglobalThisis universally available across hosts and module types) and confirming the resolved declaration is even script-scoped in the first place (alet/constbinding is never exposed this way, so the check would need to be conditioned onfindTopLevelFunctionNodeByNamehaving resolved through avariable_declaration, not alexical_declaration) — additional scope for a resolution chain this plan has already found and fixed bugs in across seven rounds, for a pattern with no observed instance anywhere in this codebase today.Suggested fix shape (not binding — decide at execute/fix time)
Add a fourth disjunct to
subtreeContainsReassignmentOf, gated on the resolved declaration having come from avariable_declaration(var) specifically: anassignment_expressionwhoseleftis amember_expressionwithobject.textin{'globalThis', 'global', 'self', 'window'}andproperty.text === name. Add a WU-10 escape-fallback case proving this shape is caught once implemented.Where
docs/plans/issue-2088.md— WU-2'ssubtreeContainsReassignmentOf(condition 4's identifier-resolution chain) and its doc comment.