From 46dd38b133e846ee57950c9fbdfe43c3d5e3d4d5 Mon Sep 17 00:00:00 2001 From: Drake Date: Sun, 5 Oct 2025 12:54:38 +0900 Subject: [PATCH 1/4] test: add Next.js dynamic routes support for filename-naming-convention rule on POSIX and Windows --- .../rules/filename-naming-convention.posix.js | 76 +++++++++++++++++++ .../filename-naming-convention.windows.js | 76 +++++++++++++++++++ 2 files changed, 152 insertions(+) diff --git a/tests/lib/rules/filename-naming-convention.posix.js b/tests/lib/rules/filename-naming-convention.posix.js index b9babe3..2ce1580 100644 --- a/tests/lib/rules/filename-naming-convention.posix.js +++ b/tests/lib/rules/filename-naming-convention.posix.js @@ -804,6 +804,82 @@ ruleTester.run( } ); +ruleTester.run( + "filename-naming-convention with Next.js dynamic routes and option: [{ '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, { ignoreMiddleExtensions: true }]", + rule, + { + valid: [ + { + code: "var foo = 'bar';", + filename: 'src/pages/blog/[[...userId]].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src/pages/blog/[[...slug]].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src/pages/blog/[...params].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src/pages/user/[id].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src/pages/post/[postId].test.tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src/pages/api/user.test.ts', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + ], + + invalid: [ + { + code: "var foo = 'bar';", + filename: 'src/pages/blog/Invalid_Name.tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + errors: [ + { + message: + 'The filename "Invalid_Name.tsx" does not match the "NEXT_JS_PAGE_ROUTER_FILENAME_CASE" pattern', + column: 1, + line: 1, + }, + ], + }, + ], + } +); + ruleTester.run( "filename-naming-convention with option: [{ '**/*.js': '__+([a-z])', '**/*.jsx': '__+([a-z])' }]", rule, diff --git a/tests/lib/rules/filename-naming-convention.windows.js b/tests/lib/rules/filename-naming-convention.windows.js index 524a4d2..8bde869 100644 --- a/tests/lib/rules/filename-naming-convention.windows.js +++ b/tests/lib/rules/filename-naming-convention.windows.js @@ -826,6 +826,82 @@ ruleTester.run( } ); +ruleTester.run( + "filename-naming-convention with Next.js dynamic routes and option on Windows: [{ '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, { ignoreMiddleExtensions: true }]", + rule, + { + valid: [ + { + code: "var foo = 'bar';", + filename: 'src\\pages\\blog\\[[...userId]].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src\\pages\\blog\\[[...slug]].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src\\pages\\blog\\[...params].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src\\pages\\user\\[id].tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src\\pages\\post\\[postId].test.tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + { + code: "var foo = 'bar';", + filename: 'src\\pages\\api\\user.test.ts', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, + ], + + invalid: [ + { + code: "var foo = 'bar';", + filename: 'src\\pages\\blog\\Invalid_Name.tsx', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + errors: [ + { + message: + 'The filename "Invalid_Name.tsx" does not match the "NEXT_JS_PAGE_ROUTER_FILENAME_CASE" pattern', + column: 1, + line: 1, + }, + ], + }, + ], + } +); + ruleTester.run( "filename-naming-convention with option on Windows: [{ '**/*.js': 'CAMEL_CASE' }, { ignoreMiddleExtensions: true }]", rule, From 7c83ccf865273a5f8c18def0d85a383ed6886623 Mon Sep 17 00:00:00 2001 From: Drake Date: Sun, 5 Oct 2025 12:56:04 +0900 Subject: [PATCH 2/4] fix: ignore dots inside brackets when extracting basename Previously, getBasename would treat dots inside brackets as extension separators. This commit adds logic to skip dots that appear within bracket pairs, ensuring they are treated as part of the filename rather than extension delimiters. --- lib/utils/filename.js | 32 +++++++++++++++++++++++++++----- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/lib/utils/filename.js b/lib/utils/filename.js index 906820a..45f7164 100644 --- a/lib/utils/filename.js +++ b/lib/utils/filename.js @@ -24,11 +24,33 @@ export const getFolderPath = (p) => posix.join(posix.dirname(p), posix.sep); * @param {string} filename filename without path * @param {boolean} [ignoreMiddleExtensions] flag to ignore middle extensions */ -export const getBasename = (filename, ignoreMiddleExtensions = false) => - filename.substring( - 0, - ignoreMiddleExtensions ? filename.indexOf('.') : filename.lastIndexOf('.') - ); +export const getBasename = (filename, ignoreMiddleExtensions = false) => { + const findDotsOutsideBrackets = (str) => { + const positions = []; + let depth = 0; + + for (let i = 0; i < str.length; i++) { + if (str[i] === '[') depth++; + else if (str[i] === ']') depth--; + else if (str[i] === '.' && depth === 0) { + positions.push(i); + } + } + return positions; + }; + + const dotPositions = findDotsOutsideBrackets(filename); + + if (dotPositions.length === 0) { + return filename; + } + + const curPosition = ignoreMiddleExtensions + ? dotPositions[0] + : dotPositions[dotPositions.length - 1]; + + return filename.substring(0, curPosition); +}; /** * @returns {string[]} all folders From 5f2bdf3507da37818a10e6db4ab48c57a228412f Mon Sep 17 00:00:00 2001 From: Drake Date: Tue, 14 Oct 2025 22:43:37 +0900 Subject: [PATCH 3/4] test: add tests for POSIX and Windows --- tests/lib/rules/filename-naming-convention.posix.js | 8 ++++++++ tests/lib/rules/filename-naming-convention.windows.js | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/tests/lib/rules/filename-naming-convention.posix.js b/tests/lib/rules/filename-naming-convention.posix.js index 2ce1580..8f22ded 100644 --- a/tests/lib/rules/filename-naming-convention.posix.js +++ b/tests/lib/rules/filename-naming-convention.posix.js @@ -833,6 +833,14 @@ ruleTester.run( { ignoreMiddleExtensions: true }, ], }, + { + code: "var foo = 'bar';", + filename: 'src/pages/blog/params', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, { code: "var foo = 'bar';", filename: 'src/pages/user/[id].tsx', diff --git a/tests/lib/rules/filename-naming-convention.windows.js b/tests/lib/rules/filename-naming-convention.windows.js index 8bde869..cc88e5f 100644 --- a/tests/lib/rules/filename-naming-convention.windows.js +++ b/tests/lib/rules/filename-naming-convention.windows.js @@ -855,6 +855,14 @@ ruleTester.run( { ignoreMiddleExtensions: true }, ], }, + { + code: "var foo = 'bar';", + filename: 'src\\pages\\blog\\params', + options: [ + { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, + { ignoreMiddleExtensions: true }, + ], + }, { code: "var foo = 'bar';", filename: 'src\\pages\\user\\[id].tsx', From a1582b1512bda4f261d8443ea3e7520da289cb72 Mon Sep 17 00:00:00 2001 From: Drake Date: Wed, 15 Oct 2025 10:21:35 +0900 Subject: [PATCH 4/4] refactor: remove redundant guard in getBasename --- lib/utils/filename.js | 4 ---- tests/lib/rules/filename-naming-convention.posix.js | 8 -------- tests/lib/rules/filename-naming-convention.windows.js | 8 -------- 3 files changed, 20 deletions(-) diff --git a/lib/utils/filename.js b/lib/utils/filename.js index 45f7164..49d5c47 100644 --- a/lib/utils/filename.js +++ b/lib/utils/filename.js @@ -41,10 +41,6 @@ export const getBasename = (filename, ignoreMiddleExtensions = false) => { const dotPositions = findDotsOutsideBrackets(filename); - if (dotPositions.length === 0) { - return filename; - } - const curPosition = ignoreMiddleExtensions ? dotPositions[0] : dotPositions[dotPositions.length - 1]; diff --git a/tests/lib/rules/filename-naming-convention.posix.js b/tests/lib/rules/filename-naming-convention.posix.js index 8f22ded..2ce1580 100644 --- a/tests/lib/rules/filename-naming-convention.posix.js +++ b/tests/lib/rules/filename-naming-convention.posix.js @@ -833,14 +833,6 @@ ruleTester.run( { ignoreMiddleExtensions: true }, ], }, - { - code: "var foo = 'bar';", - filename: 'src/pages/blog/params', - options: [ - { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, - { ignoreMiddleExtensions: true }, - ], - }, { code: "var foo = 'bar';", filename: 'src/pages/user/[id].tsx', diff --git a/tests/lib/rules/filename-naming-convention.windows.js b/tests/lib/rules/filename-naming-convention.windows.js index cc88e5f..8bde869 100644 --- a/tests/lib/rules/filename-naming-convention.windows.js +++ b/tests/lib/rules/filename-naming-convention.windows.js @@ -855,14 +855,6 @@ ruleTester.run( { ignoreMiddleExtensions: true }, ], }, - { - code: "var foo = 'bar';", - filename: 'src\\pages\\blog\\params', - options: [ - { '**/*.{js,jsx,ts,tsx}': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }, - { ignoreMiddleExtensions: true }, - ], - }, { code: "var foo = 'bar';", filename: 'src\\pages\\user\\[id].tsx',