Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Enable @typescript-eslint/no-use-before-define for functions #39602

Merged
merged 5 commits into from Aug 15, 2022
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .eslintrc.json
Expand Up @@ -68,7 +68,7 @@
"@typescript-eslint/no-use-before-define": [
"warn",
{
"functions": false,
"functions": true,
"classes": true,
"variables": true,
"enums": true,
Expand Down
92 changes: 46 additions & 46 deletions packages/next/build/analysis/extract-const-value.ts
Expand Up @@ -17,52 +17,6 @@ import type {

export class NoSuchDeclarationError extends Error {}

/**
* Extracts the value of an exported const variable named `exportedName`
* (e.g. "export const config = { runtime: 'experimental-edge' }") from swc's AST.
* The value must be one of (or throws UnsupportedValueError):
* - string
* - boolean
* - number
* - null
* - undefined
* - array containing values listed in this list
* - object containing values listed in this list
*
* Throws NoSuchDeclarationError if the declaration is not found.
*/
export function extractExportedConstValue(
module: Module,
exportedName: string
): any {
for (const moduleItem of module.body) {
if (!isExportDeclaration(moduleItem)) {
continue
}

const declaration = moduleItem.declaration
if (!isVariableDeclaration(declaration)) {
continue
}

if (declaration.kind !== 'const') {
continue
}

for (const decl of declaration.declarations) {
if (
isIdentifier(decl.id) &&
decl.id.value === exportedName &&
decl.init
) {
return extractValue(decl.init, [exportedName])
}
}
}

throw new NoSuchDeclarationError()
}

function isExportDeclaration(node: Node): node is ExportDeclaration {
return node.type === 'ExportDeclaration'
}
Expand Down Expand Up @@ -247,3 +201,49 @@ function extractValue(node: Node, path?: string[]): any {
)
}
}

/**
* Extracts the value of an exported const variable named `exportedName`
* (e.g. "export const config = { runtime: 'experimental-edge' }") from swc's AST.
* The value must be one of (or throws UnsupportedValueError):
* - string
* - boolean
* - number
* - null
* - undefined
* - array containing values listed in this list
* - object containing values listed in this list
*
* Throws NoSuchDeclarationError if the declaration is not found.
*/
export function extractExportedConstValue(
module: Module,
exportedName: string
): any {
for (const moduleItem of module.body) {
if (!isExportDeclaration(moduleItem)) {
continue
}

const declaration = moduleItem.declaration
if (!isVariableDeclaration(declaration)) {
continue
}

if (declaration.kind !== 'const') {
continue
}

for (const decl of declaration.declarations) {
if (
isIdentifier(decl.id) &&
decl.id.value === exportedName &&
decl.init
) {
return extractValue(decl.init, [exportedName])
}
}
}

throw new NoSuchDeclarationError()
}
188 changes: 94 additions & 94 deletions packages/next/build/analysis/get-page-static-info.ts
Expand Up @@ -22,79 +22,6 @@ export interface PageStaticInfo {
middleware?: Partial<MiddlewareConfig>
}

/**
* For a given pageFilePath and nextConfig, if the config supports it, this
* function will read the file and return the runtime that should be used.
* It will look into the file content only if the page *requires* a runtime
* to be specified, that is, when gSSP or gSP is used.
* Related discussion: https://github.com/vercel/next.js/discussions/34179
*/
export async function getPageStaticInfo(params: {
nextConfig: Partial<NextConfig>
pageFilePath: string
isDev?: boolean
page?: string
}): Promise<PageStaticInfo> {
const { isDev, pageFilePath, nextConfig, page } = params

const fileContent = (await tryToReadFile(pageFilePath, !isDev)) || ''
if (/runtime|getStaticProps|getServerSideProps|matcher/.test(fileContent)) {
const swcAST = await parseModule(pageFilePath, fileContent)
const { ssg, ssr } = checkExports(swcAST)

// default / failsafe value for config
let config: any = {}
try {
config = extractExportedConstValue(swcAST, 'config')
} catch (e) {
if (e instanceof UnsupportedValueError) {
warnAboutUnsupportedValue(pageFilePath, page, e)
}
// `export config` doesn't exist, or other unknown error throw by swc, silence them
}

if (
typeof config.runtime !== 'string' &&
typeof config.runtime !== 'undefined'
) {
throw new Error(`Provided runtime `)
} else if (!isServerRuntime(config.runtime)) {
const options = Object.values(SERVER_RUNTIME).join(', ')
if (typeof config.runtime !== 'string') {
throw new Error(
`The \`runtime\` config must be a string. Please leave it empty or choose one of: ${options}`
)
} else {
throw new Error(
`Provided runtime "${config.runtime}" is not supported. Please leave it empty or choose one of: ${options}`
)
}
}

let runtime =
SERVER_RUNTIME.edge === config?.runtime
? SERVER_RUNTIME.edge
: ssr || ssg
? config?.runtime || nextConfig.experimental?.runtime
: undefined

if (runtime === SERVER_RUNTIME.edge) {
warnAboutExperimentalEdgeApiFunctions()
}

const middlewareConfig = getMiddlewareConfig(config, nextConfig)

return {
ssr,
ssg,
...(middlewareConfig && { middleware: middlewareConfig }),
...(runtime && { runtime }),
}
}

return { ssr: false, ssg: false }
}

/**
* Receives a parsed AST from SWC and checks if it belongs to a module that
* requires a runtime to be specified. Those are:
Expand Down Expand Up @@ -154,27 +81,6 @@ async function tryToReadFile(filePath: string, shouldThrow: boolean) {
}
}

function getMiddlewareConfig(
config: any,
nextConfig: NextConfig
): Partial<MiddlewareConfig> {
const result: Partial<MiddlewareConfig> = {}

if (config.matcher) {
result.pathMatcher = new RegExp(
getMiddlewareRegExpStrings(config.matcher, nextConfig).join('|')
)

if (result.pathMatcher.source.length > 4096) {
throw new Error(
`generated matcher config must be less than 4096 characters.`
)
}
}

return result
}

function getMiddlewareRegExpStrings(
matcherOrMatchers: unknown,
nextConfig: NextConfig
Expand Down Expand Up @@ -226,6 +132,27 @@ function getMiddlewareRegExpStrings(
}
}

function getMiddlewareConfig(
config: any,
nextConfig: NextConfig
): Partial<MiddlewareConfig> {
const result: Partial<MiddlewareConfig> = {}

if (config.matcher) {
result.pathMatcher = new RegExp(
getMiddlewareRegExpStrings(config.matcher, nextConfig).join('|')
)

if (result.pathMatcher.source.length > 4096) {
throw new Error(
`generated matcher config must be less than 4096 characters.`
)
}
}

return result
}

let warnedAboutExperimentalEdgeApiFunctions = false
function warnAboutExperimentalEdgeApiFunctions() {
if (warnedAboutExperimentalEdgeApiFunctions) {
Expand Down Expand Up @@ -258,3 +185,76 @@ function warnAboutUnsupportedValue(

warnedUnsupportedValueMap.set(pageFilePath, true)
}

/**
* For a given pageFilePath and nextConfig, if the config supports it, this
* function will read the file and return the runtime that should be used.
* It will look into the file content only if the page *requires* a runtime
* to be specified, that is, when gSSP or gSP is used.
* Related discussion: https://github.com/vercel/next.js/discussions/34179
*/
export async function getPageStaticInfo(params: {
nextConfig: Partial<NextConfig>
pageFilePath: string
isDev?: boolean
page?: string
}): Promise<PageStaticInfo> {
const { isDev, pageFilePath, nextConfig, page } = params

const fileContent = (await tryToReadFile(pageFilePath, !isDev)) || ''
if (/runtime|getStaticProps|getServerSideProps|matcher/.test(fileContent)) {
const swcAST = await parseModule(pageFilePath, fileContent)
const { ssg, ssr } = checkExports(swcAST)

// default / failsafe value for config
let config: any = {}
try {
config = extractExportedConstValue(swcAST, 'config')
} catch (e) {
if (e instanceof UnsupportedValueError) {
warnAboutUnsupportedValue(pageFilePath, page, e)
}
// `export config` doesn't exist, or other unknown error throw by swc, silence them
}

if (
typeof config.runtime !== 'string' &&
typeof config.runtime !== 'undefined'
) {
throw new Error(`Provided runtime `)
} else if (!isServerRuntime(config.runtime)) {
const options = Object.values(SERVER_RUNTIME).join(', ')
if (typeof config.runtime !== 'string') {
throw new Error(
`The \`runtime\` config must be a string. Please leave it empty or choose one of: ${options}`
)
} else {
throw new Error(
`Provided runtime "${config.runtime}" is not supported. Please leave it empty or choose one of: ${options}`
)
}
}

let runtime =
SERVER_RUNTIME.edge === config?.runtime
? SERVER_RUNTIME.edge
: ssr || ssg
? config?.runtime || nextConfig.experimental?.runtime
: undefined

if (runtime === SERVER_RUNTIME.edge) {
warnAboutExperimentalEdgeApiFunctions()
}

const middlewareConfig = getMiddlewareConfig(config, nextConfig)

return {
ssr,
ssg,
...(middlewareConfig && { middleware: middlewareConfig }),
...(runtime && { runtime }),
}
}

return { ssr: false, ssg: false }
}