Skip to content

Commit 0b12e2e

Browse files
JanpotTimer
andauthored
Improve error for invalid page configurations (vercel#10441)
* Remove any type and fix edge cases Removed the "as any" and use the @babel/types typeguards instead. This revealed some edge cases that would just error. * Remove ts-ignore Co-authored-by: Joe Haddad <timer150@gmail.com>
1 parent 44e40d4 commit 0b12e2e

File tree

8 files changed

+123
-23
lines changed

8 files changed

+123
-23
lines changed

packages/next/build/babel/plugins/next-page-config.ts

Lines changed: 39 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@ import { NodePath, PluginObj } from '@babel/core'
22
import * as BabelTypes from '@babel/types'
33
import { PageConfig } from 'next/types'
44

5-
const configKeys = new Set(['amp'])
65
const STRING_LITERAL_DROP_BUNDLE = '__NEXT_DROP_CLIENT_FILE__'
76

87
// replace program path with just a variable with the drop identifier
@@ -26,6 +25,12 @@ function replaceBundle(path: any, t: typeof BabelTypes) {
2625
)
2726
}
2827

28+
function errorMessage(state: any, details: string) {
29+
const pageName =
30+
(state.filename || '').split(state.cwd || '').pop() || 'unknown'
31+
return `Invalid page config export found. ${details} in file ${pageName}. See: https://err.sh/zeit/next.js/invalid-page-config`
32+
}
33+
2934
interface ConfigState {
3035
bundleDropped?: boolean
3136
}
@@ -50,32 +55,51 @@ export default function nextPageConfig({
5055
return
5156
}
5257

53-
const { declarations } = path.node.declaration as any
54-
const config: PageConfig = {}
55-
56-
if (!declarations) {
58+
if (!BabelTypes.isVariableDeclaration(path.node.declaration)) {
5759
return
5860
}
61+
62+
const { declarations } = path.node.declaration
63+
const config: PageConfig = {}
64+
5965
for (const declaration of declarations) {
60-
if (declaration.id.name !== 'config') {
66+
if (
67+
!BabelTypes.isIdentifier(declaration.id, { name: 'config' })
68+
) {
6169
continue
6270
}
6371

64-
if (declaration.init.type !== 'ObjectExpression') {
65-
const pageName =
66-
(state.filename || '').split(state.cwd || '').pop() ||
67-
'unknown'
68-
72+
if (!BabelTypes.isObjectExpression(declaration.init)) {
73+
const got = declaration.init
74+
? declaration.init.type
75+
: 'undefined'
6976
throw new Error(
70-
`Invalid page config export found. Expected object but got ${declaration.init.type} in file ${pageName}. See: https://err.sh/zeit/next.js/invalid-page-config`
77+
errorMessage(state, `Expected object but got ${got}`)
7178
)
7279
}
7380

7481
for (const prop of declaration.init.properties) {
82+
if (BabelTypes.isSpreadElement(prop)) {
83+
throw new Error(
84+
errorMessage(state, `Property spread is not allowed`)
85+
)
86+
}
7587
const { name } = prop.key
76-
if (configKeys.has(name)) {
77-
// @ts-ignore
78-
config[name] = prop.value.value
88+
if (BabelTypes.isIdentifier(prop.key, { name: 'amp' })) {
89+
if (!BabelTypes.isObjectProperty(prop)) {
90+
throw new Error(
91+
errorMessage(state, `Invalid property "${name}"`)
92+
)
93+
}
94+
if (
95+
!BabelTypes.isBooleanLiteral(prop.value) &&
96+
!BabelTypes.isStringLiteral(prop.value)
97+
) {
98+
throw new Error(
99+
errorMessage(state, `Invalid value for "${name}"`)
100+
)
101+
}
102+
config.amp = prop.value.value as PageConfig['amp']
79103
}
80104
}
81105
}

test/integration/page-config/pages/index.js

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { config as hello } from '../something'
22
import { config as world } from '../config'
33

4-
// export const config = 'hello world'
5-
64
export default () => (
75
<p>
86
{hello} {world}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// export const config = { amp() {} }
2+
3+
export default () => <p>hello world</p>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const amp = 'hybrid'
2+
3+
// export const config = { amp }
4+
5+
export default () => <p>hello ${amp}</p>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// export let config
2+
3+
export default () => <p>hello world</p>
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
const props = { amp: true }
2+
3+
// export const config = { ...props }
4+
5+
export default () => <p>{props}</p>
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
// export const config = 'hello world'
2+
3+
export default () => <p>hello world</p>

test/integration/page-config/test/index.test.js

Lines changed: 65 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,28 +5,87 @@ import { join } from 'path'
55
import { nextBuild } from 'next-test-utils'
66

77
const appDir = join(__dirname, '..')
8-
const indexPage = join(appDir, 'pages/index.js')
98

109
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000 * 60 * 2
1110

11+
async function uncommentExport(page) {
12+
const pagePath = join(appDir, 'pages', page)
13+
const origContent = await fs.readFile(pagePath, 'utf8')
14+
const newContent = origContent.replace('// export', 'export')
15+
await fs.writeFile(pagePath, newContent, 'utf8')
16+
return async () => {
17+
await fs.writeFile(pagePath, origContent, 'utf8')
18+
}
19+
}
20+
1221
describe('Page Config', () => {
1322
it('builds without error when export const config is used outside page', async () => {
1423
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
1524
expect(stderr).not.toMatch(/Failed to compile\./)
1625
})
1726

18-
it('shows valid error on invalid page config', async () => {
19-
const origContent = await fs.readFile(indexPage, 'utf8')
20-
const newContent = origContent.replace('// export', 'export')
21-
await fs.writeFile(indexPage, newContent, 'utf8')
27+
it('shows valid error when page config is a string', async () => {
28+
const reset = await uncommentExport('invalid/string-config.js')
29+
30+
try {
31+
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
32+
expect(stderr).toMatch(
33+
/https:\/\/err\.sh\/zeit\/next\.js\/invalid-page-config/
34+
)
35+
} finally {
36+
await reset()
37+
}
38+
})
39+
40+
it('shows valid error when page config has no init', async () => {
41+
const reset = await uncommentExport('invalid/no-init.js')
42+
43+
try {
44+
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
45+
expect(stderr).toMatch(
46+
/https:\/\/err\.sh\/zeit\/next\.js\/invalid-page-config/
47+
)
48+
} finally {
49+
await reset()
50+
}
51+
})
52+
53+
it('shows error when page config has spread properties', async () => {
54+
const reset = await uncommentExport('invalid/spread-config.js')
55+
56+
try {
57+
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
58+
expect(stderr).toMatch(
59+
/https:\/\/err\.sh\/zeit\/next\.js\/invalid-page-config/
60+
)
61+
} finally {
62+
await reset()
63+
}
64+
})
65+
66+
it('shows error when page config has invalid properties', async () => {
67+
const reset = await uncommentExport('invalid/invalid-property.js')
68+
69+
try {
70+
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
71+
expect(stderr).toMatch(
72+
/https:\/\/err\.sh\/zeit\/next\.js\/invalid-page-config/
73+
)
74+
} finally {
75+
await reset()
76+
}
77+
})
78+
79+
it('shows error when page config has invalid property value', async () => {
80+
const reset = await uncommentExport('invalid/invalid-value.js')
2281

2382
try {
2483
const { stderr } = await nextBuild(appDir, undefined, { stderr: true })
2584
expect(stderr).toMatch(
2685
/https:\/\/err\.sh\/zeit\/next\.js\/invalid-page-config/
2786
)
2887
} finally {
29-
await fs.writeFile(indexPage, origContent, 'utf8')
88+
await reset()
3089
}
3190
})
3291
})

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy