Path: blob/main/src/rules/no-useless-path-segments.js
829 views
/**1* @fileOverview Ensures that there are no useless path segments2* @author Thomas Grainger3*/45import { getFileExtensions } from 'eslint-module-utils/ignore';6import moduleVisitor from 'eslint-module-utils/moduleVisitor';7import resolve from 'eslint-module-utils/resolve';8import path from 'path';9import docsUrl from '../docsUrl';1011/**12* convert a potentially relative path from node utils into a true13* relative path.14*15* ../ -> ..16* ./ -> .17* .foo/bar -> ./.foo/bar18* ..foo/bar -> ./..foo/bar19* foo/bar -> ./foo/bar20*21* @param relativePath {string} relative posix path potentially missing leading './'22* @returns {string} relative posix path that always starts with a ./23**/24function toRelativePath(relativePath) {25const stripped = relativePath.replace(/\/$/g, ''); // Remove trailing /2627return /^((\.\.)|(\.))($|\/)/.test(stripped) ? stripped : `./${stripped}`;28}2930function normalize(fn) {31return toRelativePath(path.posix.normalize(fn));32}3334function countRelativeParents(pathSegments) {35return pathSegments.reduce((sum, pathSegment) => pathSegment === '..' ? sum + 1 : sum, 0);36}3738module.exports = {39meta: {40type: 'suggestion',41docs: {42url: docsUrl('no-useless-path-segments'),43},4445fixable: 'code',4647schema: [48{49type: 'object',50properties: {51commonjs: { type: 'boolean' },52noUselessIndex: { type: 'boolean' },53},54additionalProperties: false,55},56],57},5859create(context) {60const currentDir = path.dirname(context.getPhysicalFilename ? context.getPhysicalFilename() : context.getFilename());61const options = context.options[0];6263function checkSourceValue(source) {64const { value: importPath } = source;6566function reportWithProposedPath(proposedPath) {67context.report({68node: source,69// Note: Using messageIds is not possible due to the support for ESLint 2 and 370message: `Useless path segments for "${importPath}", should be "${proposedPath}"`,71fix: fixer => proposedPath && fixer.replaceText(source, JSON.stringify(proposedPath)),72});73}7475// Only relative imports are relevant for this rule --> Skip checking76if (!importPath.startsWith('.')) {77return;78}7980// Report rule violation if path is not the shortest possible81const resolvedPath = resolve(importPath, context);82const normedPath = normalize(importPath);83const resolvedNormedPath = resolve(normedPath, context);84if (normedPath !== importPath && resolvedPath === resolvedNormedPath) {85return reportWithProposedPath(normedPath);86}8788const fileExtensions = getFileExtensions(context.settings);89const regexUnnecessaryIndex = new RegExp(90`.*\\/index(\\${Array.from(fileExtensions).join('|\\')})?$`,91);9293// Check if path contains unnecessary index (including a configured extension)94if (options && options.noUselessIndex && regexUnnecessaryIndex.test(importPath)) {95const parentDirectory = path.dirname(importPath);9697// Try to find ambiguous imports98if (parentDirectory !== '.' && parentDirectory !== '..') {99for (const fileExtension of fileExtensions) {100if (resolve(`${parentDirectory}${fileExtension}`, context)) {101return reportWithProposedPath(`${parentDirectory}/`);102}103}104}105106return reportWithProposedPath(parentDirectory);107}108109// Path is shortest possible + starts from the current directory --> Return directly110if (importPath.startsWith('./')) {111return;112}113114// Path is not existing --> Return directly (following code requires path to be defined)115if (resolvedPath === undefined) {116return;117}118119const expected = path.relative(currentDir, resolvedPath); // Expected import path120const expectedSplit = expected.split(path.sep); // Split by / or \ (depending on OS)121const importPathSplit = importPath.replace(/^\.\//, '').split('/');122const countImportPathRelativeParents = countRelativeParents(importPathSplit);123const countExpectedRelativeParents = countRelativeParents(expectedSplit);124const diff = countImportPathRelativeParents - countExpectedRelativeParents;125126// Same number of relative parents --> Paths are the same --> Return directly127if (diff <= 0) {128return;129}130131// Report and propose minimal number of required relative parents132return reportWithProposedPath(133toRelativePath(134importPathSplit135.slice(0, countExpectedRelativeParents)136.concat(importPathSplit.slice(countImportPathRelativeParents + diff))137.join('/'),138),139);140}141142return moduleVisitor(checkSourceValue, options);143},144};145146147