@@ -16,6 +16,8 @@ export type ClassFieldsAnalysis = {
1616 instanceFields : Map < string , ClassFieldAnalysis > ;
1717 /** Access to static fields (`C.foo`, where `C` is the class), indexed by their names. */
1818 staticFields : Map < string , ClassFieldAnalysis > ;
19+ /** Appearances of `this` as in `this.foo.bind(this)` */
20+ bindThisSites : BindThisSite [ ] ;
1921} ;
2022
2123/**
@@ -105,6 +107,24 @@ export type FieldInit = {
105107 methodPath : NodePath < ClassMethod | ClassPrivateMethod > ;
106108} ;
107109
110+ /**
111+ * Appearance of `this` as in `this.foo.bind(this)`
112+ */
113+ export type BindThisSite = {
114+ /**
115+ * true if the bind call has more arguments e.g. `this.foo.bind(this, 42)`
116+ */
117+ bindsMore : boolean ;
118+ /** `this` as in the argument to `Function.prototype.bind` */
119+ thisArgPath : NodePath < ThisExpression > ;
120+ /** The whole bind expression */
121+ binderPath : NodePath < CallExpression > ;
122+ /** The member expression that `this` is being bound to e.g. `this.foo` */
123+ bindeePath : NodePath < MemberExpression > ;
124+ /** true if this is part of self-binding: `this.foo = this.foo.bind(this);` */
125+ isSelfBindingInitialization : boolean ;
126+ } ;
127+
108128/**
109129 * Collect declarations and uses of the following:
110130 *
@@ -301,13 +321,33 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
301321 }
302322
303323 // 2nd pass: look for uses within items
324+ const bindThisSites : BindThisSite [ ] = [ ] ;
304325 function traverseItem ( owner : string | undefined , path : NodePath ) {
305326 traverseThis ( path , ( thisPath ) => {
306327 // Ensure this is part of `this.foo`
307328 const thisMemberPath = thisPath . parentPath ;
308329 if ( ! thisMemberPath . isMemberExpression ( {
309330 object : thisPath . node
310331 } ) ) {
332+ // Check for bind arguments: `this.foo.bind(this)`
333+ if (
334+ thisMemberPath . isCallExpression ( )
335+ && thisMemberPath . node . arguments [ 0 ] === thisPath . node
336+ && thisMemberPath . node . callee . type === "MemberExpression"
337+ && memberRefName ( thisMemberPath . node . callee ) === "bind"
338+ && thisMemberPath . node . callee . object . type === "MemberExpression"
339+ && thisMemberPath . node . callee . object . object . type === "ThisExpression"
340+ ) {
341+ bindThisSites . push ( {
342+ bindsMore : thisMemberPath . node . arguments . length > 1 ,
343+ thisArgPath : thisPath ,
344+ binderPath : thisMemberPath ,
345+ bindeePath : ( thisMemberPath . get ( "callee" ) as NodePath < MemberExpression > ) . get ( "object" ) as NodePath < MemberExpression > ,
346+ // Checked later
347+ isSelfBindingInitialization : false ,
348+ } ) ;
349+ return ;
350+ }
311351 throw new AnalysisError ( `Stray this` ) ;
312352 }
313353
@@ -345,6 +385,41 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
345385 traverseItem ( body . owner , body . path ) ;
346386 }
347387
388+ // Special handling for self-binding initialization (`this.foo = this.foo.bind(this)`)
389+ for ( const [ name , field ] of instanceFields ) {
390+ field . sites = field . sites . filter ( ( site ) => {
391+ if (
392+ site . type === "decl"
393+ && site . init ?. type === "init_value"
394+ ) {
395+ const valuePath = site . init . valuePath ;
396+ const bindThisSite = bindThisSites . find ( ( binder ) => binder . binderPath === valuePath )
397+ if (
398+ bindThisSite
399+ && ! bindThisSite . bindsMore
400+ && memberRefName ( bindThisSite . bindeePath . node ) === name
401+ ) {
402+ bindThisSite . isSelfBindingInitialization = true ;
403+ // Skip the self-binding initialization (lhs)
404+ return false ;
405+ }
406+ }
407+ return true ;
408+ } ) ;
409+ }
410+ for ( const [ , field ] of instanceFields ) {
411+ field . sites = field . sites . filter ( ( site ) => {
412+ if ( site . type === "expr" ) {
413+ const bindThisSite = bindThisSites . find ( ( binder ) => binder . bindeePath === site . path )
414+ if ( bindThisSite ?. isSelfBindingInitialization ) {
415+ // Skip the self-binding initialization (rhs)
416+ return false ;
417+ }
418+ }
419+ return true ;
420+ } ) ;
421+ }
422+
348423 // Post validation
349424 for ( const [ name , field ] of instanceFields ) {
350425 if ( field . sites . length === 0 ) {
@@ -373,7 +448,7 @@ export function analyzeClassFields(path: NodePath<ClassDeclaration>): ClassField
373448 }
374449 }
375450
376- return { instanceFields, staticFields } ;
451+ return { instanceFields, staticFields, bindThisSites } ;
377452}
378453
379454function traverseThis ( path : NodePath , visit : ( path : NodePath < ThisExpression > ) => void ) {
0 commit comments