eslint-plugin-fsd-paths-check 0.0.16 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const { allFsdLayers } = require("../constants/constants");
|
|
6
|
+
|
|
7
|
+
const calculateRelativePath = (fromFile, toFile) => {
|
|
8
|
+
const fromDir = path.dirname(fromFile);
|
|
9
|
+
let relative = path.relative(fromDir, toFile);
|
|
10
|
+
|
|
11
|
+
if (!relative.startsWith('.') && !relative.startsWith('/')) {
|
|
12
|
+
relative = `./${relative}`;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
relative = relative.replace(/\\/g, '/');
|
|
16
|
+
|
|
17
|
+
return relative;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const getFsdSlice = (filePath) => {
|
|
21
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
22
|
+
const segments = normalized.split('/');
|
|
23
|
+
const layerIndex = segments.findIndex(segment => allFsdLayers.includes(segment));
|
|
24
|
+
|
|
25
|
+
if (layerIndex === -1) return null;
|
|
26
|
+
if (layerIndex + 1 >= segments.length) return segments[layerIndex];
|
|
27
|
+
|
|
28
|
+
return `${segments[layerIndex]}/${segments[layerIndex + 1]}`;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
const isOnlyParentDirs = (importPath) => {
|
|
32
|
+
const normalized = importPath.replace(/\\/g, '/');
|
|
33
|
+
const segments = normalized.split('/').filter(s => s.length > 0);
|
|
34
|
+
return segments.every(s => s === '..');
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const resolveTargetDirectory = (importPath, currentFile) => {
|
|
38
|
+
const currentDir = path.dirname(currentFile);
|
|
39
|
+
const normalized = importPath.replace(/\\/g, '/');
|
|
40
|
+
const parentCount = normalized.split('/').filter(s => s === '..').length;
|
|
41
|
+
|
|
42
|
+
let targetDir = currentDir;
|
|
43
|
+
for (let i = 0; i < parentCount; i++) {
|
|
44
|
+
targetDir = path.dirname(targetDir);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return targetDir;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const findReExportInIndex = (indexFilePath, exportedName) => {
|
|
51
|
+
try {
|
|
52
|
+
if (!fs.existsSync(indexFilePath)) {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const content = fs.readFileSync(indexFilePath, 'utf-8');
|
|
57
|
+
|
|
58
|
+
const exportFromRegex = /export\s*\{\s*[^}]*?\b(\w+)\s*(?:as\s*\w+)?\s*\}\s*from\s*['"]([^'"]+)['"]/g;
|
|
59
|
+
|
|
60
|
+
let match;
|
|
61
|
+
while ((match = exportFromRegex.exec(content)) !== null) {
|
|
62
|
+
const [_, exportName, exportPath] = match;
|
|
63
|
+
if (exportName === exportedName) {
|
|
64
|
+
return exportPath;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const exportAllRegex = /export\s*\*\s*from\s*['"]([^'"]+)['"]/g;
|
|
69
|
+
while ((match = exportAllRegex.exec(content)) !== null) {
|
|
70
|
+
const [_, exportPath] = match;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return null;
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.warn(`Failed to read index.ts: ${error.message}`);
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const resolveActualFilePath = (basePath, relativeImport) => {
|
|
81
|
+
const resolved = path.resolve(basePath, relativeImport);
|
|
82
|
+
|
|
83
|
+
const extensions = ['', '.ts', '.tsx', '.js', '.jsx', '/index.ts', '/index.tsx'];
|
|
84
|
+
|
|
85
|
+
for (const ext of extensions) {
|
|
86
|
+
const fullPath = resolved + ext;
|
|
87
|
+
if (fs.existsSync(fullPath)) {
|
|
88
|
+
return fullPath;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return resolved;
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const isSameFsdModule = (file1, file2) => {
|
|
96
|
+
const slice1 = getFsdSlice(file1);
|
|
97
|
+
const slice2 = getFsdSlice(file2);
|
|
98
|
+
return slice1 !== null && slice1 === slice2;
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
module.exports = {
|
|
102
|
+
meta: {
|
|
103
|
+
type: `suggestion`,
|
|
104
|
+
docs: {
|
|
105
|
+
description: "Resolve public API imports to direct relative paths within same module",
|
|
106
|
+
recommended: false,
|
|
107
|
+
url: null,
|
|
108
|
+
},
|
|
109
|
+
fixable: `code`,
|
|
110
|
+
schema: [],
|
|
111
|
+
messages: {
|
|
112
|
+
resolvePublicApiImport: `Use direct relative import instead of public API within the same module`,
|
|
113
|
+
},
|
|
114
|
+
},
|
|
115
|
+
|
|
116
|
+
create(context) {
|
|
117
|
+
return {
|
|
118
|
+
ImportDeclaration(node) {
|
|
119
|
+
const importPath = node.source.value;
|
|
120
|
+
const currentFile = context.getFilename();
|
|
121
|
+
|
|
122
|
+
if (!isOnlyParentDirs(importPath)) {
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const targetDir = resolveTargetDirectory(importPath, currentFile);
|
|
127
|
+
const indexPath = path.join(targetDir, 'index.ts');
|
|
128
|
+
|
|
129
|
+
const importedNames = node.specifiers
|
|
130
|
+
.filter(spec => spec.type === 'ImportSpecifier')
|
|
131
|
+
.map(spec => spec.imported.name);
|
|
132
|
+
|
|
133
|
+
if (importedNames.length === 0) {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const reExportPath = findReExportInIndex(indexPath, importedNames[0]);
|
|
138
|
+
|
|
139
|
+
if (!reExportPath) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const actualFilePath = resolveActualFilePath(targetDir, reExportPath);
|
|
144
|
+
|
|
145
|
+
if (!isSameFsdModule(currentFile, actualFilePath)) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const directRelativePath = calculateRelativePath(currentFile, actualFilePath);
|
|
150
|
+
|
|
151
|
+
if (directRelativePath === importPath) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
context.report({
|
|
156
|
+
node,
|
|
157
|
+
messageId: `resolvePublicApiImport`,
|
|
158
|
+
fix(fixer) {
|
|
159
|
+
return fixer.replaceText(node.source, `'${directRelativePath}'`);
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
};
|
|
164
|
+
},
|
|
165
|
+
};
|