{"version":3,"file":"transforms.mjs","names":[],"sources":["../../../../../src/init/frameworkSetup/tanstackStart/transforms.ts"],"sourcesContent":["import * as recast from 'recast';\nimport { babelTsParser } from '../../../utils/babelParser';\nimport {\n  ensureNamedImport,\n  firstInsertIndex,\n  isModuleScopeBinding,\n} from '../../utils/astImports';\n\nconst { builders: b, namedTypes: n } = recast.types;\n\n/** Module-scope identifier bound to the locale route's typed API. */\nconst LOCALE_ROUTE_API_VARIABLE = 'localeRoute';\n\n/** babel-ts parser handles TypeScript *and* JSX. */\nconst parseTsx = (code: string): any =>\n  recast.parse(code, { parser: babelTsParser });\n\n/** Result of a source transform. `code` is unchanged for any non-`wrapped` status. */\nexport type TransformResult = {\n  code: string;\n  status: 'wrapped' | 'already' | 'skipped';\n};\n\n/**\n * Finds the single function that returns the `<html>` document element. Returns\n * `null` when there is no such function, or more than one (ambiguous).\n */\nconst findHtmlDocumentFunction = (ast: any): any => {\n  const found = new Set<any>();\n\n  recast.visit(ast, {\n    visitJSXOpeningElement(path) {\n      const node = path.node;\n      if (node.name?.type === 'JSXIdentifier' && node.name.name === 'html') {\n        // Walk up to the nearest enclosing function node.\n        let current = path.parentPath;\n        while (current) {\n          const value = current.value;\n          if (\n            value &&\n            (n.FunctionDeclaration.check(value) ||\n              n.FunctionExpression.check(value) ||\n              n.ArrowFunctionExpression.check(value))\n          ) {\n            found.add(value);\n            break;\n          }\n          current = current.parentPath;\n        }\n      }\n      this.traverse(path);\n    },\n  });\n\n  return found.size === 1 ? [...found][0] : null;\n};\n\n/**\n * Whether the binding pattern `node` introduces a variable named `name`.\n * Recurses through destructuring patterns so all of `const locale`,\n * `const { locale } = …`, `const { locale = fallback } = …`,\n * `const { foo: locale } = …`, `const [locale] = …` and `...locale` are\n * detected — not just a bare identifier.\n */\nconst patternBindsName = (node: any, name: string): boolean => {\n  if (!node) return false;\n  switch (node.type) {\n    case 'Identifier':\n      return node.name === name;\n    case 'AssignmentPattern':\n      // `locale = fallback`\n      return patternBindsName(node.left, name);\n    case 'RestElement':\n      // `...locale`\n      return patternBindsName(node.argument, name);\n    case 'ArrayPattern':\n      return node.elements.some((element: any) =>\n        patternBindsName(element, name)\n      );\n    case 'ObjectPattern':\n      return node.properties.some((property: any) =>\n        property.type === 'RestElement'\n          ? patternBindsName(property.argument, name)\n          : // The *value* is the binding target (`{ foo: locale }` binds `locale`).\n            patternBindsName(property.value, name)\n      );\n    default:\n      return false;\n  }\n};\n\n/**\n * Inserts `const { locale = defaultLocale } = localeRoute.useParams();` once, at\n * the top of the function body. The destructuring form binds only `locale`, so\n * it can never collide with a `params` variable the document already declares.\n * Expression-bodied arrow functions are first converted to a block so the\n * declaration has somewhere to live.\n *\n * No-ops when `locale` is already bound in the function — whether as a simple\n * identifier, through a destructuring pattern (e.g.\n * `const { locale = defaultLocale } = Route.useLoaderData()`), or as a function\n * parameter — so the injected declaration never collides with one the document\n * already provides (which would produce an\n * `Identifier 'locale' has already been declared` parse error).\n *\n * @returns `true` when the declaration was injected, `false` when the document\n * already binds `locale` itself. Callers use this to skip the imports and the\n * route API declaration the injected statement would have needed.\n */\nconst ensureLocaleFromParams = (funcNode: any): boolean => {\n  if (funcNode.body?.type !== 'BlockStatement') {\n    funcNode.body = b.blockStatement([b.returnStatement(funcNode.body)]);\n  }\n\n  const declaredInBody = funcNode.body.body.some(\n    (stmt: any) =>\n      stmt.type === 'VariableDeclaration' &&\n      stmt.declarations.some((d: any) => patternBindsName(d.id, 'locale'))\n  );\n  const declaredInParams = (funcNode.params ?? []).some((param: any) =>\n    patternBindsName(param, 'locale')\n  );\n  if (declaredInBody || declaredInParams) return false;\n\n  const statement = parseTsx(\n    `const { locale = defaultLocale } = ${LOCALE_ROUTE_API_VARIABLE}.useParams();`\n  ).program.body[0];\n  funcNode.body.body.unshift(statement);\n  return true;\n};\n\n/**\n * Ensures the module-scope `const localeRoute = getRouteApi(\"/<segment>\");`\n * declaration the injected locale statement reads from, inserted right after the\n * import block.\n *\n * Referencing the locale route by id keeps the root document free of an import\n * of `<segment>/route` — importing a child route module from `__root` is\n * discouraged, since the generated route tree already imports the root.\n *\n * No-ops when `localeRoute` is already bound at module scope, reusing that\n * binding rather than redeclaring it (which would be a parse error).\n *\n * @returns `true` when the declaration was inserted, `false` when an existing\n * binding was reused.\n */\nconst ensureLocaleRouteApi = (ast: any, localeSegment: string): boolean => {\n  if (isModuleScopeBinding(ast, LOCALE_ROUTE_API_VARIABLE)) return false;\n\n  const declaration = parseTsx(\n    `const ${LOCALE_ROUTE_API_VARIABLE} = getRouteApi(\"/${localeSegment}\");`\n  ).program.body[0];\n  ast.program.body.splice(firstInsertIndex(ast), 0, declaration);\n  return true;\n};\n\n/**\n * Puts a blank line between the import block and the inserted route API\n * declaration — recast prints a spliced statement flush against the preceding\n * line, which reads as part of the imports.\n */\nconst separateRouteApiDeclaration = (printedCode: string): string =>\n  printedCode.replace(\n    new RegExp(\n      `([^\\\\n])\\\\n(const ${LOCALE_ROUTE_API_VARIABLE} = getRouteApi\\\\()`\n    ),\n    '$1\\n\\n$2'\n  );\n\n/** Sets `lang={locale}` and `dir={getHTMLTextDir(locale)}` on the first `<html>` element. */\nconst setHtmlLangAndDir = (ast: any): void => {\n  recast.visit(ast, {\n    visitJSXOpeningElement(path) {\n      const node = path.node;\n      if (node.name?.type !== 'JSXIdentifier' || node.name.name !== 'html') {\n        this.traverse(path);\n        return;\n      }\n\n      const upsertAttribute = (name: string, value: any): void => {\n        const existing = node.attributes?.find(\n          (attr: any) =>\n            attr.type === 'JSXAttribute' && attr.name?.name === name\n        ) as any;\n        if (existing) {\n          existing.value = value;\n        } else {\n          node.attributes?.push(b.jsxAttribute(b.jsxIdentifier(name), value));\n        }\n      };\n\n      upsertAttribute('lang', b.jsxExpressionContainer(b.identifier('locale')));\n      upsertAttribute(\n        'dir',\n        b.jsxExpressionContainer(\n          b.callExpression(b.identifier('getHTMLTextDir'), [\n            b.identifier('locale'),\n          ])\n        )\n      );\n      return false;\n    },\n  });\n};\n\n/**\n * Wraps the `{children}` of a TanStack Start root document with\n * `IntlayerProvider`, deriving the locale from the locale segment route params.\n * Safe and idempotent: bails (returns the original code) when the provider is\n * already present, when no `<html>` document function is found, or when there is\n * no `{children}` placeholder to wrap.\n *\n * @param code Source of the project's `__root` document.\n * @param localeSegment Locale segment directory in use — `{-$locale}` for every\n * routing mode but `prefix-all`, which uses the required `$locale`.\n */\nexport const wrapRootWithProvider = (\n  code: string,\n  localeSegment: string\n): TransformResult => {\n  const ast = parseTsx(code);\n\n  if (code.includes('IntlayerProvider')) return { code, status: 'already' };\n\n  const funcNode = findHtmlDocumentFunction(ast);\n  if (!funcNode) return { code, status: 'skipped' };\n\n  let wrapped = false;\n  recast.visit(funcNode, {\n    visitJSXExpressionContainer(path) {\n      if (wrapped) return false;\n      const expression = path.node.expression;\n      if (expression?.type === 'Identifier' && expression.name === 'children') {\n        const template = parseTsx(\n          'const __wrap = <IntlayerProvider locale={locale}>{__child__}</IntlayerProvider>;'\n        );\n        const providerElement = template.program.body[0].declarations[0].init;\n        providerElement.children = [path.node];\n        path.replace(providerElement);\n        wrapped = true;\n        return false;\n      }\n      this.traverse(path);\n    },\n  });\n\n  if (!wrapped) return { code, status: 'skipped' };\n\n  const injectedLocaleDeclaration = ensureLocaleFromParams(funcNode);\n\n  ensureNamedImport(ast, 'IntlayerProvider', 'react-intlayer');\n  // Only needed by the injected declaration: a document that already derives its\n  // own `locale` must not be given unused imports or a stray route API const.\n  let insertedRouteApi = false;\n  if (injectedLocaleDeclaration) {\n    ensureNamedImport(ast, 'getRouteApi', '@tanstack/react-router');\n    ensureNamedImport(ast, 'defaultLocale', 'intlayer');\n    insertedRouteApi = ensureLocaleRouteApi(ast, localeSegment);\n  }\n  ensureNamedImport(ast, 'getHTMLTextDir', 'intlayer');\n  setHtmlLangAndDir(ast);\n\n  const printedCode = recast.print(ast).code;\n\n  return {\n    code: insertedRouteApi\n      ? separateRouteApiDeclaration(printedCode)\n      : printedCode,\n    status: 'wrapped',\n  };\n};\n"],"mappings":"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"}