Skip to content

Commit

Permalink
Clean up validateNamespacedFbtElement.
Browse files Browse the repository at this point in the history
  • Loading branch information
cpojer committed Dec 21, 2024
1 parent 4535f85 commit f3fde5b
Show file tree
Hide file tree
Showing 2 changed files with 45 additions and 86 deletions.
56 changes: 0 additions & 56 deletions packages/babel-plugin-fbtee/src/FbtUtil.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import {
isJSXElement,
isJSXEmptyExpression,
isJSXExpressionContainer,
isJSXIdentifier,
isJSXNamespacedName,
isJSXSpreadAttribute,
isJSXText,
Expand Down Expand Up @@ -52,7 +51,6 @@ import {
} from '@babel/types';
import invariant from 'invariant';
import type { AnyFbtNode } from './fbt-nodes/FbtNode.tsx';
import { ConcreteFbtNodeType } from './fbt-nodes/FbtNodeType.tsx';
import type {
BindingName,
FbtOptionConfig,
Expand Down Expand Up @@ -89,60 +87,6 @@ export function normalizeSpaces(
return value.replaceAll(/[^\S\u00A0]+/g, ' ');
}

/**
* Validates the type of fbt construct inside <fbt>.
* Currently detected:
* <fbt:param>, <FbtParam>
* <fbt:enum>, <FbtEnum>
* <fbt:name>, <FbtName>
* etc...
* @param node The node that may be a JSX fbt construct
* @return Returns the name of a corresponding handler (fbt construct).
* If a child is not valid, it is flagged as an Implicit Parameter (`implicitParamMarker`)
*/
export function validateNamespacedFbtElement(
moduleName: string,
node: Node,
): ConcreteFbtNodeType | 'implicitParamMarker' {
let valid = false;
let handlerName;

// Actual namespaced version, e.g. <fbt:param>
if (isJSXNamespacedName(node)) {
handlerName = node.name.name;
valid =
isJSXIdentifier(node.namespace) &&
node.namespace.name === moduleName &&
(handlerName === 'enum' ||
handlerName === 'param' ||
handlerName === 'plural' ||
handlerName === 'pronoun' ||
handlerName === 'name' ||
handlerName === 'same-param');
// React's version, e.g. <FbtParam>, or <FbtEnum>
} else if (isJSXIdentifier(node)) {
handlerName = node.name.slice(3).toLowerCase();
valid =
node.name === 'FbtEnum' ||
node.name === 'FbtParam' ||
node.name === 'FbtPlural' ||
node.name === 'FbtPronoun' ||
node.name === 'FbtName' ||
node.name === 'FbtSameParam';
}

if (!valid) {
handlerName = 'implicitParamMarker';
}

if (handlerName === 'same-param' || handlerName === 'sameparam') {
handlerName = 'sameParam';
}

invariant(handlerName != null, 'handlerName must not be null');
return handlerName as ConcreteFbtNodeType;
}

function isNodeCallExpressionArg(value: Node): value is CallExpressionArg {
return (
isExpression(value) ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,23 @@ import {
isCallExpression,
isJSXElement,
isJSXFragment,
isJSXNamespacedName,
isStringLiteral,
JSXAttribute,
JSXElement,
JSXExpressionContainer,
jsxExpressionContainer,
JSXNamespacedName,
memberExpression,
ObjectExpression,
StringLiteral,
stringLiteral,
} from '@babel/types';
import invariant from 'invariant';
import {
ConcreteFbtNodeType,
isConcreteFbtNode,
} from '../fbt-nodes/FbtNodeType.tsx';
import {
getCommonDescription,
getUnknownCommonStringErrorMessage,
Expand All @@ -44,7 +50,6 @@ import {
getAttributeByNameOrThrow,
getOptionsFromAttributes,
normalizeSpaces,
validateNamespacedFbtElement,
varDump,
} from '../FbtUtil.tsx';
import getNamespacedArgs from '../getNamespacedArgs.tsx';
Expand Down Expand Up @@ -236,9 +241,37 @@ export default class JSXFbtProcessor {
_transformChildrenForFbtCallSyntax(): Array<
CallExpression | JSXElement | StringLiteral
> {
this.path.traverse(jsxFbtConstructToFunctionalFormTransform, {
moduleName: this.moduleName,
this.path.traverse({
JSXElement: (path: NodePath) => {
const { node } = path;
if (!isJSXNamespacedName(node.openingElement.name)) {
return;
}

const name = validateNamespacedFbtElement(
this.moduleName,
node.openingElement.name,
);
if (name) {
const namespace = getNamespacedArgs(this.moduleName);
const args = namespace[name as keyof typeof namespace](node);
const fbtConstructCall = callExpression(
memberExpression(
identifier(this.moduleName),
identifier(name),
false,
),
args as Array<CallExpressionArg>,
);
path.replaceWith(
isJSXElement(path.parent) || isJSXFragment(path.parent)
? jsxExpressionContainer(fbtConstructCall)
: fbtConstructCall,
);
}
},
});

return (
filterEmptyNodes(this.node.children) as ReadonlyArray<JSXElementChild>
).map((node) => {
Expand Down Expand Up @@ -358,30 +391,12 @@ export default class JSXFbtProcessor {
}
}

/**
* Traverse all JSXElements, replace those that are JSX fbt constructs (e.g. <fbt:param>)
* to their functional form equivalents (e.g. fbt.param()).
*/
const jsxFbtConstructToFunctionalFormTransform = {
JSXElement(path: NodePath) {
const { node } = path;
const { moduleName } = this as unknown as { moduleName: BindingName };
const name = validateNamespacedFbtElement(
moduleName,
node.openingElement.name,
);
if (name !== 'implicitParamMarker') {
const namespace = getNamespacedArgs(moduleName);
const args = namespace[name as keyof typeof namespace](node);
let fbtConstructCall: CallExpression | JSXExpressionContainer =
callExpression(
memberExpression(identifier(moduleName), identifier(name), false),
args as Array<CallExpressionArg>,
);
if (isJSXElement(path.parent) || isJSXFragment(path.parent)) {
fbtConstructCall = jsxExpressionContainer(fbtConstructCall);
}
path.replaceWith(fbtConstructCall);
}
},
} as const;
const validateNamespacedFbtElement = (
moduleName: string,
node: JSXNamespacedName,
): ConcreteFbtNodeType | null => {
const name = node.name.name === 'same-param' ? 'sameParam' : node.name.name;
return name && node.namespace.name === moduleName && isConcreteFbtNode(name)
? name
: null;
};

0 comments on commit f3fde5b

Please sign in to comment.