Skip to content

Commit ba9c946

Browse files
committed
Handle multiple navigators
1 parent 9dee681 commit ba9c946

File tree

1 file changed

+123
-93
lines changed

1 file changed

+123
-93
lines changed

src/plugins/rehype-static-to-dynamic.mjs

Lines changed: 123 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,7 @@ function convertStaticToDynamic(code) {
5353
parser: require('recast/parsers/babel-ts'),
5454
});
5555

56-
let navigatorInfo = null;
57-
let navigatorDeclarationIndex = -1;
56+
let navigatorInfos = [];
5857
let staticNavigationIndices = [];
5958

6059
// First pass: collect information and transform imports
@@ -125,17 +124,16 @@ function convertStaticToDynamic(code) {
125124
const navigatorType = declarator.init.callee.name; // e.g., "createStackNavigator"
126125
const config = declarator.init.arguments[0];
127126

128-
navigatorInfo = {
127+
navigatorInfos.push({
129128
componentName: navigatorVariable, // Keep original name for the component
130129
type: navigatorType,
131130
config: config,
132131
// Store leading/trailing comments to preserve codeblock-focus
133132
leadingComments: node.comments || [],
134133
trailingComments: node.trailingComments || [],
135134
originalNode: node, // Keep reference to original node
136-
};
137-
138-
navigatorDeclarationIndex = index;
135+
index: index,
136+
});
139137
}
140138

