Skip to content

Commit d3105d3

Browse files
committed
wip
1 parent 9e63aca commit d3105d3

File tree

1 file changed

+114
-39
lines changed

1 file changed

+114
-39
lines changed

packages/next/src/server/typescript/rules/metadata.ts

+114-39
Original file line numberDiff line numberDiff line change
@@ -241,60 +241,135 @@ const metadata = {
241241
function hasCorrectType(
242242
node: tsModule.FunctionDeclaration | tsModule.VariableDeclaration
243243
): boolean {
244-
if (!node.type) {
245-
return false
246-
}
247-
248244
const ts = getTs()
249245
const typeChecker = getTypeChecker()
250246
if (!typeChecker) {
251247
return false
252248
}
253249

254-
const type = typeChecker.getTypeFromTypeNode(node.type)
255-
if (!type) {
256-
return false
257-
}
258-
259-
// Get the type name, handling aliases
260-
const symbol = type.getSymbol()
261-
if (!symbol) {
262-
return false
263-
}
264-
265250
// For generateMetadata, check if it's Promise<Metadata> for async or Metadata for sync
266251
if (ts.isFunctionDeclaration(node)) {
267-
const isAsync =
268-
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ??
269-
false
270-
271-
if (isAsync) {
272-
// For async functions, we need Promise<Metadata>
273-
const typeSymbol = type.getSymbol()
274-
if (!typeSymbol || typeSymbol.getName() !== 'Promise') {
275-
return false
276-
}
252+
return checkFunctionReturnType(node, typeChecker)
253+
} else {
254+
// For variable declarations (const/let/var)
255+
const name = node.name.getText()
277256

278-
// Get the type argument of Promise<T>
279-
if (!ts.isTypeReferenceNode(node.type)) {
257+
if (name === 'generateMetadata') {
258+
// For generateMetadata as a variable, it must be a function expression or arrow function
259+
if (
260+
!node.initializer ||
261+
(!ts.isFunctionExpression(node.initializer) &&
262+
!ts.isArrowFunction(node.initializer))
263+
) {
280264
return false
281265
}
282-
const typeArgs = typeChecker.getTypeArguments(
283-
type as tsModule.TypeReference
284-
)
285-
if (!typeArgs || typeArgs.length !== 1) {
286-
return false
266+
267+
// Check the return type of the function expression/arrow function
268+
if (node.initializer.type) {
269+
// If it has an explicit return type annotation
270+
return checkFunctionReturnType(node.initializer, typeChecker)
271+
} else {
272+
// If no explicit return type, infer it from the function
273+
const signature = typeChecker.getSignatureFromDeclaration(
274+
node.initializer
275+
)
276+
if (!signature) return false
277+
278+
const returnType = typeChecker.getReturnTypeOfSignature(signature)
279+
if (!returnType) return false
280+
281+
const isAsync =
282+
node.initializer.modifiers?.some(
283+
(m) => m.kind === ts.SyntaxKind.AsyncKeyword
284+
) ?? false
285+
286+
if (isAsync) {
287+
// For async functions, check if it's Promise<Metadata>
288+
const typeSymbol = returnType.getSymbol()
289+
if (!typeSymbol || typeSymbol.getName() !== 'Promise') return false
290+
291+
// Check if it's a reference type (like Promise<T>)
292+
if (
293+
!(returnType.flags & ts.TypeFlags.Object) ||
294+
!('typeArguments' in returnType)
295+
) {
296+
return false
297+
}
298+
299+
const typeArgs = (
300+
returnType as { typeArguments: readonly tsModule.Type[] }
301+
).typeArguments
302+
if (!typeArgs || typeArgs.length !== 1) return false
303+
304+
const promiseType = typeArgs[0]
305+
const promiseTypeSymbol = promiseType.getSymbol()
306+
return promiseTypeSymbol?.getName() === 'Metadata'
307+
} else {
308+
// For sync functions, check if it returns Metadata
309+
const returnTypeSymbol = returnType.getSymbol()
310+
return returnTypeSymbol?.getName() === 'Metadata'
311+
}
287312
}
288-
const promiseType = typeArgs[0]
289-
const promiseTypeSymbol = promiseType.getSymbol()
290-
return promiseTypeSymbol?.getName() === 'Metadata'
291313
} else {
292-
// For sync functions, we need Metadata directly
293-
return symbol.getName() === 'Metadata'
314+
// For metadata export, we just need Metadata type
315+
if (!node.type) return false
316+
const type = typeChecker.getTypeFromTypeNode(node.type)
317+
if (!type) return false
318+
const symbol = type.getSymbol()
319+
return symbol?.getName() === 'Metadata'
320+
}
321+
}
322+
}
323+
324+
function checkFunctionReturnType(
325+
node:
326+
| tsModule.FunctionDeclaration
327+
| tsModule.FunctionExpression
328+
| tsModule.ArrowFunction,
329+
typeChecker: tsModule.TypeChecker
330+
): boolean {
331+
const ts = getTs()
332+
333+
if (!node.type) return false
334+
335+
const returnType = typeChecker.getTypeFromTypeNode(node.type)
336+
if (!returnType) return false
337+
338+
const isAsync =
339+
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false
340+
341+
if (isAsync) {
342+
// For async functions, we need Promise<Metadata>
343+
const typeSymbol = returnType.getSymbol()
344+
if (!typeSymbol || typeSymbol.getName() !== 'Promise') {
345+
return false
346+
}
347+
348+
// Get the type argument of Promise<T>
349+
if (!ts.isTypeReferenceNode(node.type)) {
350+
return false
351+
}
352+
353+
// Check if it's a reference type (like Promise<T>)
354+
if (
355+
!(returnType.flags & ts.TypeFlags.Object) ||
356+
!('typeArguments' in returnType)
357+
) {
358+
return false
359+
}
360+
361+
const typeArgs = (returnType as { typeArguments: readonly tsModule.Type[] })
362+
.typeArguments
363+
if (!typeArgs || typeArgs.length !== 1) {
364+
return false
294365
}
366+
const promiseType = typeArgs[0]
367+
const promiseTypeSymbol = promiseType.getSymbol()
368+
return promiseTypeSymbol?.getName() === 'Metadata'
295369
} else {
296-
// For metadata export, we just need Metadata
297-
return symbol.getName() === 'Metadata'
370+
// For sync functions, we need Metadata directly
371+
const symbol = returnType.getSymbol()
372+
return symbol?.getName() === 'Metadata'
298373
}
299374
}
300375

0 commit comments

Comments
 (0)