forked from vercel/next.js
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add no-anon-default-export Babel lint rule (vercel#14519)
Fixes vercel#12291 ![image](https://user-images.githubusercontent.com/616428/85499698-176b4900-b5b0-11ea-8a5d-a7f0b4c20307.png)
- Loading branch information
Showing
9 changed files
with
206 additions
and
1 deletion.
There are no files selected for viewing
83 changes: 83 additions & 0 deletions
83
packages/next/build/babel/plugins/no-anonymous-default-export.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { PluginObj, types as BabelTypes } from '@babel/core' | ||
import chalk from 'next/dist/compiled/chalk' | ||
|
||
export default function NoAnonymousDefaultExport({ | ||
types: t, | ||
...babel | ||
}: { | ||
types: typeof BabelTypes | ||
caller: (callerCallback: (caller: any) => any) => any | ||
}): PluginObj<any> { | ||
let onWarning: ((reason: string | Error) => void) | null = null | ||
babel.caller((caller) => { | ||
onWarning = caller.onWarning | ||
return '' // Intentionally empty to not invalidate cache | ||
}) | ||
|
||
if (typeof onWarning !== 'function') { | ||
return { visitor: {} } | ||
} | ||
|
||
const warn = onWarning! | ||
return { | ||
visitor: { | ||
ExportDefaultDeclaration(path) { | ||
const def = path.node.declaration | ||
|
||
if ( | ||
!( | ||
def.type === 'ArrowFunctionExpression' || | ||
def.type === 'FunctionDeclaration' | ||
) | ||
) { | ||
return | ||
} | ||
|
||
switch (def.type) { | ||
case 'ArrowFunctionExpression': { | ||
warn( | ||
[ | ||
chalk.yellow.bold( | ||
'Anonymous arrow functions cause Fast Refresh to not preserve local component state.' | ||
), | ||
'Please add a name to your function, for example:', | ||
'', | ||
chalk.bold('Before'), | ||
chalk.cyan('export default () => <div />;'), | ||
'', | ||
chalk.bold('After'), | ||
chalk.cyan('const Named = () => <div />;'), | ||
chalk.cyan('export default Named;'), | ||
].join('\n') | ||
) | ||
break | ||
} | ||
case 'FunctionDeclaration': { | ||
const isAnonymous = !Boolean(def.id) | ||
if (isAnonymous) { | ||
warn( | ||
[ | ||
chalk.yellow.bold( | ||
'Anonymous function declarations cause Fast Refresh to not preserve local component state.' | ||
), | ||
'Please add a name to your function, for example:', | ||
'', | ||
chalk.bold('Before'), | ||
chalk.cyan('export default function () { /* ... */ }'), | ||
'', | ||
chalk.bold('After'), | ||
chalk.cyan('export default function Named() { /* ... */ }'), | ||
].join('\n') | ||
) | ||
} | ||
break | ||
} | ||
default: { | ||
// eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
const _: never = def | ||
} | ||
} | ||
}, | ||
}, | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,3 @@ | ||
export default ({ Component, pageProps }) => <Component {...pageProps} /> | ||
export default function CustomApp({ Component, pageProps }) { | ||
return <Component {...pageProps} /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default () => { | ||
return <div /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import Child from '../components/Child' | ||
export default function () { | ||
return <Child /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
import Child from '../components/Child' | ||
export default function A() { | ||
return <Child /> | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export default function () { | ||
return <div /> | ||
} |
86 changes: 86 additions & 0 deletions
86
test/integration/no-anon-default-export/test/index.test.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
/* eslint-env jest */ | ||
|
||
import fs from 'fs-extra' | ||
import { check, findPort, killApp, launchApp } from 'next-test-utils' | ||
import webdriver from 'next-webdriver' | ||
import { join } from 'path' | ||
|
||
jest.setTimeout(1000 * 60 * 3) | ||
|
||
const appDir = join(__dirname, '../') | ||
|
||
describe('no anonymous default export warning', () => { | ||
function getRegexCount(text, regex) { | ||
return (text.match(regex) || []).length | ||
} | ||
|
||
beforeEach(async () => { | ||
await fs.remove(join(appDir, '.next')) | ||
}) | ||
|
||
it('show correct warnings for page', async () => { | ||
let stdout = '' | ||
|
||
const appPort = await findPort() | ||
const app = await launchApp(appDir, appPort, { | ||
env: { __NEXT_TEST_WITH_DEVTOOL: true }, | ||
onStdout(msg) { | ||
stdout += msg || '' | ||
}, | ||
}) | ||
|
||
const browser = await webdriver(appPort, '/page') | ||
|
||
const found = await check(() => stdout, /anonymous/i, false) | ||
expect(found).toBeTruthy() | ||
await browser.close() | ||
|
||
expect(getRegexCount(stdout, /not preserve local component state/g)).toBe(1) | ||
|
||
await killApp(app) | ||
}) | ||
|
||
it('show correct warnings for child', async () => { | ||
let stdout = '' | ||
|
||
const appPort = await findPort() | ||
const app = await launchApp(appDir, appPort, { | ||
env: { __NEXT_TEST_WITH_DEVTOOL: true }, | ||
onStdout(msg) { | ||
stdout += msg || '' | ||
}, | ||
}) | ||
|
||
const browser = await webdriver(appPort, '/child') | ||
|
||
const found = await check(() => stdout, /anonymous/i, false) | ||
expect(found).toBeTruthy() | ||
await browser.close() | ||
|
||
expect(getRegexCount(stdout, /not preserve local component state/g)).toBe(1) | ||
|
||
await killApp(app) | ||
}) | ||
|
||
it('show correct warnings for both', async () => { | ||
let stdout = '' | ||
|
||
const appPort = await findPort() | ||
const app = await launchApp(appDir, appPort, { | ||
env: { __NEXT_TEST_WITH_DEVTOOL: true }, | ||
onStdout(msg) { | ||
stdout += msg || '' | ||
}, | ||
}) | ||
|
||
const browser = await webdriver(appPort, '/both') | ||
|
||
const found = await check(() => stdout, /anonymous/i, false) | ||
expect(found).toBeTruthy() | ||
await browser.close() | ||
|
||
expect(getRegexCount(stdout, /not preserve local component state/g)).toBe(2) | ||
|
||
await killApp(app) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters