Skip to content

Commit da958d4

Browse files
authored
[ts-next-plugin] fix: warn only if no type (#78628)
1 parent 8385a5c commit da958d4

File tree

1 file changed

+27
-131
lines changed

1 file changed

+27
-131
lines changed

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

Lines changed: 27 additions & 131 deletions
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ const metadata = {
8282

8383
if (ts.isFunctionDeclaration(node)) {
8484
if (node.name?.getText() === 'generateMetadata') {
85-
if (hasCorrectType(node)) {
85+
if (hasType(node)) {
8686
return []
8787
}
8888

@@ -103,7 +103,7 @@ const metadata = {
103103
}
104104
} else {
105105
for (const declaration of node.declarationList.declarations) {
106-
if (hasCorrectType(declaration)) {
106+
if (hasType(declaration)) {
107107
return []
108108
}
109109

@@ -209,7 +209,7 @@ const metadata = {
209209
if (
210210
declaration &&
211211
ts.isFunctionDeclaration(declaration) &&
212-
!hasCorrectType(declaration)
212+
!hasType(declaration)
213213
) {
214214
diagnostics.push({
215215
file: source,
@@ -226,7 +226,7 @@ const metadata = {
226226
if (
227227
declaration &&
228228
ts.isVariableDeclaration(declaration) &&
229-
!hasCorrectType(declaration)
229+
!hasType(declaration)
230230
) {
231231
diagnostics.push({
232232
file: source,
@@ -246,144 +246,40 @@ const metadata = {
246246
},
247247
}
248248

249-
function hasCorrectType(
250-
node: tsModule.FunctionDeclaration | tsModule.VariableDeclaration
251-
): boolean {
252-
// Skip if already has type.
253-
if (node.type) {
254-
return true
255-
}
256-
257-
const ts = getTs()
258-
const typeChecker = getTypeChecker()
259-
if (!typeChecker) {
260-
return false
261-
}
262-
263-
// For generateMetadata, check if it's Promise<Metadata> for async or Metadata for sync
264-
if (ts.isFunctionDeclaration(node)) {
265-
return checkFunctionReturnType(node, typeChecker)
266-
} else {
267-
// For variable declarations (const/let/var)
268-
const name = node.name.getText()
269-
270-
if (name === 'generateMetadata') {
271-
// For generateMetadata as a variable, it must be a function expression or arrow function
272-
if (
273-
!node.initializer ||
274-
(!ts.isFunctionExpression(node.initializer) &&
275-
!ts.isArrowFunction(node.initializer))
276-
) {
277-
return false
278-
}
279-
280-
// Check the return type of the function expression/arrow function
281-
if (node.initializer.type) {
282-
// If it has an explicit return type annotation
283-
return checkFunctionReturnType(node.initializer, typeChecker)
284-
} else {
285-
// If no explicit return type, infer it from the function
286-
const signature = typeChecker.getSignatureFromDeclaration(
287-
node.initializer
288-
)
289-
if (!signature) return false
290-
291-
const returnType = typeChecker.getReturnTypeOfSignature(signature)
292-
if (!returnType) return false
293-
294-
const isAsync =
295-
node.initializer.modifiers?.some(
296-
(m) => m.kind === ts.SyntaxKind.AsyncKeyword
297-
) ?? false
298-
299-
if (isAsync) {
300-
// For async functions, check if it's Promise<Metadata>
301-
const typeSymbol = returnType.getSymbol()
302-
if (!typeSymbol || typeSymbol.getName() !== 'Promise') return false
303-
304-
// Check if it's a reference type (like Promise<T>)
305-
if (
306-
!(returnType.flags & ts.TypeFlags.Object) ||
307-
!('typeArguments' in returnType)
308-
) {
309-
return false
310-
}
311-
312-
const typeArgs = (
313-
returnType as { typeArguments: readonly tsModule.Type[] }
314-
).typeArguments
315-
if (!typeArgs || typeArgs.length !== 1) return false
316-
317-
const promiseType = typeArgs[0]
318-
const promiseTypeSymbol = promiseType.getSymbol()
319-
return promiseTypeSymbol?.getName() === 'Metadata'
320-
} else {
321-
// For sync functions, check if it returns Metadata
322-
const returnTypeSymbol = returnType.getSymbol()
323-
return returnTypeSymbol?.getName() === 'Metadata'
324-
}
325-
}
326-
} else {
327-
// For metadata export, we just need Metadata type
328-
if (!node.type) return false
329-
const type = typeChecker.getTypeFromTypeNode(node.type)
330-
if (!type) return false
331-
const symbol = type.getSymbol()
332-
return symbol?.getName() === 'Metadata'
333-
}
334-
}
335-
}
336-
337-
function checkFunctionReturnType(
249+
function hasType(
338250
node:
339251
| tsModule.FunctionDeclaration
252+
| tsModule.VariableDeclaration
340253
| tsModule.FunctionExpression
341-
| tsModule.ArrowFunction,
342-
typeChecker: tsModule.TypeChecker
254+
| tsModule.ArrowFunction
343255
): boolean {
344256
const ts = getTs()
345257

346-
if (!node.type) return false
347-
348-
const returnType = typeChecker.getTypeFromTypeNode(node.type)
349-
if (!returnType) return false
350-
351-
const isAsync =
352-
node.modifiers?.some((m) => m.kind === ts.SyntaxKind.AsyncKeyword) ?? false
353-
354-
if (isAsync) {
355-
// For async functions, we need Promise<Metadata>
356-
const typeSymbol = returnType.getSymbol()
357-
if (!typeSymbol || typeSymbol.getName() !== 'Promise') {
358-
return false
359-
}
360-
361-
// Get the type argument of Promise<T>
362-
if (!ts.isTypeReferenceNode(node.type)) {
363-
return false
364-
}
258+
// For function declarations, expressions, and arrow functions, check if they have return type
259+
if (
260+
ts.isFunctionDeclaration(node) ||
261+
ts.isFunctionExpression(node) ||
262+
ts.isArrowFunction(node)
263+
) {
264+
return !!node.type
265+
}
365266

366-
// Check if it's a reference type (like Promise<T>)
267+
// For variable declarations
268+
if (!node.name) return false
269+
const name = node.name.getText()
270+
if (name === 'generateMetadata') {
271+
// If it's a function expression or arrow function, check if it has return type
367272
if (
368-
!(returnType.flags & ts.TypeFlags.Object) ||
369-
!('typeArguments' in returnType)
273+
node.initializer &&
274+
(ts.isFunctionExpression(node.initializer) ||
275+
ts.isArrowFunction(node.initializer))
370276
) {
371-
return false
372-
}
373-
374-
const typeArgs = (returnType as { typeArguments: readonly tsModule.Type[] })
375-
.typeArguments
376-
if (!typeArgs || typeArgs.length !== 1) {
377-
return false
277+
return !!node.initializer.type
378278
}
379-
const promiseType = typeArgs[0]
380-
const promiseTypeSymbol = promiseType.getSymbol()
381-
return promiseTypeSymbol?.getName() === 'Metadata'
382-
} else {
383-
// For sync functions, we need Metadata directly
384-
const symbol = returnType.getSymbol()
385-
return symbol?.getName() === 'Metadata'
386279
}
280+
281+
// For all other cases, check if the node has a type annotation
282+
return !!node.type
387283
}
388284

389285
export default metadata

0 commit comments

Comments
 (0)