Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
epidemian
GitHub Repository: epidemian/eslint-plugin-import
Path: blob/main/src/rules/no-useless-path-segments.js
829 views
1
/**
2
* @fileOverview Ensures that there are no useless path segments
3
* @author Thomas Grainger
4
*/
5
6
import { getFileExtensions } from 'eslint-module-utils/ignore';
7
import moduleVisitor from 'eslint-module-utils/moduleVisitor';
8
import resolve from 'eslint-module-utils/resolve';
9
import path from 'path';
10
import docsUrl from '../docsUrl';
11
12
/**
13
* convert a potentially relative path from node utils into a true
14
* relative path.
15
*
16
* ../ -> ..
17
* ./ -> .
18
* .foo/bar -> ./.foo/bar
19
* ..foo/bar -> ./..foo/bar
20
* foo/bar -> ./foo/bar
21
*
22
* @param relativePath {string} relative posix path potentially missing leading './'
23
* @returns {string} relative posix path that always starts with a ./
24
**/
25
function toRelativePath(relativePath) {
26
const stripped = relativePath.replace(/\/$/g, ''); // Remove trailing /
27
28
return /^((\.\.)|(\.))($|\/)/.test(stripped) ? stripped : `./${stripped}`;
29
}
30
31
function normalize(fn) {
32
return toRelativePath(path.posix.normalize(fn));
33
}
34
35
function countRelativeParents(pathSegments) {
36
return pathSegments.reduce((sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0);
37
}
38
39
module.exports = {
40
meta: {
41
type: 'suggestion',
42
docs: {
43
url: docsUrl('no-useless-path-segments'),
44
},
45
46
fixable: 'code',
47
48
schema: [
49
{
50
type: 'object',
51
properties: {
52
commonjs: { type: 'boolean' },
53
noUselessIndex: { type: 'boolean' },
54
},
55
additionalProperties: false,
56
},
57
],
58
},
59
60
create(context) {
61
const currentDir = path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());
62
const options = context.options[0];
63
64
function checkSourceValue(source) {
65
const { value: importPath } = source;
66
67
function reportWithProposedPath(proposedPath) {
68
context.report({
69
node: source,
70
// Note: Using messageIds is not possible due to the support for ESLint 2 and 3
71
message: `Useless path segments for "${importPath}", should be "${proposedPath}"`,
72
fix: fixer => proposedPath && fixer.replaceText(source, JSON.stringify(proposedPath)),
73
});
74
}
75
76
// Only relative imports are relevant for this rule --> Skip checking
77
if (!importPath.startsWith('.')) {
78
return;
79
}
80
81
// Report rule violation if path is not the shortest possible
82
const resolvedPath = resolve(importPath, context);
83
const normedPath = normalize(importPath);
84
const resolvedNormedPath = resolve(normedPath, context);
85
if (normedPath !== importPath && resolvedPath === resolvedNormedPath) {
86
return reportWithProposedPath(normedPath);
87
}
88
89
const fileExtensions = getFileExtensions(context.settings);
90
const regexUnnecessaryIndex = new RegExp(
91
`.*\\/index(\\${Array.from(fileExtensions).join('|\\')})?$`,
92
);
93
94
// Check if path contains unnecessary index (including a configured extension)
95
if (options && options.noUselessIndex && regexUnnecessaryIndex.test(importPath)) {
96
const parentDirectory = path.dirname(importPath);
97
98
// Try to find ambiguous imports
99
if (parentDirectory !== '.' && parentDirectory !== '..') {
100
for (const fileExtension of fileExtensions) {
101
if (resolve(`${parentDirectory}${fileExtension}`, context)) {
102
return reportWithProposedPath(`${parentDirectory}/`);
103
}
104
}
105
}
106
107
return reportWithProposedPath(parentDirectory);
108
}
109
110
// Path is shortest possible + starts from the current directory --> Return directly
111
if (importPath.startsWith('./')) {
112
return;
113
}
114
115
// Path is not existing --> Return directly (following code requires path to be defined)
116
if (resolvedPath === undefined) {
117
return;
118
}
119
120
const expected = path.relative(currentDir, resolvedPath); // Expected import path
121
const expectedSplit = expected.split(path.sep); // Split by / or \ (depending on OS)
122
const importPathSplit = importPath.replace(/^\.\//, '').split('/');
123
const countImportPathRelativeParents = countRelativeParents(importPathSplit);
124
const countExpectedRelativeParents = countRelativeParents(expectedSplit);
125
const diff = countImportPathRelativeParents - countExpectedRelativeParents;
126
127
// Same number of relative parents --> Paths are the same --> Return directly
128
if (diff <= 0) {
129
return;
130
}
131
132
// Report and propose minimal number of required relative parents
133
return reportWithProposedPath(
134
toRelativePath(
135
importPathSplit
136
.slice(0, countExpectedRelativeParents)
137
.concat(importPathSplit.slice(countImportPathRelativeParents + diff))
138
.join('/'),
139
),
140
);
141
}
142
143
return moduleVisitor(checkSourceValue, options);
144
},
145
};
146
147