From dd55e2c7d5355f5198d04964ebba9a483985e1e6 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Sat, 24 Oct 2026 03:04:27 +0800 Subject: [PATCH] fix(javascript): retain initialized class self binding at body reads --- ...Analysis.initialization_boundaries.test.ts | 5 +++ .../javascriptSemanticMutationInitializers.ts | 37 +++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/src/domain/javascript/javascriptSemanticAnalysis.initialization_boundaries.test.ts b/src/domain/javascript/javascriptSemanticAnalysis.initialization_boundaries.test.ts index f7cf8da8fc..216ef23e90 100644 --- a/src/domain/javascript/javascriptSemanticAnalysis.initialization_boundaries.test.ts +++ b/src/domain/javascript/javascriptSemanticAnalysis.initialization_boundaries.test.ts @@ -211,3 +211,8 @@ it("owns a class declaration's self reference separately from its outer variable )?.resolution, ).toBe("exact"); }); + +it("does not initialize a class self name during computed-key evaluation", () => { + const ir = analyze(`class Service { [Service()]() {} }`, "fixture.mjs"); + expect(ir.callSites[0]?.resolution).not.toBe("exact"); +}); diff --git a/src/domain/javascript/javascriptSemanticMutationInitializers.ts b/src/domain/javascript/javascriptSemanticMutationInitializers.ts index 190b055b4d..218b1cc849 100644 --- a/src/domain/javascript/javascriptSemanticMutationInitializers.ts +++ b/src/domain/javascript/javascriptSemanticMutationInitializers.ts @@ -11,6 +11,7 @@ import { semanticStaticPropertyKey, unwrapJavaScriptExpression, } from "./javascriptAstValues.js"; +import { semanticScopeId } from "./javascriptSemanticState.js"; import type { JavaScriptSemanticBindingState, JavaScriptSemanticAnalysisState, @@ -746,6 +747,42 @@ export const semanticBindingAtRead = ( t.isFunctionDeclaration(soleInitializer.node) ) return { status: "ordered", binding, hasDefiniteInitializer: true }; + if ( + soleInitializer !== undefined && + t.isClass(soleInitializer.node) && + binding.scopeId === semanticScopeId("class", soleInitializer.node) && + !binding.mutable && + binding.initializers.length === 1 && + binding.referenceInitializers.length === 0 && + !binding.definitions.some(({ kind }) => kind === "assignment") + ) { + // The class self name is initialized before fields/static blocks run and + // before a body method can be invoked, but not during heritage/key evaluation. + const owner = soleInitializer.node; + let child = read; + let memberPart: t.Node | undefined; + for ( + let parent = parents.get(child); + parent !== undefined; + child = parent, parent = parents.get(parent) + ) { + if (parents.get(parent) === owner.body) memberPart = child; + if (parent === owner.body) { + if ( + t.isClassMethod(child) || t.isClassPrivateMethod(child) + ? memberPart === child.body + : t.isStaticBlock(child) || + ((t.isClassProperty(child) || + t.isClassPrivateProperty(child) || + t.isClassAccessorProperty(child)) && + memberPart === child.value) + ) + return { status: "ordered", binding, hasDefiniteInitializer: true }; + break; + } + if (parent === owner) break; + } + } const initializer = binding.initializers[0]; const reference = binding.referenceInitializers[0]; const origin = initializer ?? reference;