141139
// Find createStaticNavigation usage
@@ -159,11 +157,14 @@ function convertStaticToDynamic(code) {
159157
t.isJSXIdentifier(path.node.openingElement.name) &&
160158
path.node.openingElement.name.name === 'Navigation' &&
161159
path.node.children.length === 0 &&
162-
navigatorInfo
160+
navigatorInfos.length > 0
163161
) {
164162
// Preserve any props passed to Navigation
165163
const navigationProps = path.node.openingElement.attributes || [];
166164

165+
// Use the last navigator (which is passed to createStaticNavigation)
166+
const mainNavigator = navigatorInfos[navigatorInfos.length - 1];
167+
167168
// Replace with <NavigationContainer><MyStack /></NavigationContainer>
168169
// Pass the props from Navigation to NavigationContainer
169170
const newElement = t.jsxElement(
@@ -176,7 +177,7 @@ function convertStaticToDynamic(code) {
176177
t.jsxText('\n '),
177178
t.jsxElement(
178179
t.jsxOpeningElement(
179-
t.jsxIdentifier(navigatorInfo.componentName),
180+
t.jsxIdentifier(mainNavigator.componentName),
180181
[],
181182
true
182183
),
@@ -197,108 +198,137 @@ function convertStaticToDynamic(code) {
197198
});
198199

199200
// Second pass: manually transform the AST body
200-
if (navigatorInfo && navigatorDeclarationIndex !== -1) {
201-
const {
202-
componentName,
203-
type,
204-
config,
205-
leadingComments,
206-
trailingComments,
207-
originalNode,
208-
} = navigatorInfo;
209-
210-
// Extract navigator constant name from the type
211-
// Get the last word before "Navigator"
212-
// e.g., "createStackNavigator" -> "Stack"
213-
// e.g., "createNativeStackNavigator" -> "Stack"
214-
// e.g., "createBottomTabNavigator" -> "Tab"
215-
// e.g., "createMaterialTopTabNavigator" -> "Tab"
216-
const withoutCreate = type.replace(/^create/, ''); // "StackNavigator"
217-
const withoutNavigator = withoutCreate.replace(/Navigator$/, ''); // "Stack"
218-
// Find the last capitalized word (e.g., "NativeStack" -> "Stack", "MaterialTopTab" -> "Tab")
219-
const match = withoutNavigator.match(/([A-Z][a-z]+)$/);
220-
const navigatorConstName = match ? match[1] : withoutNavigator;
221-
222-
// Parse the config object
223-
const parsedConfig = parseNavigatorConfig(config);
224-
225-
// Create: const Stack = createStackNavigator();
226-
const navigatorConstDeclaration = t.variableDeclaration('const', [
227-
t.variableDeclarator(
228-
t.identifier(navigatorConstName),
229-
t.callExpression(t.identifier(type), [])
230-
),
231-
]);
201+
// Process all navigators
202+
if (navigatorInfos.length > 0) {
203+
const replacements = [];
204+
205+
navigatorInfos.forEach((navigatorInfo) => {
206+
const {
207+
componentName,
208+
type,
209+
config,
210+
leadingComments,
211+
trailingComments,
212+
originalNode,
213+
index,
214+
} = navigatorInfo;
215+
216+
// Extract navigator constant name from the type
217+
// Get the last word before "Navigator"
218+
// e.g., "createStackNavigator" -> "Stack"
219+
// e.g., "createNativeStackNavigator" -> "Stack"
220+
// e.g., "createBottomTabNavigator" -> "Tab"
221+
// e.g., "createMaterialTopTabNavigator" -> "Tab"
222+
const withoutCreate = type.replace(/^create/, ''); // "StackNavigator"
223+
const withoutNavigator = withoutCreate.replace(/Navigator$/, ''); // "Stack"
224+
// Find the last capitalized word (e.g., "NativeStack" -> "Stack", "MaterialTopTab" -> "Tab")
225+
const match = withoutNavigator.match(/([A-Z][a-z]+)$/);
226+
const navigatorConstName = match ? match[1] : withoutNavigator;
227+
228+
// Parse the config object
229+
const parsedConfig = parseNavigatorConfig(config);
230+
231+
// Create: const Stack = createStackNavigator();
232+
const navigatorConstDeclaration = t.variableDeclaration('const', [
233+
t.variableDeclarator(
234+
t.identifier(navigatorConstName),
235+
t.callExpression(t.identifier(type), [])
236+
),
237+
]);
232238

233-
// Create the navigator component function (e.g., function MyStack() {...})
234-
const navigatorComponent = createNavigatorComponent(
235-
componentName, // function name: MyStack
236-
navigatorConstName, // Stack.Navigator, Stack.Screen
237-
parsedConfig
238-
);
239+
// Create the navigator component function (e.g., function MyStack() {...})
240+
const navigatorComponent = createNavigatorComponent(
241+
componentName, // function name: MyStack
242+
navigatorConstName, // Stack.Navigator, Stack.Screen
243+
parsedConfig
244+
);
239245

240-
// Preserve all comments from the original node
241-
if (originalNode.comments && originalNode.comments.length > 0) {
242-
// Separate leading and trailing comments
243-
const leadingComments = [];
244-
const trailingCommentsFromNode = [];
245-
246-
originalNode.comments.forEach((comment) => {
247-
// Recast marks comments with leading/trailing properties
248-
if (comment.trailing) {
249-
trailingCommentsFromNode.push(comment);
250-
} else {
251-
leadingComments.push(comment);
246+
// Preserve all comments from the original node
247+
if (originalNode.comments && originalNode.comments.length > 0) {
248+
// Separate leading and trailing comments
249+
const leadingComments = [];
250+
const trailingCommentsFromNode = [];
251+
252+
originalNode.comments.forEach((comment) => {
253+
// Recast marks comments with leading/trailing properties
254+
if (comment.trailing) {
255+
trailingCommentsFromNode.push(comment);
256+
} else {
257+
leadingComments.push(comment);
258+
}
259+
});
260+
261+
// Attach leading comments to the const declaration
262+
if (leadingComments.length > 0) {
263+
// Mark as leading comments for proper placement
264+
leadingComments.forEach((c) => {
265+
c.leading = true;
266+
c.trailing = false;
267+
});
268+
navigatorConstDeclaration.comments = leadingComments;
252269
}
253-
});
254270

255-
// Attach leading comments to the const declaration
256-
if (leadingComments.length > 0) {
257-
// Mark as leading comments for proper placement
258-
leadingComments.forEach((c) => {
259-
c.leading = true;
260-
c.trailing = false;
261-
});
262-
navigatorConstDeclaration.comments = leadingComments;
271+
// Attach trailing comments to the function component (after the function body)
272+
if (trailingCommentsFromNode.length > 0) {
273+
// Mark as trailing comments for proper placement
274+
trailingCommentsFromNode.forEach((c) => {
275+
c.leading = false;
276+
c.trailing = true;
277+
});
278+
navigatorComponent.comments = trailingCommentsFromNode;
279+
}
263280
}
264281

265-
// Attach trailing comments to the function component (after the function body)
266-
if (trailingCommentsFromNode.length > 0) {
267-
// Mark as trailing comments for proper placement
268-
trailingCommentsFromNode.forEach((c) => {
282+
// Also check for trailingComments property
283+
if (trailingComments && trailingComments.length > 0) {
284+
trailingComments.forEach((c) => {
269285
c.leading = false;
270286
c.trailing = true;
271287
});
272-
navigatorComponent.comments = trailingCommentsFromNode;
288+
navigatorComponent.comments = [
289+
...(navigatorComponent.comments || []),
290+
...trailingComments,
291+
];
273292
}
274-
}
275293

276-
// Also check for trailingComments property
277-
if (trailingComments && trailingComments.length > 0) {
278-
trailingComments.forEach((c) => {
279-
c.leading = false;
280-
c.trailing = true;
294+
// Store the replacement info
295+
replacements.push({
296+
index: index,
297+
navigatorConstDeclaration,
298+
navigatorComponent,
281299
});
282-
navigatorComponent.comments = [
283-
...(navigatorComponent.comments || []),
284-
...trailingComments,
285-
];
286-
}
300+
});
301+
302+
// Replace declarations in reverse order to maintain correct indices
303+
replacements.sort((a, b) => b.index - a.index);
287304

288-
// Replace the declaration by manipulating the body array directly
289305
const programBody = ast.program.body;
290-
programBody.splice(
291-
navigatorDeclarationIndex,
292-
1,
293-
navigatorConstDeclaration,
294-
navigatorComponent
306+
let indexShift = 0;
307+
308+
replacements.forEach(
309+
({ index, navigatorConstDeclaration, navigatorComponent }) => {
310+
// Replace 1 node with 2 nodes
311+
programBody.splice(
312+
index,
313+
1,
314+
navigatorConstDeclaration,
315+
navigatorComponent
316+
);
317+
318+
// Track the shift for adjusting staticNavigation indices
319+
indexShift++;
320+
}
295321
);
296322

297323
// Adjust indices for createStaticNavigation declarations
298-
// Since we replaced 1 node with 2 nodes, indices after this point shift by +1
299-
staticNavigationIndices = staticNavigationIndices.map((idx) =>
300-
idx > navigatorDeclarationIndex ? idx + 1 : idx
301-
);
324+
// Account for the fact that we replaced each navigator (1 node) with 2 nodes
325+
staticNavigationIndices = staticNavigationIndices.map((idx) => {
326+
let shift = 0;
327+
replacements.forEach(({ index }) => {
328+
if (idx > index) shift++;
329+
});
330+
return idx + shift;
331+
});
302332
}
303333

304334
// Remove createStaticNavigation declarations (in reverse order to maintain indices)

0 commit comments

Comments
 (0)