Skip to content

Commit aaa8a62

Browse files
committed
feat: implement rule folder-naming-convention
1 parent 3bf94c7 commit aaa8a62

File tree

3 files changed

+201
-14
lines changed

3 files changed

+201
-14
lines changed

lib/rules/folder-naming-convention.js

Lines changed: 72 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,15 @@
44
*/
55
'use strict';
66

7+
const micromatch = require('micromatch');
8+
const { getFolder, getSubPaths } = require('../utils/filename');
9+
const {
10+
checkSettings,
11+
namingPatternValidator,
12+
globPatternValidator,
13+
} = require('../utils/settings');
714
const { getDocUrl } = require('../utils/doc');
15+
const NAMING_CONVENTION = require('../constants/naming-convention');
816

917
/**
1018
* @type {import('eslint').Rule.RuleModule}
@@ -19,10 +27,71 @@ module.exports = {
1927
url: getDocUrl('folder-naming-convention'),
2028
},
2129
fixable: null,
22-
schema: [],
30+
schema: [
31+
{
32+
additionalProperties: {
33+
type: 'string',
34+
},
35+
},
36+
],
2337
},
2438

25-
create() {
26-
return {};
39+
create(context) {
40+
return {
41+
Program: (node) => {
42+
const rules = context.options[0];
43+
const invalidPattern = checkSettings(
44+
rules,
45+
globPatternValidator,
46+
namingPatternValidator
47+
);
48+
49+
if (invalidPattern) {
50+
context.report({
51+
node,
52+
message:
53+
'There is an invalid pattern "{{invalidPattern}}", please check it',
54+
data: {
55+
invalidPattern,
56+
},
57+
});
58+
return;
59+
}
60+
61+
const filenameWithPath = context.getFilename();
62+
const folder = getFolder(filenameWithPath);
63+
const subPaths = getSubPaths(folder);
64+
65+
for (const path of subPaths) {
66+
for (const [folderPattern, namingPattern] of Object.entries(rules)) {
67+
if (!micromatch.isMatch(path, folderPattern)) {
68+
continue;
69+
} else {
70+
const matchedValue = micromatch.capture(folderPattern, path);
71+
72+
for (const value of matchedValue) {
73+
if (
74+
!micromatch.isMatch(
75+
value,
76+
NAMING_CONVENTION[namingPattern] || namingPattern
77+
)
78+
) {
79+
context.report({
80+
node,
81+
message:
82+
'The folder "{{value}}" does not match the "{{namingPattern}}" style',
83+
data: {
84+
value,
85+
namingPattern,
86+
},
87+
});
88+
return;
89+
}
90+
}
91+
}
92+
}
93+
}
94+
},
95+
};
2796
},
2897
};

lib/utils/filename.js

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
const getFilename = (path) => path.substring(path.lastIndexOf('/') + 1);
1212

1313
/**
14-
* @type {string} folder
14+
* @type {string} path of folder
1515
* @param {string} path filename concat with path
1616
*/
1717
const getFolder = (path) => path.substring(0, path.lastIndexOf('/') + 1);
@@ -23,8 +23,32 @@ const getFolder = (path) => path.substring(0, path.lastIndexOf('/') + 1);
2323
const getBasename = (filename) =>
2424
filename.substring(0, filename.lastIndexOf('.'));
2525

26+
/**
27+
* @type {string[]} all sub paths
28+
* @param {string} folder path of folder
29+
*/
30+
const getSubPaths = (path) => {
31+
const folders = path.split('/').filter((folder) => folder !== '');
32+
let subPaths = [];
33+
34+
const handler = (array) =>
35+
array.reduce((acc, folder, index) => {
36+
if (folder) {
37+
acc.push(index === 0 ? folder : `${acc[acc.length - 1]}/${folder}`);
38+
}
39+
return acc;
40+
}, []);
41+
42+
for (let i = 0; i < folders.length; i++) {
43+
subPaths = subPaths.concat(handler(folders.slice(i)));
44+
}
45+
46+
return subPaths;
47+
};
48+
2649
module.exports = {
2750
getFolder,
2851
getFilename,
2952
getBasename,
53+
getSubPaths,
3054
};

