Skip to content

Commit

Permalink
Enable @typescript-eslint/no-use-before-define for functions (#39602)
Browse files Browse the repository at this point in the history
Follow-up to the earlier enabling of classes/variables etc.

Bug

 Related issues linked using fixes #number
 Integration tests added
 Errors have helpful link attached, see contributing.md

Feature

 Implements an existing feature request or RFC. Make sure the feature request has been accepted for implementation before opening a PR.
 Related issues linked using fixes #number
 Integration tests added
 Documentation added
 Telemetry added. In case of a feature if it's used or not.
 Errors have helpful link attached, see contributing.md

Documentation / Examples

 Make sure the linting passes by running pnpm lint
 The examples guidelines are followed from our contributing doc

Co-authored-by: Steven <steven@ceriously.com>
  • Loading branch information
timneutkens and styfle committed Aug 15, 2022
1 parent 683db9a commit 4cd8b23
Show file tree
Hide file tree
Showing 55 changed files with 3,312 additions and 3,309 deletions.
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 }
}

0 comments on commit 4cd8b23

Please sign in to comment.