eslint-plugin-canonical
Version:
Canonical linting rules for ESLint.
119 lines (118 loc) • 5.09 kB
JavaScript
;
/* eslint-disable unicorn/no-array-reduce */
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
/**
* @author Steven Sojka
* @see https://github.com/steelsojka/eslint-import-alias
*/
const node_path_1 = __importDefault(require("node:path"));
const posix_1 = require("node:path/posix");
const win32_1 = require("node:path/win32");
const utilities_1 = require("../utilities");
const RELATIVE_MATCHER = /^(?:(\.\/)|(\.\.\/))+/u;
const CWD = process.cwd();
exports.default = (0, utilities_1.createRule)({
create: (context, [options]) => {
var _a;
const { aliases: temporaryAliases = [], baseDirectory = CWD } = options;
const aliases = temporaryAliases.map((temporaryAlias) => {
return Object.assign(Object.assign({}, temporaryAlias), { matchPath: new RegExp(temporaryAlias.matchPath, 'u') });
});
const filename = (_a = context.filename) !== null && _a !== void 0 ? _a : context.getFilename();
return {
ImportDeclaration(node) {
var _a;
const importValue = node.source.value;
const accessor = (_a = importValue.match(RELATIVE_MATCHER)) === null || _a === void 0 ? void 0 : _a[0];
if (!accessor) {
return;
}
const parsedPath = node_path_1.default.parse(filename);
// e.g. grandparentAccessor => '../../' => 2
const depth = accessor === './' ? 0 : accessor.length / 3;
const parentPath = node_path_1.default.resolve(baseDirectory, node_path_1.default.resolve(parsedPath.dir, '../'.repeat(depth)));
const importPath = node_path_1.default
.relative(baseDirectory, node_path_1.default.resolve(parsedPath.dir, importValue))
.replaceAll(win32_1.sep, posix_1.sep);
for (const item of aliases) {
const { alias, matchPath, matchParent, maxRelativeDepth = -1 } = item;
if (maxRelativeDepth < -1) {
throw new Error('maxRelativeDepth cannot be less than -1');
}
if (depth < maxRelativeDepth) {
continue;
}
if (matchParent && matchParent !== parentPath) {
continue;
}
const pathMatch = importPath.match(matchPath);
if (!pathMatch) {
continue;
}
context.report({
data: {
maxRelativeDepth,
},
fix: (fixer) => {
const matchingString = pathMatch[pathMatch.length - 1];
const index = pathMatch[0].indexOf(matchingString);
const newImportPath = importPath.slice(0, index) +
alias +
importPath.slice(index + matchingString.length);
return fixer.replaceTextRange([node.source.range[0] + 1, node.source.range[1] - 1], newImportPath);
},
messageId: maxRelativeDepth === -1 ? 'mustBeAlias' : 'mustBeAliasOrShallow',
node,
});
break;
}
},
};
},
defaultOptions: [
{
aliases: [],
baseDirectory: process.cwd(),
},
],
meta: {
docs: {
description: 'Restrict imports to path aliases or relative imports limited by depth.',
},
fixable: 'code',
messages: {
mustBeAlias: 'Import path mush be a path alias',
mustBeAliasOrShallow: 'Import statement must be an alias or no more than {{maxRelativeDepth}} levels deep',
},
schema: [
{
properties: {
aliases: {
items: {
properties: {
alias: { type: 'string' },
matchParent: { type: 'string' },
matchPath: { type: 'string' },
maxRelativeDepth: {
type: 'number',
},
},
required: ['alias', 'matchPath'],
type: 'object',
},
type: 'array',
},
cwd: {
type: 'string',
},
},
type: 'object',
},
],
type: 'problem',
},
name: 'prefer-import-alias',
});