{"record":{"id":"65a94e8095ca5a53","repo":"facebook/flow","slug":"unable-to-resolve-scope","errorCode":null,"errorMessage":"unable to resolve scope","messagePattern":"unable to resolve scope","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/flow-api-translator/src/flowDefToTSDef.js","lineNumber":320,"sourceCode":"      return globalScope.childScopes[0];\n    }\n    return globalScope;\n  })();\n\n  function isReactImport(scopeNode: FlowESTree.ESNode, name: string): boolean {\n    let currentScope = (() => {\n      let scope = null;\n      let node: FlowESTree.ESNode = scopeNode;\n      while (!scope && node) {\n        scope = scopeManager.acquire(node, true);\n        node = node.parent;\n      }\n\n      return scope;\n    })();\n\n    if (currentScope == null) {\n      throw new Error('unable to resolve scope');\n    }\n\n    const variableDef = (() => {\n      while (currentScope != null) {\n        for (const variable of currentScope.variables) {\n          if (variable.defs.length && variable.name === name) {\n            return variable;\n          }\n        }\n        currentScope = currentScope.upper;\n      }\n    })();\n\n    // No variable found, it is not imported.\n    // It could be a global though if isValidReactImportOrGlobal returns true.\n    if (variableDef == null) {\n      return false;\n    }","sourceCodeStart":302,"sourceCodeEnd":338,"githubUrl":"https://github.com/facebook/flow/blob/5c865861998a8ccb7dbc82b0c1f511e9ef60c3d9/packages/flow-api-translator/src/flowDefToTSDef.js#L302-L338","documentation":"Thrown inside flowDefToTSDef.js while checking whether a name resolves to a React import (isReactImport). It walks up from scopeNode via node.parent calling scopeManager.acquire(node, true) until a scope is found; if the whole parent chain yields no scope it throws 'unable to resolve scope'. This happens when the AST node has no parent pointers or the scopeManager was built from a different AST than the node belongs to.","triggerScenarios":"Calling internal flow-api-translator functions (flowDefToTSDef, isReactImport path) with a node whose .parent chain is broken: a detached/hand-built node, a Program parsed by flow-parser without scope analysis, or a scopeManager produced from a different parse of the code. Reached during JSX translation in translateFlowDefToTSDef/translateFlowToTSDef.","commonSituations":"Using the translator's internal modules directly instead of the public translateFlowDefToTSDef API (which parses and analyzes coherently); pre-transforming the AST with a custom pass that drops parent pointers; passing a scopeManager from a stale copy of the code.","solutions":["Use the public APIs (translateFlowDefToTSDef / translateFlowToTSDef from flow-api-translator) which build the AST and scopeManager together.","If calling internals, re-parse the code and re-run scope analysis so every node reachable from scopeNode has parent pointers and an acquirable scope.","Verify the scopeManager and AST come from the same source string before invoking translation.","Wrap the call in try/catch and surface the offending node location for triage."],"exampleFix":"// before\nconst {flowDefToTSDef} = require('flow-api-translator/src/flowDefToFlowDef.js');\nconst ast = parse(code); // no parents, no scope analysis\nflowDefToTSDef(ast, code, someOtherScopeManager, opts);\n\n// after\nimport {translateFlowDefToTSDef} from 'flow-api-translator';\nconst result = await translateFlowDefToTSDef({code, recoverFromErrors: true});","handlingStrategy":"validation","validationCode":"// Verify the node can reach a scope before translating\nfunction nodeResolvesToScope(scopeManager, node) {\n  let cur = node;\n  while (cur) {\n    if (scopeManager.acquire(cur, true) != null) return true;\n    cur = cur.parent;\n  }\n  return false;\n}","typeGuard":"function hasParentChain(node, upTo = 100) {\n  let cur = node, i = 0;\n  while (cur && i++ < upTo) cur = cur.parent;\n  return cur != null;\n}","tryCatchPattern":"try {\n  const out = await translateFlowDefToTSDef({code, recoverFromErrors: true});\n} catch (e) {\n  if (e.message === 'unable to resolve scope') {\n    throw new Error('AST/scopeManager mismatch: re-parse and re-analyze the same source before translating');\n  }\n  throw e;\n}","preventionTips":["Use public translate* APIs instead of internal modules so parse + scope analysis stay coherent.","Never mix a scopeManager from one parse with an AST from another.","Re-run scope analysis after any custom transform that rebuilds nodes, so parent links stay intact."],"tags":["flow","api-translator","scope","ast","parent-pointers"],"backgroundTag":"scope-resolution-failed","analyzedSha":"5c865861998a8ccb7dbc82b0c1f511e9ef60c3d9","analyzedAt":"2026-08-20T10:41:37.992Z","contentChangedAt":"2026-08-20T10:41:37.992Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}