fix(cpa): ast parse error handling (#5793)

This commit is contained in:
Elliot DeNolf
2024-04-11 12:01:16 -04:00
committed by GitHub
parent 512b7bd429
commit a4956dc649
4 changed files with 65 additions and 20 deletions

View File

@@ -35,7 +35,7 @@
"comment-json": "^4.2.3",
"degit": "^2.8.4",
"detect-package-manager": "^3.0.1",
"esprima": "^4.0.1",
"esprima-next": "^6.0.3",
"execa": "^5.0.0",
"figures": "^6.1.0",
"fs-extra": "^9.0.1",

View File

@@ -17,6 +17,11 @@ export default someFunc(
nextConfigExportNamedDefault: `const nextConfig = {};
const wrapped = someFunc(asdf);
export { wrapped as default };
`,
nextConfigWithSpread: `const nextConfig = {
...someConfig,
};
export default nextConfig;
`,
}
@@ -38,6 +43,9 @@ module.exports = someFunc(
nextConfigExportNamedDefault: `const nextConfig = {};
const wrapped = someFunc(asdf);
module.exports = wrapped;
`,
nextConfigWithSpread: `const nextConfig = { ...someConfig };
module.exports = nextConfig;
`,
}
@@ -70,6 +78,15 @@ describe('parseAndInsertWithPayload', () => {
expect(modifiedConfigContent).toMatch(/withPayload\(someFunc\(\n nextConfig\n\)\)/)
})
it('should parse the config with a spread', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
esmConfigs.nextConfigWithSpread,
configType,
)
expect(modifiedConfigContent).toContain(importStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
// Unsupported: export { wrapped as default }
it('should give warning with a named export as default', () => {
const warnLogSpy = jest.spyOn(p.log, 'warn').mockImplementation(() => {})
@@ -129,5 +146,14 @@ describe('parseAndInsertWithPayload', () => {
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(wrapped)')
})
it('should parse the config with a spread', () => {
const { modifiedConfigContent } = parseAndModifyConfigContent(
cjsConfigs.nextConfigWithSpread,
configType,
)
expect(modifiedConfigContent).toContain(requireStatement)
expect(modifiedConfigContent).toContain('withPayload(nextConfig)')
})
})
})

View File

@@ -1,5 +1,7 @@
import type { Program } from 'esprima-next'
import chalk from 'chalk'
import { parseModule } from 'esprima'
import { Syntax, parseModule } from 'esprima-next'
import fs from 'fs'
import { warning } from '../utils/log.js'
@@ -38,16 +40,29 @@ export function parseAndModifyConfigContent(
configType: NextConfigType,
): { modifiedConfigContent: string; success: boolean } {
content = withPayloadStatement[configType] + content
const ast = parseModule(content, { loc: true })
let ast: Program | undefined
try {
ast = parseModule(content, { loc: true })
} catch (error: unknown) {
if (error instanceof Error) {
warning(`Unable to parse Next config. Error: ${error.message} `)
warnUserWrapNotSuccessful(configType)
}
return {
modifiedConfigContent: content,
success: false,
}
}
if (configType === 'esm') {
const exportDefaultDeclaration = ast.body.find((p) => p.type === 'ExportDefaultDeclaration') as
| Directive
| undefined
const exportDefaultDeclaration = ast.body.find(
(p) => p.type === Syntax.ExportDefaultDeclaration,
) as Directive | undefined
const exportNamedDeclaration = ast.body.find((p) => p.type === 'ExportNamedDeclaration') as
| ExportNamedDeclaration
| undefined
const exportNamedDeclaration = ast.body.find(
(p) => p.type === Syntax.ExportNamedDeclaration,
) as ExportNamedDeclaration | undefined
if (!exportDefaultDeclaration && !exportNamedDeclaration) {
throw new Error('Could not find ExportDefaultDeclaration in next.config.js')
@@ -57,7 +72,6 @@ export function parseAndModifyConfigContent(
const modifiedConfigContent = insertBeforeAndAfter(
content,
exportDefaultDeclaration.declaration.loc,
configType,
)
return { modifiedConfigContent, success: true }
} else if (exportNamedDeclaration) {
@@ -91,12 +105,12 @@ export function parseAndModifyConfigContent(
// Find `module.exports = X`
const moduleExports = ast.body.find(
(p) =>
p.type === 'ExpressionStatement' &&
p.expression?.type === 'AssignmentExpression' &&
p.expression.left?.type === 'MemberExpression' &&
p.expression.left.object?.type === 'Identifier' &&
p.type === Syntax.ExpressionStatement &&
p.expression?.type === Syntax.AssignmentExpression &&
p.expression.left?.type === Syntax.MemberExpression &&
p.expression.left.object?.type === Syntax.Identifier &&
p.expression.left.object.name === 'module' &&
p.expression.left.property?.type === 'Identifier' &&
p.expression.left.property?.type === Syntax.Identifier &&
p.expression.left.property.name === 'exports',
// eslint-disable-next-line @typescript-eslint/no-explicit-any
) as any
@@ -105,7 +119,6 @@ export function parseAndModifyConfigContent(
const modifiedConfigContent = insertBeforeAndAfter(
content,
moduleExports.expression.right.loc,
configType,
)
return { modifiedConfigContent, success: true }
}
@@ -174,7 +187,7 @@ type Loc = {
start: { column: number; line: number }
}
function insertBeforeAndAfter(content: string, loc: Loc, configType: NextConfigType) {
function insertBeforeAndAfter(content: string, loc: Loc) {
const { end, start } = loc
const lines = content.split('\n')

12
pnpm-lock.yaml generated
View File

@@ -319,9 +319,9 @@ importers:
detect-package-manager:
specifier: ^3.0.1
version: 3.0.1
esprima:
specifier: ^4.0.1
version: 4.0.1
esprima-next:
specifier: ^6.0.3
version: 6.0.3
execa:
specifier: ^5.0.0
version: 5.1.1
@@ -9396,6 +9396,12 @@ packages:
acorn-jsx: 5.3.2(acorn@8.11.3)
eslint-visitor-keys: 3.4.3
/esprima-next@6.0.3:
resolution: {integrity: sha512-fVfE+9qIOJSbS3AR7roIuL0gCeS+tC86bJV9GlJtwXCRoo67q6tsGGUjThW+JtR5IQSShnHqaDqX8D0IYDfRGA==}
engines: {node: '>=12'}
hasBin: true
dev: false
/esprima@4.0.1:
resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==}
engines: {node: '>=4'}