eslint-plugin-paths
Version:
A plugin for ESLint, to force use paths aliases from tsconfig
143 lines (137 loc) • 5.46 kB
JavaScript
var fs = require('fs');
var path = require('path');
var commentJson = require('comment-json');
function getCompilerConfigFromFile(baseDir, configFilePath) {
if (!configFilePath) {
// Looking for a config file
for (const filename of ['tsconfig.json', 'jsconfig.json']) {
const resolvedPath = path.resolve(path.join(baseDir, filename));
const isFileExists = fs.existsSync(resolvedPath);
if (isFileExists) {
configFilePath = resolvedPath;
break;
}
}
if (!configFilePath)
return null;
}
const tsconfig = commentJson.parse(fs.readFileSync(path.resolve(configFilePath)).toString('utf8'));
// TODO: validate options
const { baseUrl, paths = {} } = tsconfig?.compilerOptions ?? {};
return {
baseUrl,
paths,
};
}
function findDirWithFile(filename) {
let dir = path.resolve(filename);
do {
dir = path.dirname(dir);
} while (!fs.existsSync(path.join(dir, filename)) && dir !== '/');
if (!fs.existsSync(path.join(dir, filename))) {
return null;
}
return dir;
}
function findAlias(compilerOptions, baseDir, importPath, filePath, ignoredPaths = []) {
for (const [alias, aliasPaths] of Object.entries(compilerOptions.paths)) {
// TODO: support full featured glob patterns instead of trivial cases like `@utils/*` and `src/utils/*`
const matchedPath = aliasPaths.find((dirPath) => {
// Remove last asterisk
const dirPathBase = path
.join(baseDir, dirPath)
.split('/')
.slice(0, -1)
.join('/');
if (filePath.startsWith(dirPathBase))
return false;
if (ignoredPaths.some((ignoredPath) => ignoredPath.startsWith(dirPathBase)))
return false;
return importPath.startsWith(dirPathBase);
});
if (!matchedPath)
continue;
// Split import path
// Remove basedir and slash in start
const slicedImportPath = importPath
.slice(baseDir.length + 1)
.slice(path.dirname(path.normalize(matchedPath)).length + 1);
// Remove asterisk from end of alias
const replacedPathSegments = path
.join(path.dirname(alias), slicedImportPath)
.split('/');
// Add index in path
return (replacedPathSegments.length === 1
? [...replacedPathSegments, 'index']
: replacedPathSegments).join('/');
}
return null;
}
// TODO: implement option to force relative path instead of alias (for remove alias case)
// TODO: add tests
const rule = {
meta: {
fixable: 'code',
schema: {
type: 'array',
minItems: 0,
maxItems: 1,
items: [
{
type: 'object',
properties: {
configFilePath: { type: 'string' },
ignoredPaths: { type: 'array', items: { type: 'string' } },
},
additionalProperties: false,
},
],
},
},
create(context) {
const baseDir = findDirWithFile('package.json');
if (!baseDir)
throw new Error("Can't find base dir");
const [{ ignoredPaths = [], configFilePath = null } = {}] = context.options;
const compilerOptions = getCompilerConfigFromFile(baseDir, configFilePath ?? undefined);
if (!compilerOptions)
throw new Error('Compiler options did not found');
const pathTrailers = ['.', '/', '~'];
return {
ImportDeclaration(node) {
const importPath = node.source.value;
if (typeof importPath !== 'string')
return;
const isPathInImport = pathTrailers.some((pathTrailer) => importPath.startsWith(pathTrailer));
if (!isPathInImport)
return;
const filename = context.filename;
const resolvedIgnoredPaths = ignoredPaths.map((ignoredPath) => path.normalize(path.join(path.dirname(filename), ignoredPath)));
const absolutePath = path.normalize(path.resolve(importPath.startsWith('.')
? path.join(path.dirname(filename), importPath)
: importPath));
const replacement = findAlias(compilerOptions, baseDir, absolutePath, filename, resolvedIgnoredPaths);
if (!replacement)
return;
context.report({
node,
message: `Update import to ${replacement}`,
fix(fixer) {
const acceptableQuoteSymbols = [`'`, `"`];
const originalStringQuote = node.source.raw?.slice(0, 1);
const quote = originalStringQuote &&
acceptableQuoteSymbols.includes(originalStringQuote)
? originalStringQuote
: acceptableQuoteSymbols[0];
return fixer.replaceText(node.source, quote + replacement + quote);
},
});
},
};
},
};
const rules = {
alias: rule,
};
exports.rules = rules;
;