Skip to content

Commit

Permalink
feat(#43): add NEXT_JS_PAGE_ROUTER_FILENAME_CASE for rule filename-na…
Browse files Browse the repository at this point in the history
…ming-convention
  • Loading branch information
dukeluo committed Jan 29, 2025
1 parent 8ba26db commit 005037e
Show file tree
Hide file tree
Showing 9 changed files with 355 additions and 8 deletions.
4 changes: 3 additions & 1 deletion docs/rules/filename-naming-convention.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Allows you to enforce a consistent naming pattern for the filename of the specif

This rule aims to format the filename of the specified file. This rule uses the glob match syntax to match target files and declare the naming pattern for the filename.

There are six naming conventions built into this rule, including `CAMEL_CASE`, `PASCAL_CASE`, `SNAKE_CASE`, `KEBAB_CASE`, `SCREAMING_SNAKE_CASE` and `FLAT_CASE`.
There are six basic naming conventions built into this rule, including `CAMEL_CASE`, `PASCAL_CASE`, `SNAKE_CASE`, `KEBAB_CASE`, `SCREAMING_SNAKE_CASE` and `FLAT_CASE`.

| Formatting | Name |
| ----------- | ---------------------- |
Expand All @@ -17,6 +17,8 @@ There are six naming conventions built into this rule, including `CAMEL_CASE`, `
| HELLO_WORLD | `SCREAMING_SNAKE_CASE` |
| helloworld | `FLAT_CASE` |

And there is also a special naming convention for Next.js page router project, which is `NEXT_JS_PAGE_ROUTER_FILENAME_CASE`, you can use it to ensure the filename of the page router is consistent with the naming convention.

If the rule had been set as follows:

```js
Expand Down
7 changes: 6 additions & 1 deletion lib/constants/next-js-naming-convention.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/**
* @file Built in next js app router naming convention
* @file Built in next js naming convention
* @author Huan Luo
*/

Expand Down Expand Up @@ -44,3 +44,8 @@ const NEXT_JS_FILENAME_ROUTE = `+([a-z])?(.+([a-z]))`;
* @example app, [helpPageId], [...auth], [[...auth]], (auth), \@feed
*/
export const NEXT_JS_APP_ROUTER_CASE = `@(${KEBAB_CASE}|${NEXT_JS_DYNAMIC_SEGMENTS}|${NEXT_JS_CATCH_ALL_SEGMENTS}|${NEXT_JS_OPTIONAL_CATCH_ALL_SEGMENTS}|${NEXT_JS_ROUTE_GROUPS}|${NEXT_JS_NAMED_SLOTS}|${NEXT_JS_PRIVATE_FOLDERS}|${NEXT_JS_FILENAME_ROUTE})`;

/**
* @example _app, _document, index, [helpPageId], [...auth], [[...auth]]
*/
export const NEXT_JS_PAGE_ROUTER_FILENAME_CASE = `@(_app|_document|404|500|_error|index|${NEXT_JS_DYNAMIC_SEGMENTS}|${NEXT_JS_CATCH_ALL_SEGMENTS}|${NEXT_JS_OPTIONAL_CATCH_ALL_SEGMENTS})`;
5 changes: 4 additions & 1 deletion lib/rules/filename-naming-convention.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ import { isNotEmpty } from '../utils/utility.js';
import {
filenameNamingPatternValidator,
globPatternValidator,
nextJsFilenameNamingPatternValidator,
validateNamingPatternObject,
} from '../utils/validation.js';

Expand Down Expand Up @@ -64,7 +65,9 @@ export default {
const error = validateNamingPatternObject(
rules,
globPatternValidator,
filenameNamingPatternValidator
(p) =>
filenameNamingPatternValidator(p) ||
nextJsFilenameNamingPatternValidator(p)
);

if (error) {
Expand Down
5 changes: 4 additions & 1 deletion lib/utils/rule.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import micromatch from 'micromatch';
import * as NAMING_CONVENTION from '../constants/naming-convention.js';
import * as NEXT_JS_NAMING_CONVENTION from '../constants/next-js-naming-convention.js';
import { PREFINED_MATCH_SYNTAX_REGEXP } from '../constants/regex.js';
import { isEmpty, isNil } from './utility.js';

Expand Down Expand Up @@ -78,7 +79,9 @@ export const matchRule = (
targetNamingPattern &&
micromatch.isMatch(
targetNaming,
NAMING_CONVENTION[targetNamingPattern] || targetNamingPattern
NAMING_CONVENTION[targetNamingPattern] ||
NEXT_JS_NAMING_CONVENTION[targetNamingPattern] ||
targetNamingPattern
)
) {
return;
Expand Down
14 changes: 10 additions & 4 deletions lib/utils/validation.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@

import isGlob from 'is-glob';
import * as BASIC_NAMING_CONVENTION from '../constants/naming-convention.js';
import * as NEXT_JS_NAMING_CONVENTION from '../constants/next-js-naming-convention.js';
import { PREFINED_MATCH_SYNTAX_REGEXP } from '../constants/regex.js';
import { isObject } from './utility.js';

Expand Down Expand Up @@ -53,8 +52,15 @@ const basicNamingPatternValidator = (namingPattern) =>
* @returns {boolean} true if pattern is a valid naming pattern
* @param {string} namingPattern pattern string
*/
const nextJsNamingPatternValidator = (namingPattern) =>
Object.keys(NEXT_JS_NAMING_CONVENTION).includes(namingPattern);
const nextJsFolderNamingPatternValidator = (namingPattern) =>
['NEXT_JS_APP_ROUTER_CASE'].includes(namingPattern);

/**
* @returns {boolean} true if pattern is a valid naming pattern
* @param {string} namingPattern pattern string
*/
export const nextJsFilenameNamingPatternValidator = (namingPattern) =>
['NEXT_JS_PAGE_ROUTER_FILENAME_CASE'].includes(namingPattern);

/**
* @returns {boolean} true if pattern is a valid glob pattern
Expand All @@ -78,4 +84,4 @@ export const filenameNamingPatternValidator = (namingPattern) =>
export const folderNamingPatternValidator = (namingPattern) =>
globPatternValidator(namingPattern) ||
basicNamingPatternValidator(namingPattern) ||
nextJsNamingPatternValidator(namingPattern);
nextJsFolderNamingPatternValidator(namingPattern);
140 changes: 140 additions & 0 deletions tests/lib/rules/filename-naming-convention.posix.js
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,146 @@ ruleTester.run(
}
);

ruleTester.run(
"filename-naming-convention with option: [{ '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }]",
rule,
{
valid: [
{
code: "var foo = 'bar';",
filename: 'src/pages/_app.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/_document.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/_error.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/404.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/500.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/blog/index.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/blog/[post].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/blog/[blogPost].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/blog/[[...slug]].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/blog/[...params].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src/pages/blog/[category]/[post].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
],

invalid: [
{
code: "var foo = 'bar';",
filename: 'src/utils/CALCULATE_PRICE.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
errors: [
{
message:
'The filename "CALCULATE_PRICE.js" 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,
Expand Down
140 changes: 140 additions & 0 deletions tests/lib/rules/filename-naming-convention.windows.js
Original file line number Diff line number Diff line change
Expand Up @@ -676,6 +676,146 @@ ruleTester.run(
}
);

ruleTester.run(
"filename-naming-convention with option on Windows: [{ '**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE', '**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE' }]",
rule,
{
valid: [
{
code: "var foo = 'bar';",
filename: 'src\\pages\\_app.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\_document.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\_error.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\404.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\500.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\blog\\index.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\blog\\[post].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\blog\\[blogPost].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\blog\\[[...slug]].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\blog\\[...params].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
{
code: "var foo = 'bar';",
filename: 'src\\pages\\blog\\[category]\\[post].js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
},
],

invalid: [
{
code: "var foo = 'bar';",
filename: 'src\\utils\\CALCULATE_PRICE.js',
options: [
{
'**/*.js': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
'**/*.jsx': 'NEXT_JS_PAGE_ROUTER_FILENAME_CASE',
},
],
errors: [
{
message:
'The filename "CALCULATE_PRICE.js" 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,
Expand Down
Loading

0 comments on commit 005037e

Please sign in to comment.