eslint-plugin-path
Advanced tools
Comparing version 0.1.0-rc.3 to 1.0.0-rc.4
"use strict"; | ||
const { | ||
getImport, | ||
getBaseDirs, | ||
isRelativeToParent, | ||
isExternalPath, | ||
} = require("../utils"); | ||
const { getImport } = require("../utils"); | ||
const { isRelativeToParent, isExternalPath } = require("../utils/import-types"); | ||
const { relative } = require("path"); | ||
/** | ||
* @typedef {import("../utils/tsconfig").AliasItem} AliasItem | ||
*/ | ||
const { getTSconfigSettings } = require("../utils/tsconfig"); | ||
function findAbsolutePath(target, baseDirs = []) { | ||
if (!target || !baseDirs) { | ||
/** | ||
* Creates an absolute path to target using an array of alias items | ||
* @param {string} target | ||
* @param {Array<AliasItem>} aliases | ||
* @returns {string|null} - absolute path import | ||
*/ | ||
function getAbsolutePathToTarget(target, aliases = []) { | ||
if (!target || !aliases) { | ||
return null; | ||
} | ||
return baseDirs | ||
const absolutePath = aliases | ||
.map(({ path, alias }) => `${alias || ""}${relative(path, target)}`) | ||
.find((path) => !isRelativeToParent(path)); | ||
return absolutePath || null; | ||
} | ||
function getSegmentCount(path) { | ||
/** | ||
* Calculate slash counts | ||
* @param {string} path | ||
* @returns {number} - number of slashes | ||
*/ | ||
function getSlashCounts(path) { | ||
if (!path) { | ||
@@ -29,2 +41,8 @@ return 0; | ||
/** | ||
* Checks if the max path depth has been exceeded | ||
* @param {string} current | ||
* @param {RuleSettings} settings | ||
* @returns | ||
*/ | ||
function isMaxDepthExceeded(current, settings) { | ||
@@ -39,2 +57,9 @@ if (!current) { | ||
/** | ||
* | ||
* @param {string} current | ||
* @param {string} expected | ||
* @param {RuleSettings} settings | ||
* @returns | ||
*/ | ||
function isWrongRelativeImport(current, expected, settings) { | ||
@@ -55,3 +80,3 @@ if ( | ||
return settings.suggested | ||
? getSegmentCount(current) > getSegmentCount(expected) | ||
? getSlashCounts(current) > getSlashCounts(expected) | ||
: false; | ||
@@ -91,10 +116,16 @@ } | ||
/** | ||
* @typedef {Object} RuleSettings | ||
* @property {number} maxDepth | ||
* @property {boolean} suggested | ||
*/ | ||
module.exports.create = (context) => { | ||
const baseDirs = getBaseDirs(); | ||
const filename = context.getFilename(); | ||
const { maxDepth = 2, suggested = false } = context.options[0] || {}; | ||
/** @type {RuleSettings} */ | ||
const settings = { maxDepth, suggested }; | ||
const tsconfigSettings = getTSconfigSettings(); | ||
return getImport(filename, ({ node, start, value: current, end, path }) => { | ||
const expected = findAbsolutePath(path, baseDirs); | ||
const expected = getAbsolutePathToTarget(path, tsconfigSettings); | ||
@@ -101,0 +132,0 @@ if (isWrongRelativeImport(current, expected, settings)) { |
"use strict"; | ||
const { dirname, join, normalize, resolve, relative } = require("path"); | ||
const { existsSync, readFileSync } = require("fs"); | ||
const { dirname, join, normalize } = require("path"); | ||
const FILES = { | ||
package: "package.json", | ||
tsconfig: "tsconfig.json", | ||
}; | ||
/** | ||
* @typedef {Object} ImportHandlerParams | ||
* @property {any} node - context node from eslint | ||
* @property {string} value - import value | ||
* @property {string} path - path to import value | ||
* @property {number} start - star of line where import is declared | ||
* @property {number} end - end of line where import is declared | ||
*/ | ||
function hasKey(map, path) { | ||
let inner = map; | ||
for (let step of path.split(".")) { | ||
inner = inner[step]; | ||
if (inner === undefined) { | ||
return false; | ||
} | ||
} | ||
return true; | ||
} | ||
function getContextPackagePath() { | ||
let dir = resolve(FILES.package); | ||
do { | ||
dir = dirname(dir); | ||
} while (!existsSync(join(dir, FILES.package)) && dir !== "/"); | ||
if (!existsSync(join(dir, FILES.package))) { | ||
return; | ||
} | ||
return dir; | ||
} | ||
function isRelativeToParent(name) { | ||
return /^\.\.$|^\.\.[\\/]/.test(name); | ||
} | ||
function loadFile(baseDir, filename) { | ||
return JSON.parse(readFileSync(join(baseDir, filename), "utf-8")); | ||
} | ||
function isFileExists(baseDir, filename) { | ||
return existsSync(join(baseDir, filename)); | ||
} | ||
function isExternalPath(path) { | ||
if (!path) { | ||
return false; | ||
} | ||
const packagePath = getContextPackagePath(); | ||
if (relative(packagePath, path).startsWith("..")) { | ||
return true; | ||
} | ||
const folders = ["node_modules"]; | ||
return folders.some((folder) => { | ||
const folderPath = resolve(packagePath, folder); | ||
const relativePath = relative(folderPath, path); | ||
return !relativePath.startsWith(".."); | ||
}); | ||
} | ||
function getTSconfigDirs() { | ||
const rootDir = getContextPackagePath(); | ||
const urls = []; | ||
if (isFileExists(rootDir, FILES.tsconfig)) { | ||
const config = loadFile(rootDir, FILES.tsconfig); | ||
if (hasKey(config, "compilerOptions.paths")) { | ||
Object.entries(config.compilerOptions.paths).forEach(([key, items]) => | ||
items.forEach((item) => | ||
urls.push({ | ||
path: item.replace("*", ""), | ||
alias: key.replace("*", ""), | ||
}) | ||
) | ||
); | ||
} | ||
if (hasKey(config, "include")) { | ||
config.include.forEach((item) => urls.push({ path: item, alias: null })); | ||
} | ||
if (hasKey(config, "compilerOptions.baseUrl")) { | ||
urls.push({ | ||
path: config.compilerOptions.baseUrl, | ||
alias: null, | ||
}); | ||
} | ||
} | ||
return urls.map((item) => ({ | ||
...item, | ||
path: join(rootDir, item.path), | ||
})); | ||
} | ||
const configureParams = (filename, node, { value, range }) => { | ||
/** | ||
* Params configurator | ||
* @param {string} filename | ||
* @param {any} node | ||
* @param {{value: string; range: [number, number]}} source | ||
* @returns {ImportHandlerParams} | ||
*/ | ||
function configureParams(filename, node, { value, range }) { | ||
const path = normalize(join(dirname(filename), value)); | ||
const [start, end] = range; | ||
const [start, end] = range || []; | ||
return { node, value, path, start, end }; | ||
}; | ||
} | ||
const getImport = (filename, callback) => ({ | ||
ImportDeclaration: (node) => { | ||
callback(configureParams(filename, node, node.source)); | ||
}, | ||
CallExpression: (node) => { | ||
if ( | ||
node.arguments.length > 0 && | ||
node.arguments[0].type === "Literal" && | ||
(node.callee.type === "Import" || | ||
(node.callee.type === "Identifier" && node.callee.name === "require")) | ||
) { | ||
callback(configureParams(filename, node, node.arguments[0])); | ||
} | ||
}, | ||
ImportExpression: (node) => { | ||
if (node.source.type === "Literal") { | ||
/** | ||
* ESLint rule handler | ||
* @param {string} filename | ||
* @param {(params: ImportHandlerParams) => void} callback | ||
* @returns {any} eslint rule handler | ||
*/ | ||
function getImport(filename, callback) { | ||
return { | ||
ImportDeclaration: (node) => { | ||
callback(configureParams(filename, node, node.source)); | ||
} | ||
}, | ||
}); | ||
}, | ||
CallExpression: (node) => { | ||
if ( | ||
node.arguments.length > 0 && | ||
node.arguments[0].type === "Literal" && | ||
(node.callee.type === "Import" || | ||
(node.callee.type === "Identifier" && node.callee.name === "require")) | ||
) { | ||
callback(configureParams(filename, node, node.arguments[0])); | ||
} | ||
}, | ||
ImportExpression: (node) => { | ||
if (node.source.type === "Literal") { | ||
callback(configureParams(filename, node, node.source)); | ||
} | ||
}, | ||
}; | ||
} | ||
module.exports = { | ||
getBaseDirs: getTSconfigDirs, | ||
getImport, | ||
isRelativeToParent, | ||
isExternalPath, | ||
}; |
{ | ||
"name": "eslint-plugin-path", | ||
"version": "0.1.0-rc.3", | ||
"version": "1.0.0-rc.4", | ||
"main": "lib/index.js", | ||
@@ -5,0 +5,0 @@ "author": "qDanik <qdanik@yandex.ru>", |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
13823
12
370
4
1