Skip to content

Commit 4062f27

Browse files
committed
feat: fsd layer 모듈 상호작용 규칙 작성
1 parent d8a0fa7 commit 4062f27

File tree

5 files changed

+93
-1
lines changed

5 files changed

+93
-1
lines changed

.eslintrc

+7-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"root": true,
33
"parser": "@typescript-eslint/parser",
4-
"plugins": ["@typescript-eslint", "react"],
4+
"plugins": ["@typescript-eslint", "react", "fsd-internal"],
55
"extends": [
66
"eslint:recommended",
77
"plugin:@typescript-eslint/recommended",
@@ -35,5 +35,11 @@
3535
"react/jsx-uses-react": "off",
3636
"react/no-unknown-property": "off",
3737
"@typescript-eslint/no-explicit-any": "off",
38+
"fsd-internal/layer-imports": [
39+
"error",
40+
{
41+
"alias": "~",
42+
},
43+
],
3844
},
3945
}

eslint/fsd/index.js

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
'use strict';
2+
3+
module.exports = {
4+
rules: {
5+
'layer-imports': require('./layer-imports'),
6+
},
7+
};

eslint/fsd/layer-imports.js

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
'use strict';
2+
3+
/**
4+
* Get the current file layer.
5+
*
6+
* @example
7+
* ```js
8+
* getCurrentFileLayer('src/shared/ui/button.tsx');
9+
* // => 'shared'
10+
*
11+
* getCurrentFileLayer('src/entities/theme/model/use-dark.ts');
12+
* // => 'entities'
13+
* ```
14+
*/
15+
function getCurrentFileLayer(currentFilePath) {
16+
const normalizedPath = currentFilePath.replace(/\\/g, '/');
17+
const projectPath = normalizedPath?.split('src')[1];
18+
const segments = projectPath?.split('/');
19+
20+
return segments?.[1];
21+
}
22+
23+
exports.create = function create(context) {
24+
const { alias = '' } = context.options[0] ?? {};
25+
26+
const layers = {
27+
background: ['background', 'widgets', 'features', 'entities', 'shared'],
28+
contentScripts: ['contentScripts', 'widgets', 'features', 'entities', 'shared'],
29+
options: ['options', 'widgets', 'features', 'entities', 'shared'],
30+
widgets: ['features', 'entities', 'shared'],
31+
features: ['entities', 'shared'],
32+
entities: ['entities', 'shared'],
33+
shared: ['shared'],
34+
};
35+
36+
const availableLayers = Object.keys(layers);
37+
38+
const getImportLayer = (value) => {
39+
const importPath = alias ? value.replace(new RegExp(`^${alias}/`), '') : value;
40+
const segments = importPath.split('/');
41+
42+
return segments.find((segment) => availableLayers.includes(segment));
43+
};
44+
45+
/**
46+
* @example
47+
* // src/shared/ui/button.tsx
48+
* import a from '../lib/env';
49+
* import a1 from '~/shared/lib/env';
50+
* import b from './label';
51+
* import b1 from '~/shared/ui/label';
52+
* import z from '../../entities/...'; // trigger error!
53+
* import z1 from '~/entities/...'; // trigger error!
54+
*/
55+
return {
56+
ImportDeclaration(node) {
57+
const { value } = node.source;
58+
const currentFileLayer = getCurrentFileLayer(context.getFilename());
59+
const importLayer = getImportLayer(value);
60+
61+
if (!importLayer) {
62+
return;
63+
}
64+
65+
const isAllowed = layers[currentFileLayer].includes(importLayer);
66+
67+
if (!isAllowed) {
68+
context.report({
69+
node,
70+
message: `Importing from "${importLayer}" is not allowed in the current layer "${currentFileLayer}".`,
71+
});
72+
}
73+
},
74+
};
75+
};

package.json

+1
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,7 @@
8686
"esbuild": "^0.20.2",
8787
"eslint": "^8.57.0",
8888
"eslint-config-prettier": "^9.1.0",
89+
"eslint-plugin-fsd-internal": "link:./eslint/fsd",
8990
"eslint-plugin-react": "^7.34.1",
9091
"eslint-plugin-react-hooks": "^4.6.0",
9192
"eslint-plugin-vitest": "^0.4.1",

pnpm-lock.yaml

+3
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)