compiler/packages/babel-plugin-react-compiler/src/HIR/FindContextIdentifiers.ts TYPESCRIPT 230 lines View on github.com → Search inside
1/**2 * Copyright (c) Meta Platforms, Inc. and affiliates.3 *4 * This source code is licensed under the MIT license found in the5 * LICENSE file in the root directory of this source tree.6 */78import type {NodePath} from '@babel/traverse';9import type * as t from '@babel/types';10import {CompilerError} from '../CompilerError';11import {getOrInsertDefault} from '../Utils/utils';12import {GeneratedSource} from './HIR';1314type IdentifierInfo = {15  reassigned: boolean;16  reassignedByInnerFn: boolean;17  referencedByInnerFn: boolean;18};19const DEFAULT_IDENTIFIER_INFO: IdentifierInfo = {20  reassigned: false,21  reassignedByInnerFn: false,22  referencedByInnerFn: false,23};2425type BabelFunction =26  | NodePath<t.FunctionDeclaration>27  | NodePath<t.FunctionExpression>28  | NodePath<t.ArrowFunctionExpression>29  | NodePath<t.ObjectMethod>;30type FindContextIdentifierState = {31  currentFn: Array<BabelFunction>;32  identifiers: Map<t.Identifier, IdentifierInfo>;33};3435const withFunctionScope = {36  enter: function (37    path: BabelFunction,38    state: FindContextIdentifierState,39  ): void {40    state.currentFn.push(path);41  },42  exit: function (_: BabelFunction, state: FindContextIdentifierState): void {43    state.currentFn.pop();44  },45};4647export function findContextIdentifiers(48  func: NodePath<t.Function>,49): Set<t.Identifier> {50  const state: FindContextIdentifierState = {51    currentFn: [],52    identifiers: new Map(),53  };5455  func.traverse<FindContextIdentifierState>(56    {57      FunctionDeclaration: withFunctionScope,58      FunctionExpression: withFunctionScope,59      ArrowFunctionExpression: withFunctionScope,60      ObjectMethod: withFunctionScope,61      AssignmentExpression(62        path: NodePath<t.AssignmentExpression>,63        state: FindContextIdentifierState,64      ): void {65        const left = path.get('left');66        if (left.isLVal()) {67          const currentFn = state.currentFn.at(-1) ?? null;68          handleAssignment(currentFn, state.identifiers, left);69        } else {70          /**71           * OptionalMemberExpressions as the left side of an AssignmentExpression are Stage 1 and72           * not supported by React Compiler yet.73           */74          CompilerError.throwTodo({75            reason: `Unsupported syntax on the left side of an AssignmentExpression`,76            description: `Expected an LVal, got: ${left.type}`,77            loc: left.node.loc ?? null,78          });79        }80      },81      UpdateExpression(82        path: NodePath<t.UpdateExpression>,83        state: FindContextIdentifierState,84      ): void {85        const argument = path.get('argument');86        const currentFn = state.currentFn.at(-1) ?? null;87        if (argument.isLVal()) {88          handleAssignment(currentFn, state.identifiers, argument);89        }90      },91      Identifier(92        path: NodePath<t.Identifier>,93        state: FindContextIdentifierState,94      ): void {95        const currentFn = state.currentFn.at(-1) ?? null;96        if (path.isReferencedIdentifier()) {97          handleIdentifier(currentFn, state.identifiers, path);98        }99      },100    },101    state,102  );103104  const result = new Set<t.Identifier>();105  for (const [id, info] of state.identifiers.entries()) {106    if (info.reassignedByInnerFn) {107      result.add(id);108    } else if (info.reassigned && info.referencedByInnerFn) {109      result.add(id);110    }111  }112  return result;113}114115function handleIdentifier(116  currentFn: BabelFunction | null,117  identifiers: Map<t.Identifier, IdentifierInfo>,118  path: NodePath<t.Identifier>,119): void {120  const name = path.node.name;121  const binding = path.scope.getBinding(name);122  if (binding == null) {123    return;124  }125  const identifier = getOrInsertDefault(identifiers, binding.identifier, {126    ...DEFAULT_IDENTIFIER_INFO,127  });128129  if (currentFn != null) {130    const bindingAboveLambdaScope = currentFn.scope.parent.getBinding(name);131132    if (binding === bindingAboveLambdaScope) {133      identifier.referencedByInnerFn = true;134    }135  }136}137138function handleAssignment(139  currentFn: BabelFunction | null,140  identifiers: Map<t.Identifier, IdentifierInfo>,141  lvalPath: NodePath<t.LVal>,142): void {143  /*144   * Find all reassignments to identifiers declared outside of currentFn145   * This closely follows destructuring assignment assumptions and logic in BuildHIR146   */147  const lvalNode = lvalPath.node;148  switch (lvalNode.type) {149    case 'Identifier': {150      const path = lvalPath as NodePath<t.Identifier>;151      const name = path.node.name;152      const binding = path.scope.getBinding(name);153      if (binding == null) {154        break;155      }156      const state = getOrInsertDefault(identifiers, binding.identifier, {157        ...DEFAULT_IDENTIFIER_INFO,158      });159      state.reassigned = true;160161      if (currentFn != null) {162        const bindingAboveLambdaScope = currentFn.scope.parent.getBinding(name);163164        if (binding === bindingAboveLambdaScope) {165          state.reassignedByInnerFn = true;166        }167      }168      break;169    }170    case 'ArrayPattern': {171      const path = lvalPath as NodePath<t.ArrayPattern>;172      for (const element of path.get('elements')) {173        if (nonNull(element)) {174          handleAssignment(currentFn, identifiers, element);175        }176      }177      break;178    }179    case 'ObjectPattern': {180      const path = lvalPath as NodePath<t.ObjectPattern>;181      for (const property of path.get('properties')) {182        if (property.isObjectProperty()) {183          const valuePath = property.get('value');184          CompilerError.invariant(valuePath.isLVal(), {185            reason: `[FindContextIdentifiers] Expected object property value to be an LVal, got: ${valuePath.type}`,186            loc: valuePath.node.loc ?? GeneratedSource,187          });188          handleAssignment(currentFn, identifiers, valuePath);189        } else {190          CompilerError.invariant(property.isRestElement(), {191            reason: `[FindContextIdentifiers] Invalid assumptions for babel types.`,192            loc: property.node.loc ?? GeneratedSource,193          });194          handleAssignment(currentFn, identifiers, property);195        }196      }197      break;198    }199    case 'AssignmentPattern': {200      const path = lvalPath as NodePath<t.AssignmentPattern>;201      const left = path.get('left');202      handleAssignment(currentFn, identifiers, left);203      break;204    }205    case 'RestElement': {206      const path = lvalPath as NodePath<t.RestElement>;207      handleAssignment(currentFn, identifiers, path.get('argument'));208      break;209    }210    case 'MemberExpression': {211      // Interior mutability (not a reassign)212      break;213    }214    default: {215      CompilerError.throwTodo({216        reason: `[FindContextIdentifiers] Cannot handle Object destructuring assignment target ${lvalNode.type}`,217        description: null,218        loc: lvalNode.loc ?? GeneratedSource,219        suggestions: null,220      });221    }222  }223}224225function nonNull<T extends NonNullable<t.Node>>(226  t: NodePath<T | null>,227): t is NodePath<T> {228  return t.node != null;229}

Findings

✓ No findings reported for this file.

Get this view in your editor

Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.