Skip to content

Commit 5ff1d65

Browse files
feat: improved type handling
1 parent 333605c commit 5ff1d65

File tree

2 files changed

+247
-333
lines changed

2 files changed

+247
-333
lines changed

src/ssr-plugin-utils.ts

Lines changed: 44 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ const resolver = new ResolverFactory({
2424
extensions: [".tsx", ".ts", ".jsx", ".js"],
2525
});
2626

27-
// Type guards for better type safety
2827
export function isFunction(node: Node): node is OxcFunction {
2928
const functionTypes: FunctionType[] = [
3029
"FunctionDeclaration",
@@ -73,29 +72,28 @@ export function traverseChildren(
7372
return true;
7473
}
7574
}
76-
} else if (child && typeof child === "object") {
77-
if (callback(child as Node)) return true;
75+
} else if (child && typeof child === "object" && callback(child as Node)) {
76+
return true;
7877
}
7978
}
8079
return false;
8180
}
8281

83-
export function hasRenderSSRCallInAST(ast: unknown, code: string): boolean {
82+
export function hasRenderSSRCallInAST(ast: Node, code: string): boolean {
8483
const renderSSRIdentifiers = new Set<string>(["renderSSR"]);
8584
let hasRenderSSRCallInCode = false;
8685

8786
function walkForDetection(node: Node): boolean {
8887
if (!node || typeof node !== "object") return false;
8988

90-
// Track renderSSR imports and aliases
91-
if (isImportDeclaration(node)) {
92-
if (!node.source?.value || !node.specifiers) return false;
93-
89+
if (isImportDeclaration(node) && node.source?.value && node.specifiers) {
9490
for (const spec of node.specifiers) {
9591
if (spec.type === "ImportSpecifier") {
9692
const importSpec = spec as ImportSpecifier;
97-
if (importSpec.imported.type !== "Identifier") continue;
98-
if (importSpec.imported.name === "renderSSR") {
93+
if (
94+
importSpec.imported.type === "Identifier" &&
95+
importSpec.imported.name === "renderSSR"
96+
) {
9997
renderSSRIdentifiers.add(importSpec.local.name);
10098
}
10199
} else if (spec.type === "ImportDefaultSpecifier") {
@@ -107,47 +105,36 @@ export function hasRenderSSRCallInAST(ast: unknown, code: string): boolean {
107105
}
108106
}
109107

110-
// Track declared renderSSR functions (including TypeScript declares)
111-
if (isFunction(node)) {
112-
if (node.id?.name === "renderSSR") {
113-
renderSSRIdentifiers.add("renderSSR");
114-
}
108+
if (isFunction(node) && node.id?.name === "renderSSR") {
109+
renderSSRIdentifiers.add("renderSSR");
115110
}
116111

117-
// Track variable aliases
118112
if (isVariableDeclarator(node)) {
119-
if (node.id.type !== "Identifier") return false;
120-
if (node.init?.type !== "Identifier") return false;
121-
if (!renderSSRIdentifiers.has(node.init.name)) return false;
122-
123-
const bindingId = node.id as BindingIdentifier;
124-
renderSSRIdentifiers.add(bindingId.name);
113+
if (
114+
node.id.type === "Identifier" &&
115+
node.init?.type === "Identifier" &&
116+
renderSSRIdentifiers.has(node.init.name)
117+
) {
118+
const bindingId = node.id as BindingIdentifier;
119+
renderSSRIdentifiers.add(bindingId.name);
120+
}
125121
}
126122

127-
// Check for renderSSR calls
128-
if (isCallExpression(node)) {
129-
if (node.callee.type === "Identifier") {
130-
if (renderSSRIdentifiers.has(node.callee.name)) {
131-
hasRenderSSRCallInCode = true;
132-
return true;
133-
}
134-
}
123+
if (
124+
isCallExpression(node) &&
125+
node.callee.type === "Identifier" &&
126+
renderSSRIdentifiers.has(node.callee.name)
127+
) {
128+
hasRenderSSRCallInCode = true;
129+
return true;
135130
}
136131

137-
// Recursively check children
138132
return traverseChildren(node, walkForDetection);
139133
}
140134

141-
walkForDetection(ast as Node);
135+
walkForDetection(ast);
142136

143-
// If we have renderSSR calls, transform the code
144-
// This handles both cases:
145-
// 1. Explicit imports/declares with calls
146-
// 2. Direct renderSSR calls (common in tests)
147-
const hasCallsInString = code.includes("renderSSR(");
148-
const result = hasRenderSSRCallInCode || hasCallsInString;
149-
150-
return result;
137+
return hasRenderSSRCallInCode || code.includes("renderSSR(");
151138
}
152139

153140
export function extractPropsFromJSX(
@@ -160,20 +147,18 @@ export function extractPropsFromJSX(
160147
if (attr.type !== "JSXAttribute") continue;
161148

162149
const jsxAttr = attr as JSXAttribute;
163-
if (jsxAttr.name.type !== "JSXIdentifier") continue;
150+
if (jsxAttr.name.type !== "JSXIdentifier" || !jsxAttr.value) continue;
164151

165152
const propName = jsxAttr.name.name;
166-
if (!jsxAttr.value) continue;
167-
168-
if (isJSXExpressionContainer(jsxAttr.value)) {
169-
// Extract the raw source code of the expression
170-
if (jsxAttr.value.expression.type !== "JSXEmptyExpression") {
171-
const exprSpan = jsxAttr.value.expression as Node & Span;
172-
const expressionCode = sourceCode.slice(exprSpan.start, exprSpan.end);
173-
props[propName] = expressionCode;
174-
}
153+
154+
if (
155+
isJSXExpressionContainer(jsxAttr.value) &&
156+
jsxAttr.value.expression.type !== "JSXEmptyExpression"
157+
) {
158+
const exprSpan = jsxAttr.value.expression as Node & Span;
159+
const expressionCode = sourceCode.slice(exprSpan.start, exprSpan.end);
160+
props[propName] = expressionCode;
175161
} else if (jsxAttr.value.type === "Literal") {
176-
// For string literals, use the actual value
177162
const literal = jsxAttr.value as { value: unknown };
178163
props[propName] = JSON.stringify(literal.value);
179164
}
@@ -191,19 +176,16 @@ function fallbackResolveComponentPath(
191176
testFileId: string,
192177
): string {
193178
if (!importPath.startsWith(".")) {
194-
// Absolute import, add extension if needed
195179
return importPath.endsWith(".tsx") || importPath.endsWith(".ts")
196180
? importPath
197181
: `${importPath}.tsx`;
198182
}
199183

200-
// Relative import - resolve relative to test file
201184
const testFileDir = dirname(testFileId);
202185
const resolvedPath = resolve(testFileDir, importPath);
203186
const projectRoot = process.cwd();
204187
let componentPath = `./${relative(projectRoot, resolvedPath)}`;
205188

206-
// Add extension if needed
207189
if (!componentPath.endsWith(".tsx") && !componentPath.endsWith(".ts")) {
208190
componentPath += ".tsx";
209191
}
@@ -219,12 +201,9 @@ export function resolveComponentPath(
219201
const result = resolver.sync(testFileDir, importPath);
220202

221203
if (result.error || !result.path) {
222-
const errorMsg = result.error || "No path resolved";
223-
224204
console.warn(
225-
`[oxc-resolver] Could not resolve "${importPath}" from "${testFileId}": ${errorMsg}. Using fallback resolution. If this is not a test file, this might be a bug.`,
205+
`[oxc-resolver] Could not resolve "${importPath}" from "${testFileId}": ${result.error || "No path resolved"}. Using fallback resolution.`,
226206
);
227-
228207
return fallbackResolveComponentPath(importPath, testFileId);
229208
}
230209

@@ -235,10 +214,13 @@ export function resolveComponentPath(
235214
}
236215

237216
export function hasCommandsImport(node: Node): boolean {
238-
if (!isImportDeclaration(node)) return false;
239-
240-
if (node.source?.value !== "@vitest/browser/context") return false;
241-
if (!node.specifiers) return false;
217+
if (
218+
!isImportDeclaration(node) ||
219+
node.source?.value !== "@vitest/browser/context" ||
220+
!node.specifiers
221+
) {
222+
return false;
223+
}
242224

243225
return node.specifiers.some(
244226
(spec) =>

0 commit comments

Comments
 (0)