tests/lib/rules/folder-naming-convention.js

Lines changed: 104 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,107 @@ const RuleTester = require('eslint').RuleTester;
99

1010
const ruleTester = new RuleTester();
1111

12-
ruleTester.run('folder-naming-convention', rule, {
13-
valid: [],
14-
15-
invalid: [
16-
{
17-
code: '',
18-
errors: [{ message: 'Fill me in.', type: 'Me too' }],
19-
},
20-
],
21-
});
12+
ruleTester.run(
13+
"folder-naming-convention with option: [{ '*/__tests__': 'PASCAL_CASE', 'src/*': 'CAMEL_CASE' }]",
14+
rule,
15+
{
16+
valid: [
17+
{
18+
code: "var foo = 'bar';",
19+
filename: 'src/components/DisplayLabel/__tests__/displayLabel.test.js',
20+
options: [{ '*/__tests__': 'PASCAL_CASE', 'src/*': 'CAMEL_CASE' }],
21+
},
22+
],
23+
24+
invalid: [
25+
{
26+
code: "var foo = 'bar';",
27+
filename: 'src/Components/DisplayLabel/__tests__/displayLabel.test.js',
28+
options: [{ '*/__tests__': 'PASCAL_CASE', 'src/*': 'CAMEL_CASE' }],
29+
errors: [
30+
{
31+
message:
32+
'The folder "Components" does not match the "CAMEL_CASE" style',
33+
column: 1,
34+
line: 1,
35+
},
36+
],
37+
},
38+
{
39+
code: "var foo = 'bar';",
40+
filename: 'src/components/displayLabel/__tests__/displayLabel.test.js',
41+
options: [{ '*/__tests__': 'PASCAL_CASE', 'src/*': 'CAMEL_CASE' }],
42+
errors: [
43+
{
44+
message:
45+
'The folder "displayLabel" does not match the "PASCAL_CASE" style',
46+
column: 1,
47+
line: 1,
48+
},
49+
],
50+
},
51+
],
52+
}
53+
);
54+
55+
ruleTester.run(
56+
'folder-naming-convention with folder that has not been set',
57+
rule,
58+
{
59+
valid: [
60+
{
61+
code: "var foo = 'bar';",
62+
filename: 'scripts/build.js',
63+
options: [{ '*/__tests__': 'PASCAL_CASE', 'src/*': 'CAMEL_CASE' }],
64+
},
65+
],
66+
67+
invalid: [],
68+
}
69+
);
70+
71+
ruleTester.run(
72+
"folder-naming-convention with option: [{ '*/__tests__': 'FOO', 'src/*': 'CAMEL_CASE' }]",
73+
rule,
74+
{
75+
valid: [],
76+
77+
invalid: [
78+
{
79+
code: "var foo = 'bar';",
80+
filename: 'src/utils/calculatePrice.js',
81+
options: [{ '*/__tests__': 'FOO', 'src/*': 'CAMEL_CASE' }],
82+
errors: [
83+
{
84+
message: 'There is an invalid pattern "FOO", please check it',
85+
column: 1,
86+
line: 1,
87+
},
88+
],
89+
},
90+
],
91+
}
92+
);
93+
94+
ruleTester.run(
95+
"filename-naming-convention with option: [{ '*/__tests__': 'PASCAL_CASE', 'src/': 'CAMEL_CASE' }]",
96+
rule,
97+
{
98+
valid: [],
99+
100+
invalid: [
101+
{
102+
code: "var foo = 'bar';",
103+
filename: 'src/utils/calculatePrice.js',
104+
options: [{ '*/__tests__': 'PASCAL_CASE', 'src/': 'CAMEL_CASE' }],
105+
errors: [
106+
{
107+
message: 'There is an invalid pattern "src/", please check it',
108+
column: 1,
109+
line: 1,
110+
},
111+
],
112+
},
113+
],
114+
}
115+
);

0 commit comments

Comments
 (0)