159 lines
4.2 KiB
JavaScript
159 lines
4.2 KiB
JavaScript
|
"use strict";
|
|||
|
|
|||
|
const shared = require("./shared");
|
|||
|
|
|||
|
const defaultGroups = [
|
|||
|
// Side effect imports.
|
|||
|
["^\\u0000"],
|
|||
|
// Node.js builtins prefixed with `node:`.
|
|||
|
["^node:"],
|
|||
|
// Packages.
|
|||
|
// Things that start with a letter (or digit or underscore), or `@` followed by a letter.
|
|||
|
["^@?\\w"],
|
|||
|
// Absolute imports and other imports such as Vue-style `@/foo`.
|
|||
|
// Anything not matched in another group.
|
|||
|
["^"],
|
|||
|
// Relative imports.
|
|||
|
// Anything that starts with a dot.
|
|||
|
["^\\."],
|
|||
|
];
|
|||
|
|
|||
|
module.exports = {
|
|||
|
meta: {
|
|||
|
type: "layout",
|
|||
|
fixable: "code",
|
|||
|
schema: [
|
|||
|
{
|
|||
|
type: "object",
|
|||
|
properties: {
|
|||
|
groups: {
|
|||
|
type: "array",
|
|||
|
items: {
|
|||
|
type: "array",
|
|||
|
items: {
|
|||
|
type: "string",
|
|||
|
},
|
|||
|
},
|
|||
|
},
|
|||
|
},
|
|||
|
additionalProperties: false,
|
|||
|
},
|
|||
|
],
|
|||
|
docs: {
|
|||
|
url: "https://github.com/lydell/eslint-plugin-simple-import-sort#sort-order",
|
|||
|
},
|
|||
|
messages: {
|
|||
|
sort: "Run autofix to sort these imports!",
|
|||
|
},
|
|||
|
},
|
|||
|
create: (context) => {
|
|||
|
const { groups: rawGroups = defaultGroups } = context.options[0] || {};
|
|||
|
|
|||
|
const outerGroups = rawGroups.map((groups) =>
|
|||
|
groups.map((item) => RegExp(item, "u"))
|
|||
|
);
|
|||
|
|
|||
|
const parents = new Set();
|
|||
|
|
|||
|
return {
|
|||
|
ImportDeclaration: (node) => {
|
|||
|
parents.add(node.parent);
|
|||
|
},
|
|||
|
|
|||
|
"Program:exit": () => {
|
|||
|
for (const parent of parents) {
|
|||
|
for (const chunk of shared.extractChunks(parent, (node) =>
|
|||
|
isImport(node) ? "PartOfChunk" : "NotPartOfChunk"
|
|||
|
)) {
|
|||
|
maybeReportChunkSorting(chunk, context, outerGroups);
|
|||
|
}
|
|||
|
}
|
|||
|
parents.clear();
|
|||
|
},
|
|||
|
};
|
|||
|
},
|
|||
|
};
|
|||
|
|
|||
|
function maybeReportChunkSorting(chunk, context, outerGroups) {
|
|||
|
const sourceCode = context.getSourceCode();
|
|||
|
const items = shared.getImportExportItems(
|
|||
|
chunk,
|
|||
|
sourceCode,
|
|||
|
isSideEffectImport,
|
|||
|
getSpecifiers
|
|||
|
);
|
|||
|
const sortedItems = makeSortedItems(items, outerGroups);
|
|||
|
const sorted = shared.printSortedItems(sortedItems, items, sourceCode);
|
|||
|
const { start } = items[0];
|
|||
|
const { end } = items[items.length - 1];
|
|||
|
shared.maybeReportSorting(context, sorted, start, end);
|
|||
|
}
|
|||
|
|
|||
|
function makeSortedItems(items, outerGroups) {
|
|||
|
const itemGroups = outerGroups.map((groups) =>
|
|||
|
groups.map((regex) => ({ regex, items: [] }))
|
|||
|
);
|
|||
|
const rest = [];
|
|||
|
|
|||
|
for (const item of items) {
|
|||
|
const { originalSource } = item.source;
|
|||
|
const source = item.isSideEffectImport
|
|||
|
? `\0${originalSource}`
|
|||
|
: item.source.kind !== "value"
|
|||
|
? `${originalSource}\0`
|
|||
|
: originalSource;
|
|||
|
const [matchedGroup] = shared
|
|||
|
.flatMap(itemGroups, (groups) =>
|
|||
|
groups.map((group) => [group, group.regex.exec(source)])
|
|||
|
)
|
|||
|
.reduce(
|
|||
|
([group, longestMatch], [nextGroup, nextMatch]) =>
|
|||
|
nextMatch != null &&
|
|||
|
(longestMatch == null || nextMatch[0].length > longestMatch[0].length)
|
|||
|
? [nextGroup, nextMatch]
|
|||
|
: [group, longestMatch],
|
|||
|
[undefined, undefined]
|
|||
|
);
|
|||
|
if (matchedGroup == null) {
|
|||
|
rest.push(item);
|
|||
|
} else {
|
|||
|
matchedGroup.items.push(item);
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
return itemGroups
|
|||
|
.concat([[{ regex: /^/, items: rest }]])
|
|||
|
.map((groups) => groups.filter((group) => group.items.length > 0))
|
|||
|
.filter((groups) => groups.length > 0)
|
|||
|
.map((groups) =>
|
|||
|
groups.map((group) => shared.sortImportExportItems(group.items))
|
|||
|
);
|
|||
|
}
|
|||
|
|
|||
|
// Exclude "ImportDefaultSpecifier" – the "def" in `import def, {a, b}`.
|
|||
|
function getSpecifiers(importNode) {
|
|||
|
return importNode.specifiers.filter((node) => isImportSpecifier(node));
|
|||
|
}
|
|||
|
|
|||
|
// Full import statement.
|
|||
|
function isImport(node) {
|
|||
|
return node.type === "ImportDeclaration";
|
|||
|
}
|
|||
|
|
|||
|
// import def, { a, b as c, type d } from "A"
|
|||
|
// ^ ^^^^^^ ^^^^^^
|
|||
|
function isImportSpecifier(node) {
|
|||
|
return node.type === "ImportSpecifier";
|
|||
|
}
|
|||
|
|
|||
|
// import "setup"
|
|||
|
// But not: import {} from "setup"
|
|||
|
// And not: import type {} from "setup"
|
|||
|
function isSideEffectImport(importNode, sourceCode) {
|
|||
|
return (
|
|||
|
importNode.specifiers.length === 0 &&
|
|||
|
(!importNode.importKind || importNode.importKind === "value") &&
|
|||
|
!shared.isPunctuator(sourceCode.getFirstToken(importNode, { skip: 1 }), "{")
|
|||
|
);
|
|||
|
}
|