eslint-plugin-import-sorting
Version:
ESLint plugin to group and sort imports by module, à la Python isort
110 lines (109 loc) • 3.52 kB
JavaScript
import { builtinModules } from "node:module";
import { AST_NODE_TYPES } from "@typescript-eslint/utils";
import { IMPORT_GROUPS } from "../rules/order.js";
import { useGroups } from "./use-groups.js";
function computeGroup(node, settings, sourceCode) {
let { getGroup, defineGroup } = useGroups(IMPORT_GROUPS);
if (node.type === AST_NODE_TYPES.ImportDeclaration || node.type === AST_NODE_TYPES.VariableDeclaration) {
let value;
let frameworkPatterns = validateSetting(settings, "import-sorting/framework-patterns");
let internalPatterns = validateSetting(settings, "import-sorting/internal-patterns");
if (node.type === AST_NODE_TYPES.ImportDeclaration) {
value = node.source.value;
} else {
let decl = node.declarations[0].init;
let declValue = decl.arguments[0].value;
value = declValue.toString();
}
if (isSideEffectImport(node, sourceCode))
defineGroup("unassigned");
if (isBuiltin(value))
defineGroup("builtin");
if (isStyle(value))
defineGroup("style");
if (frameworkPatterns && isFramework(value, frameworkPatterns))
defineGroup("framework");
if (internalPatterns && isInternal(value, internalPatterns))
defineGroup("internal");
if (isExternal(value))
defineGroup("external");
if (isLocal(value))
defineGroup("local");
}
return getGroup();
}
function isSideEffectImport(node, sourceCode) {
return node.type === AST_NODE_TYPES.ImportDeclaration && node.specifiers.length === 0 && /* Avoid matching on named imports without specifiers. */
!/}\s*from\s+/.test(sourceCode.getText(node));
}
function isBuiltin(name) {
let bunModules = [
"bun",
"bun:ffi",
"bun:jsc",
"bun:sqlite",
"bun:test",
"bun:wrap",
"detect-libc",
"undici",
"ws"
];
let builtinPrefixOnlyModules = ["sea", "sqlite", "test"];
return builtinModules.includes(name.startsWith("node:") ? name.split("node:")[1] : name) || builtinPrefixOnlyModules.some((module) => `node:${module}` === name) || bunModules.includes(name);
}
const moduleRegExp = /^\w/;
function isModule(name) {
return moduleRegExp.test(name);
}
const scopedRegExp = /^@[^/]+\/?[^/]+/;
function isScoped(name) {
return scopedRegExp.test(name);
}
function isFramework(name, pattern) {
if (Array.isArray(pattern)) {
return pattern.some((item) => new RegExp(item).test(name));
}
return new RegExp(pattern).test(name);
}
function isExternal(name) {
return isModule(name) || isScoped(name);
}
function isInternal(name, pattern) {
if (Array.isArray(pattern)) {
return pattern.some((item) => new RegExp(item).test(name));
}
return new RegExp(pattern).test(name);
}
function isLocal(name) {
return name.startsWith(".");
}
function isStyle(name) {
return [".less", ".scss", ".sass", ".styl", ".pcss", ".css", ".sss"].some(
(extension) => name.endsWith(extension)
);
}
function assertString(value, setting) {
if (typeof value !== "string")
throw new Error(
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
`Invalid value for '${setting}': '${value}'.
Expected 'string', got '${typeof value}' instead.`
);
}
function validateSetting(settings, setting) {
let value = settings[setting];
if (!value)
return void 0;
if (Array.isArray(value)) {
for (let item of value) {
assertString(item, setting);
}
return value;
}
assertString(value, setting);
return value;
}
export {
computeGroup,
isSideEffectImport
};