import { unsupportedComponentDomRefDiagnostic, unsupportedRefAttributeDiagnostic, } from "./diagnostics.js"; import type { ExpressionFactsIr } from "./expression-facts.js"; import { RENDERABLE_PRIMITIVE_FACTS } from "./oxc-expression-facts.js"; import type { ComponentPropIr, JsxNodeIr } from "./ir.js"; import type { OxcBodyStatementJsxMode } from "./oxc-analysis-types.js"; import { normalizeOxcExpressionCode, stripOxcGeneratedImports } from "./oxc-code-utils.js"; import { getOxcLocation, readArray, readObject, readSource, unwrapOxcParentheses, } from "./oxc-node-utils.js"; import { containsOxcJsxSyntax } from "./oxc-render-values.js"; import { transformJsxWithOxc } from "./oxc-transform.js"; import { readOxcJsxTagName } from "./oxc-jsx-attributes.js"; import type { Diagnostic } from "./types.js"; export type AnalyzeOxcJsxNodeCallback = ( node: Record, bodyStatementJsx?: OxcBodyStatementJsxMode, shadowNames?: readonly string[], ) => JsxNodeIr; export function analyzeOxcComponentProp( code: string, attr: unknown, analyzeJsxNode: AnalyzeOxcJsxNodeCallback, diagnostics: Pick[] = [], options: { allowRef?: boolean; analyzeExpression?: (expression: Record) => JsxNodeIr[]; analyzeRenderValueExpression?: ( expression: Record, ) => JsxNodeIr[] | undefined; resolveExpressionCode?: (expression: Record) => string; resolveServerRenderValueExpressionCode?: ( expression: Record, ) => { code: string; placeholder: string } | undefined; analyzeFacts?: (expression: Record) => ExpressionFactsIr | undefined; } = {}, ): ComponentPropIr[] { const object = readObject(attr); if (object.type === "JSXSpreadAttribute") { const expression = unwrapOxcParentheses(readObject(object.argument)); const serverRenderValue = options.resolveServerRenderValueExpressionCode?.(expression); return [ { kind: "spread-prop", code: serverRenderValue?.code ?? options.resolveExpressionCode?.(expression) ?? readSource(code, expression), ...(serverRenderValue === undefined ? {} : { serverRenderValuePlaceholder: serverRenderValue.placeholder }), }, ]; } if (object.type !== "JSXAttribute") { return []; } const name = readOxcJsxTagName(readObject(object.name)); const value = readObject(object.value); if (name === "ref" && options.allowRef !== true) { diagnostics.push(unsupportedRefAttributeDiagnostic(getOxcLocation(code, object.name))); } if (name === "domRef") { diagnostics.push(unsupportedComponentDomRefDiagnostic(getOxcLocation(code, object.name))); return []; } if (value.type === "Literal") { // A literal JSX attribute value is always a string, so a callee can render // it as text no matter which call site it came from. return [ { kind: "prop", name, code: JSON.stringify(value.value), facts: RENDERABLE_PRIMITIVE_FACTS }, ]; } if (value.type === "JSXExpressionContainer") { const expression = unwrapOxcParentheses(readObject(value.expression)); const analyzedRenderValue = options.analyzeRenderValueExpression?.(expression); if (analyzedRenderValue !== undefined) { return [{ kind: "render-prop", name, children: analyzedRenderValue }]; } if (expression.type === "JSXElement" || expression.type === "JSXFragment") { return [ { kind: "render-prop", name, children: [analyzeJsxNode(expression)], }, ]; } const serverRenderValue = options.resolveServerRenderValueExpressionCode?.(expression); if (serverRenderValue !== undefined) { return [ { kind: "prop", name, code: serverRenderValue.code, serverRenderValuePlaceholder: serverRenderValue.placeholder, }, ]; } const facts = options.analyzeFacts?.(expression); return [ { kind: "prop", name, ...(facts === undefined ? {} : { facts }), code: options.resolveExpressionCode?.(expression) ?? (expression.type === "ArrowFunctionExpression" && containsOxcJsxSyntax(expression) ? normalizeOxcExpressionCode( stripOxcGeneratedImports(transformJsxWithOxc(readSource(code, expression))), ) : readSource(code, expression)), }, ]; } return [{ kind: "prop", name, code: "true" }]; } export function readOxcConsumerRenderProp( code: string, children: readonly unknown[], analyzeJsxNode: AnalyzeOxcJsxNodeCallback, bodyStatementJsx?: OxcBodyStatementJsxMode, ): ComponentPropIr | undefined { for (const child of children) { const object = readObject(child); if (object.type !== "JSXExpressionContainer") { continue; } const expression = unwrapOxcParentheses(readObject(object.expression)); if (expression.type !== "ArrowFunctionExpression") { continue; } const renderer = analyzeOxcArrowJsxRenderer( code, expression, analyzeJsxNode, bodyStatementJsx, ); return { kind: "render-prop", name: "children", valueName: renderer.valueName, children: renderer.children, }; } return undefined; } export function analyzeOxcSingleArrowJsxChild( code: string, children: readonly unknown[], analyzeJsxNode: AnalyzeOxcJsxNodeCallback, bodyStatementJsx?: OxcBodyStatementJsxMode, ): { valueName: string; children: JsxNodeIr[]; } { for (const child of children) { const object = readObject(child); if (object.type !== "JSXExpressionContainer") { continue; } const expression = unwrapOxcParentheses(readObject(object.expression)); if (expression.type === "ArrowFunctionExpression") { return analyzeOxcArrowJsxRenderer(code, expression, analyzeJsxNode, bodyStatementJsx); } } return { valueName: "_value", children: [], }; } export function analyzeOxcArrowJsxRenderer( code: string, arrow: Record, analyzeJsxNode: AnalyzeOxcJsxNodeCallback, bodyStatementJsx?: OxcBodyStatementJsxMode, ): { valueName: string; children: JsxNodeIr[]; } { const firstParameter = readObject(readArray(arrow.params)[0]); const valueName = typeof firstParameter.name === "string" ? firstParameter.name : "_value"; const body = unwrapOxcParentheses(readObject(arrow.body)); if (body.type === "JSXElement" || body.type === "JSXFragment") { return { valueName, children: [analyzeJsxNode(body, bodyStatementJsx, [valueName])], }; } return { valueName, children: [{ kind: "expr", code: readSource(code, body) }], }; }