Skip to content

Commit

Permalink
codemod: comment on reexport (#71017)
Browse files Browse the repository at this point in the history
  • Loading branch information
huozhi authored Oct 9, 2024
1 parent d938b07 commit 81147ee
Show file tree
Hide file tree
Showing 7 changed files with 117 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { Page as default } from './page'
export { generateMetadata } from './generate-metadata'
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export { /* Next.js Dynamic Async API Codemod: `Page` export is re-exported. Check if this component uses `params` or `searchParams`*/
Page as default } from './page'
export { /* Next.js Dynamic Async API Codemod: `generateMetadata` export is re-exported. Check if this component uses `params` or `searchParams`*/
generateMetadata } from './generate-metadata'

Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { generateMetadata } from './generate-metadata'
export { default } from './page'
export { generateMetadata }
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { generateMetadata } from './generate-metadata'
export { /* Next.js Dynamic Async API Codemod: `default` export is re-exported. Check if this component uses `params` or `searchParams`*/
default } from './page'
export { /* Next.js Dynamic Async API Codemod: `generateMetadata` export is re-exported. Check if this component uses `params` or `searchParams`*/
generateMetadata }
Original file line number Diff line number Diff line change
Expand Up @@ -29,8 +29,12 @@ function findDynamicImportsAndComment(root: Collection<any>, j: API['j']) {
})

importPaths.forEach((path) => {
insertCommentOnce(path.node, j, DYNAMIC_IMPORT_WARN_COMMENT)
modified = true
const inserted = insertCommentOnce(
path.node,
j,
DYNAMIC_IMPORT_WARN_COMMENT
)
modified ||= inserted
})
return modified
}
Expand Down Expand Up @@ -180,7 +184,7 @@ export function transformDynamicAPI(
)
needsReactUseImport = true
} else {
castTypesOrAddComment(
const casted = castTypesOrAddComment(
j,
path,
originRequestApiName,
Expand All @@ -189,9 +193,10 @@ export function transformDynamicAPI(
insertedTypes,
` Next.js Dynamic Async API Codemod: Manually await this call, if it's a Server Component `
)
modified ||= casted
}
} else {
castTypesOrAddComment(
const casted = castTypesOrAddComment(
j,
path,
originRequestApiName,
Expand All @@ -200,8 +205,8 @@ export function transformDynamicAPI(
insertedTypes,
' Next.js Dynamic Async API Codemod: please manually await this call, codemod cannot transform due to undetermined async scope '
)
modified ||= casted
}
modified = true
}
}
})
Expand Down Expand Up @@ -263,9 +268,8 @@ export function transformDynamicAPI(
insertReactUseImport(root, j)
}

if (findDynamicImportsAndComment(root, j)) {
modified = true
}
const commented = findDynamicImportsAndComment(root, j)
modified ||= commented

