diff --git a/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts b/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts index 769fd538d5f4..8ee840f1554d 100644 --- a/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts +++ b/packages/eslint-plugin-next/src/rules/no-html-link-for-pages.ts @@ -107,8 +107,18 @@ export default defineRule({ return {} } - const pageUrls = cachedGetUrlFromPagesDirectories('/', foundPagesDirs) - const appDirUrls = cachedGetUrlFromAppDirectory('/', foundAppDirs) + // Get pageExtensions from settings, default to ['jsx', 'js', 'ts', 'tsx'] + const nextSettings: { pageExtensions?: string[] } = + context.settings.next || {} + const pageExtensions = nextSettings.pageExtensions || [ + 'jsx', + 'js', + 'ts', + 'tsx', + ] + + const pageUrls = cachedGetUrlFromPagesDirectories('/', foundPagesDirs, pageExtensions) + const appDirUrls = cachedGetUrlFromAppDirectory('/', foundAppDirs, pageExtensions) const allUrlRegex = [...pageUrls, ...appDirUrls] return { diff --git a/packages/eslint-plugin-next/src/utils/url.ts b/packages/eslint-plugin-next/src/utils/url.ts index c0d7c22bddc9..9cd44fb241c7 100644 --- a/packages/eslint-plugin-next/src/utils/url.ts +++ b/packages/eslint-plugin-next/src/utils/url.ts @@ -8,25 +8,31 @@ const fsReadDirSyncCache = {} /** * Recursively parse directory for page URLs. */ -function parseUrlForPages(urlprefix: string, directory: string) { +function parseUrlForPages( + urlprefix: string, + directory: string, + pageExtensions: string[] +) { fsReadDirSyncCache[directory] ??= fs.readdirSync(directory, { withFileTypes: true, }) const res = [] + // Build a regex from the configured pageExtensions + const extPattern = new RegExp(`(\\.(?:${pageExtensions.join('|')}))$`) + const indexPattern = new RegExp(`^index(\\.(?:${pageExtensions.join('|')}))$`) + fsReadDirSyncCache[directory].forEach((dirent) => { - // TODO: this should account for all page extensions - // not just js(x) and ts(x) - if (/(\.(j|t)sx?)$/.test(dirent.name)) { - if (/^index(\.(j|t)sx?)$/.test(dirent.name)) { + if (extPattern.test(dirent.name)) { + if (indexPattern.test(dirent.name)) { res.push( - `${urlprefix}${dirent.name.replace(/^index(\.(j|t)sx?)$/, '')}` + `${urlprefix}${dirent.name.replace(indexPattern, '')}` ) } - res.push(`${urlprefix}${dirent.name.replace(/(\.(j|t)sx?)$/, '')}`) + res.push(`${urlprefix}${dirent.name.replace(extPattern, '')}`) } else { const dirPath = path.join(directory, dirent.name) if (dirent.isDirectory() && !dirent.isSymbolicLink()) { - res.push(...parseUrlForPages(urlprefix + dirent.name + '/', dirPath)) + res.push(...parseUrlForPages(urlprefix + dirent.name + '/', dirPath, pageExtensions)) } } }) @@ -36,24 +42,31 @@ function parseUrlForPages(urlprefix: string, directory: string) { /** * Recursively parse app directory for URLs. */ -function parseUrlForAppDir(urlprefix: string, directory: string) { +function parseUrlForAppDir( + urlprefix: string, + directory: string, + pageExtensions: string[] +) { fsReadDirSyncCache[directory] ??= fs.readdirSync(directory, { withFileTypes: true, }) const res = [] + // Build a regex from the configured pageExtensions + const extPattern = new RegExp(`(\\.(?:${pageExtensions.join('|')}))$`) + const pagePattern = new RegExp(`^page(\\.(?:${pageExtensions.join('|')}))$`) + const layoutPattern = new RegExp(`^layout(\\.(?:${pageExtensions.join('|')}))$`) + fsReadDirSyncCache[directory].forEach((dirent) => { - // TODO: this should account for all page extensions - // not just js(x) and ts(x) - if (/(\.(j|t)sx?)$/.test(dirent.name)) { - if (/^page(\.(j|t)sx?)$/.test(dirent.name)) { - res.push(`${urlprefix}${dirent.name.replace(/^page(\.(j|t)sx?)$/, '')}`) - } else if (!/^layout(\.(j|t)sx?)$/.test(dirent.name)) { - res.push(`${urlprefix}${dirent.name.replace(/(\.(j|t)sx?)$/, '')}`) + if (extPattern.test(dirent.name)) { + if (pagePattern.test(dirent.name)) { + res.push(`${urlprefix}${dirent.name.replace(pagePattern, '')}`) + } else if (!layoutPattern.test(dirent.name)) { + res.push(`${urlprefix}${dirent.name.replace(extPattern, '')}`) } } else { const dirPath = path.join(directory, dirent.name) if (dirent.isDirectory(dirPath) && !dirent.isSymbolicLink()) { - res.push(...parseUrlForPages(urlprefix + dirent.name + '/', dirPath)) + res.push(...parseUrlForAppDir(urlprefix + dirent.name + '/', dirPath, pageExtensions)) } } }) @@ -136,13 +149,16 @@ export function normalizeAppPath(route: string) { */ export function getUrlFromPagesDirectories( urlPrefix: string, - directories: string[] + directories: string[], + pageExtensions: string[] = ['jsx', 'js', 'ts', 'tsx'] ) { return Array.from( // De-duplicate similar pages across multiple directories. new Set( directories - .flatMap((directory) => parseUrlForPages(urlPrefix, directory)) + .flatMap((directory) => + parseUrlForPages(urlPrefix, directory, pageExtensions) + ) .map( // Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly. (url) => `^${normalizeURL(url)}$` @@ -156,13 +172,14 @@ export function getUrlFromPagesDirectories( export function getUrlFromAppDirectory( urlPrefix: string, - directories: string[] + directories: string[], + pageExtensions: string[] = ['jsx', 'js', 'ts', 'tsx'] ) { return Array.from( // De-duplicate similar pages across multiple directories. new Set( directories - .map((directory) => parseUrlForAppDir(urlPrefix, directory)) + .map((directory) => parseUrlForAppDir(urlPrefix, directory, pageExtensions)) .flat() .map( // Since the URLs are normalized we add `^` and `$` to the RegExp to make sure they match exactly.