return modified ? root.toSource() : null
}
Expand All @@ -286,10 +290,11 @@ function castTypesOrAddComment(
insertedTypes: Set<string>,
customMessage: string
) {
let modified = false
const isTsFile = filePath.endsWith('.ts') || filePath.endsWith('.tsx')
if (isTsFile) {
// if the path of call expression is already being awaited, no need to cast
if (path.parentPath?.node?.type === 'AwaitExpression') return
if (path.parentPath?.node?.type === 'AwaitExpression') return false

/* Do type cast for headers, cookies, draftMode
import {
Expand All @@ -314,6 +319,7 @@ function castTypesOrAddComment(
// Replace the original expression with the new cast expression,
// also wrap () around the new cast expression.
j(path).replaceWith(j.parenthesizedExpression(newCastExpression))
modified = true

// If cast types are not imported, add them to the import list
const importDeclaration = root.find(j.ImportDeclaration, {
Expand Down Expand Up @@ -343,8 +349,11 @@ function castTypesOrAddComment(
}
} else {
// Otherwise for JS file, leave a message to the user to manually handle the transformation
insertCommentOnce(path.node, j, customMessage)
const inserted = insertCommentOnce(path.node, j, customMessage)
modified ||= inserted
}

return modified
}

function findImportMappingFromNextHeaders(root: Collection<any>, j: API['j']) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
} from './utils'

const PAGE_PROPS = 'props'
const MATCHED_FILE_PATTERNS = /([\\/]|^)(page|layout|route)\.(t|j)sx?$/

function findFunctionBody(path: ASTPath<FunctionScope>) {
let functionBody = path.node.body
Expand Down Expand Up @@ -175,14 +176,64 @@ function applyUseAndRenameAccessedProp(
return modified
}

const MATCHED_FILE_PATTERNS = /([\\/]|^)(page|layout|route)\.(t|j)sx?$/
function commentOnMatchedReExports(
root: Collection<any>,
j: API['jscodeshift']
): boolean {
let modified = false
root.find(j.ExportNamedDeclaration).forEach((path) => {
if (j.ExportSpecifier.check(path.value.specifiers[0])) {
const specifiers = path.value.specifiers
for (const specifier of specifiers) {
if (
j.ExportSpecifier.check(specifier) &&
// Find matched named exports and default export
(TARGET_NAMED_EXPORTS.has(specifier.exported.name) ||
specifier.exported.name === 'default')
) {
if (j.Literal.check(path.value.source)) {
const localName = specifier.local.name

const commentInserted = insertCommentOnce(
specifier,
j,
` Next.js Dynamic Async API Codemod: \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``
)
modified ||= commentInserted
} else if (path.value.source === null) {
const localIdentifier = specifier.local
const localName = localIdentifier.name
// search if local identifier is from imports
const importDeclaration = root
.find(j.ImportDeclaration)
.filter((importPath) => {
return importPath.value.specifiers.some(
(importSpecifier) => importSpecifier.local.name === localName
)
})
if (importDeclaration.size() > 0) {
const commentInserted = insertCommentOnce(
specifier,
j,
` Next.js Dynamic Async API Codemod: \`${localName}\` export is re-exported. Check if this component uses \`params\` or \`searchParams\``
)
modified ||= commentInserted
}
}
}
}
}
})
return modified
}

function modifyTypes(
paramTypeAnnotation: any,
propsIdentifier: Identifier,
root: Collection<any>,
j: API['jscodeshift']
) {
): boolean {
let modified = false
if (paramTypeAnnotation && paramTypeAnnotation.typeAnnotation) {
const typeAnnotation = paramTypeAnnotation.typeAnnotation
if (typeAnnotation.type === 'TSTypeLiteral') {
Expand Down Expand Up @@ -220,6 +271,7 @@ function modifyTypes(
member.typeAnnotation.typeAnnotation,
])
)
modified = true
}
}
})
Expand Down Expand Up @@ -270,6 +322,7 @@ function modifyTypes(
member.typeAnnotation.typeAnnotation,
])
)
modified = true
}
}
})
Expand All @@ -279,7 +332,9 @@ function modifyTypes(
}

propsIdentifier.typeAnnotation = paramTypeAnnotation
modified = true
}
return modified
}

export function transformDynamicProps(
Expand Down Expand Up @@ -380,7 +435,8 @@ export function transformDynamicProps(
modified = true
}
} else {
modified = awaitMemberAccessOfProp(argName, path, j)
const awaited = awaitMemberAccessOfProp(argName, path, j)
modified ||= awaited
}

// cases of passing down `props` into any function
Expand All @@ -407,9 +463,8 @@ export function transformDynamicProps(
(arg) => j.Identifier.check(arg) && arg.name === argName
)
const comment = ` Next.js Dynamic Async API Codemod: '${argName}' is passed as an argument. Any asynchronous properties of 'props' must be awaited when accessed. `
insertCommentOnce(propPassedAsArg, j, comment)

modified = true
const inserted = insertCommentOnce(propPassedAsArg, j, comment)
modified ||= inserted
})

if (modified) {
Expand Down Expand Up @@ -438,9 +493,14 @@ export function transformDynamicProps(
} else {
// When the prop argument is not destructured, we need to add comments to the spread properties
if (j.Identifier.check(currentParam)) {
commentSpreadProps(path, currentParam.name, j)
modifyTypes(currentParam.typeAnnotation, propsIdentifier, root, j)
modified = true
const commented = commentSpreadProps(path, currentParam.name, j)
const modifiedTypes = modifyTypes(
currentParam.typeAnnotation,
propsIdentifier,
root,
j
)
modified ||= commented || modifiedTypes
}
}
}
Expand Down Expand Up @@ -754,6 +814,9 @@ export function transformDynamicProps(
insertReactUseImport(root, j)
}

const commented = commentOnMatchedReExports(root, j)
modified ||= commented

return modified ? root.toSource() : null
}

Expand Down Expand Up @@ -824,7 +887,8 @@ function commentSpreadProps(
path: ASTPath<FunctionScope>,
propsIdentifierName: string,
j: API['jscodeshift']
) {
): boolean {
let modified = false
const functionBody = findFunctionBody(path)
const functionBodyCollection = j(functionBody)
// Find all the usage of spreading properties of `props`
Expand All @@ -839,10 +903,14 @@ function commentSpreadProps(

// Add comment before it
jsxSpreadProperties.forEach((spread) => {
insertCommentOnce(spread.value, j, comment)
const inserted = insertCommentOnce(spread.value, j, comment)
if (inserted) modified = true
})

objSpreadProperties.forEach((spread) => {
insertCommentOnce(spread.value, j, comment)
const inserted = insertCommentOnce(spread.value, j, comment)
if (inserted) modified = true
})

return modified
}
Original file line number Diff line number Diff line change
Expand Up @@ -427,16 +427,17 @@ export function insertCommentOnce(
node: ASTPath<any>['node'],
j: API['j'],
comment: string
) {
): boolean {
if (node.comments) {
const hasComment = node.comments.some(
(commentNode) => commentNode.value === comment
)
if (hasComment) {
return
return false
}
}
node.comments = [j.commentBlock(comment), ...(node.comments || [])]
return true
}

export function getVariableDeclaratorId(
Expand Down

0 comments on commit 81147ee

Please sign in to comment.