Files
routie/frontend/node_modules/eslint-plugin-antfu/dist/index.mjs
T

913 lines
27 KiB
JavaScript

const version = "3.2.2";
const hasDocs = [
"consistent-chaining",
"consistent-list-newline",
"curly",
"if-newline",
"import-dedupe",
"indent-unindent",
"top-level-function"
];
const blobUrl = "https://github.com/antfu/eslint-plugin-antfu/blob/main/src/rules/";
function RuleCreator(urlCreator) {
return function createNamedRule({
name,
meta,
...rule
}) {
return createRule({
meta: {
...meta,
docs: {
...meta.docs,
url: urlCreator(name)
}
},
...rule
});
};
}
function createRule({
create,
defaultOptions,
meta
}) {
return {
create: ((context) => {
const optionsWithDefault = context.options.map((options, index) => {
return {
...defaultOptions?.[index] || {},
...options || {}
};
});
return create(context, optionsWithDefault);
}),
defaultOptions,
meta
};
}
const createEslintRule = RuleCreator(
(ruleName) => hasDocs.includes(ruleName) ? `${blobUrl}${ruleName}.md` : `${blobUrl}${ruleName}.test.ts`
);
const RULE_NAME$9 = "consistent-chaining";
const consistentChaining = createEslintRule({
name: RULE_NAME$9,
meta: {
type: "layout",
docs: {
description: "Having line breaks styles to object, array and named imports"
},
fixable: "whitespace",
schema: [
{
type: "object",
properties: {
allowLeadingPropertyAccess: {
type: "boolean",
description: "Allow leading property access to be on the same line",
default: true
}
},
additionalProperties: false
}
],
messages: {
shouldWrap: "Should have line breaks between items, in node {{name}}",
shouldNotWrap: "Should not have line breaks between items, in node {{name}}"
}
},
defaultOptions: [
{
allowLeadingPropertyAccess: true
}
],
create: (context) => {
const knownRoot = /* @__PURE__ */ new WeakSet();
const {
allowLeadingPropertyAccess = true
} = context.options[0] || {};
return {
MemberExpression(node) {
let root = node;
while (root.parent && (root.parent.type === "MemberExpression" || root.parent.type === "CallExpression"))
root = root.parent;
if (knownRoot.has(root))
return;
knownRoot.add(root);
const members = [];
let current = root;
while (current) {
switch (current.type) {
case "MemberExpression": {
if (!current.computed)
members.unshift(current);
current = current.object;
break;
}
case "CallExpression": {
current = current.callee;
break;
}
case "TSNonNullExpression": {
current = current.expression;
break;
}
default: {
current = void 0;
break;
}
}
}
let leadingPropertyAcccess = allowLeadingPropertyAccess;
let mode = null;
members.forEach((m) => {
const token = context.sourceCode.getTokenBefore(m.property);
const tokenBefore = context.sourceCode.getTokenBefore(token);
const currentMode = token.loc.start.line === tokenBefore.loc.end.line ? "single" : "multi";
const object = m.object.type === "TSNonNullExpression" ? m.object.expression : m.object;
if (leadingPropertyAcccess && (object.type === "ThisExpression" || object.type === "Identifier" || object.type === "MemberExpression" || object.type === "Literal") && currentMode === "single") {
return;
}
leadingPropertyAcccess = false;
if (mode == null) {
mode = currentMode;
return;
}
if (mode !== currentMode) {
context.report({
messageId: mode === "single" ? "shouldNotWrap" : "shouldWrap",
loc: token.loc,
data: {
name: root.type
},
fix(fixer) {
if (mode === "multi")
return fixer.insertTextAfter(tokenBefore, "\n");
else
return fixer.removeRange([tokenBefore.range[1], token.range[0]]);
}
});
}
});
}
};
}
});
const RULE_NAME$8 = "consistent-list-newline";
function isCommaToken(token) {
return token.type === "Punctuator" && token.value === ",";
}
const consistentListNewline = createEslintRule({
name: RULE_NAME$8,
meta: {
type: "layout",
docs: {
description: "Having line breaks styles to object, array and named imports"
},
fixable: "whitespace",
schema: [{
type: "object",
properties: {
ArrayExpression: { type: "boolean" },
ArrayPattern: { type: "boolean" },
ArrowFunctionExpression: { type: "boolean" },
CallExpression: { type: "boolean" },
ExportNamedDeclaration: { type: "boolean" },
FunctionDeclaration: { type: "boolean" },
FunctionExpression: { type: "boolean" },
IfStatement: { type: "boolean" },
ImportDeclaration: { type: "boolean" },
JSONArrayExpression: { type: "boolean" },
JSONObjectExpression: { type: "boolean" },
JSXOpeningElement: { type: "boolean" },
NewExpression: { type: "boolean" },
ObjectExpression: { type: "boolean" },
ObjectPattern: { type: "boolean" },
TSFunctionType: { type: "boolean" },
TSInterfaceDeclaration: { type: "boolean" },
TSTupleType: { type: "boolean" },
TSTypeLiteral: { type: "boolean" },
TSTypeParameterDeclaration: { type: "boolean" },
TSTypeParameterInstantiation: { type: "boolean" }
},
additionalProperties: false
}],
messages: {
shouldWrap: "Should have line breaks between items, in node {{name}}",
shouldNotWrap: "Should not have line breaks between items, in node {{name}}"
}
},
defaultOptions: [{}],
create: (context, [options = {}] = [{}]) => {
const multilineNodes = /* @__PURE__ */ new Set([
"ArrayExpression",
"FunctionDeclaration",
"IfStatement",
"ObjectExpression",
"ObjectPattern",
"TSTypeLiteral",
"TSTupleType",
"TSInterfaceDeclaration"
]);
function removeLines(fixer, start, end, delimiter) {
const range = [start, end];
const code = context.sourceCode.text.slice(...range);
return fixer.replaceTextRange(range, code.replace(/(\r\n|\n)/g, delimiter ?? ""));
}
function getDelimiter(root, current) {
if (root.type !== "TSInterfaceDeclaration" && root.type !== "TSTypeLiteral")
return;
const currentContent = context.sourceCode.text.slice(current.range[0], current.range[1]);
return currentContent.match(/(?:,|;)$/) ? void 0 : ",";
}
function hasComments(current) {
let program = current;
while (program.type !== "Program")
program = program.parent;
const currentRange = current.range;
return !!program.comments?.some((comment) => {
const commentRange = comment.range;
return commentRange[0] > currentRange[0] && commentRange[1] < currentRange[1];
});
}
function check(node, children, nextNode) {
const items = children.filter(Boolean);
if (items.length === 0)
return;
let startToken = ["CallExpression", "NewExpression"].includes(node.type) ? void 0 : context.sourceCode.getFirstToken(node);
if (node.type === "CallExpression") {
startToken = context.sourceCode.getTokenAfter(
node.typeArguments ? node.typeArguments : node.callee.type === "MemberExpression" ? node.callee.property : node.callee
);
}
if (startToken?.type !== "Punctuator")
startToken = context.sourceCode.getTokenBefore(items[0]);
const endToken = context.sourceCode.getTokenAfter(items[items.length - 1]);
const startLine = startToken.loc.start.line;
if (startToken.loc.start.line === endToken.loc.end.line)
return;
let mode = null;
let lastLine = startLine;
items.forEach((item, idx) => {
if (mode == null) {
mode = item.loc.start.line === lastLine ? "inline" : "newline";
lastLine = item.loc.end.line;
return;
}
const currentStart = item.loc.start.line;
if (mode === "newline" && currentStart === lastLine) {
context.report({
node: item,
messageId: "shouldWrap",
data: {
name: node.type
},
*fix(fixer) {
yield fixer.insertTextBefore(item, "\n");
}
});
} else if (mode === "inline" && currentStart !== lastLine) {
const lastItem2 = items[idx - 1];
if (context.sourceCode.getCommentsBefore(item).length > 0)
return;
const content = context.sourceCode.text.slice(lastItem2.range[1], item.range[0]);
if (content.includes("\n")) {
context.report({
node: item,
messageId: "shouldNotWrap",
data: {
name: node.type
},
*fix(fixer) {
yield removeLines(fixer, lastItem2.range[1], item.range[0], getDelimiter(node, lastItem2));
}
});
}
}
lastLine = item.loc.end.line;
});
const endRange = nextNode ? Math.min(
context.sourceCode.getTokenBefore(nextNode).range[0],
node.range[1]
) : node.range[1];
const endLoc = context.sourceCode.getLocFromIndex(endRange);
const lastItem = items[items.length - 1];
if (mode === "newline" && endLoc.line === lastLine) {
context.report({
node: lastItem,
messageId: "shouldWrap",
data: {
name: node.type
},
*fix(fixer) {
yield fixer.insertTextAfter(lastItem, "\n");
}
});
} else if (mode === "inline" && endLoc.line !== lastLine) {
if (items.length === 1 && !multilineNodes.has(node.type))
return;
const nextToken = context.sourceCode.getTokenAfter(lastItem);
if (context.sourceCode.getCommentsAfter(nextToken && isCommaToken(nextToken) ? nextToken : lastItem).length > 0)
return;
const content = context.sourceCode.text.slice(lastItem.range[1], endRange);
if (content.includes("\n")) {
context.report({
node: lastItem,
messageId: "shouldNotWrap",
data: {
name: node.type
},
*fix(fixer) {
const delimiter = items.length === 1 ? "" : getDelimiter(node, lastItem);
yield removeLines(fixer, lastItem.range[1], endRange, delimiter);
}
});
}
}
}
const listenser = {
ObjectExpression: (node) => {
check(node, node.properties);
},
ArrayExpression: (node) => {
check(node, node.elements);
},
ImportDeclaration: (node) => {
check(
node,
node.specifiers[0]?.type === "ImportDefaultSpecifier" ? node.specifiers.slice(1) : node.specifiers
);
},
ExportNamedDeclaration: (node) => {
check(node, node.specifiers);
},
FunctionDeclaration: (node) => {
check(
node,
node.params,
node.returnType || node.body
);
},
FunctionExpression: (node) => {
check(
node,
node.params,
node.returnType || node.body
);
},
IfStatement: (node) => {
check(node, [node.test], node.consequent);
},
ArrowFunctionExpression: (node) => {
if (node.params.length <= 1)
return;
check(
node,
node.params,
node.returnType || node.body
);
},
CallExpression: (node) => {
check(node, node.arguments);
},
TSInterfaceDeclaration: (node) => {
check(node, node.body.body);
},
TSTypeLiteral: (node) => {
check(node, node.members);
},
TSTupleType: (node) => {
check(node, node.elementTypes);
},
TSFunctionType: (node) => {
check(node, node.params);
},
NewExpression: (node) => {
check(node, node.arguments);
},
TSTypeParameterDeclaration(node) {
check(node, node.params);
},
TSTypeParameterInstantiation(node) {
check(node, node.params);
},
ObjectPattern(node) {
check(node, node.properties, node.typeAnnotation);
},
ArrayPattern(node) {
check(node, node.elements);
},
JSXOpeningElement(node) {
if (node.attributes.some((attr) => attr.loc.start.line !== attr.loc.end.line))
return;
check(node, node.attributes);
},
JSONArrayExpression(node) {
if (hasComments(node))
return;
check(node, node.elements);
},
JSONObjectExpression(node) {
if (hasComments(node))
return;
check(node, node.properties);
}
};
Object.keys(options).forEach((key) => {
if (options[key] === false)
delete listenser[key];
});
return listenser;
}
});
const RULE_NAME$7 = "curly";
const curly = createEslintRule({
name: RULE_NAME$7,
meta: {
type: "layout",
docs: {
description: "Enforce Anthony's style of curly bracket"
},
fixable: "whitespace",
schema: [],
messages: {
missingCurlyBrackets: "Expect curly brackets"
}
},
defaultOptions: [],
create: (context) => {
function requireCurly(body) {
if (!body)
return false;
if (body.type === "BlockStatement")
return true;
if (["IfStatement", "WhileStatement", "DoWhileStatement", "ForStatement", "ForInStatement", "ForOfStatement"].includes(body.type))
return true;
const statement = body.type === "ExpressionStatement" ? body.expression : body;
if (statement.loc.start.line !== statement.loc.end.line)
return true;
return false;
}
function wrapCurlyIfNeeded(body) {
if (body.type === "BlockStatement")
return;
context.report({
node: body,
messageId: "missingCurlyBrackets",
*fix(fixer) {
yield fixer.insertTextAfter(body, "\n}");
const token = context.sourceCode.getTokenBefore(body);
yield fixer.insertTextAfterRange(token.range, " {");
}
});
}
function check(bodies, additionalChecks = []) {
const requires = [...bodies, ...additionalChecks].map((body) => requireCurly(body));
if (requires.some((i) => i))
bodies.map((body) => wrapCurlyIfNeeded(body));
}
return {
IfStatement(node) {
const parent = node.parent;
if (parent.type === "IfStatement" && parent.alternate === node)
return;
const statements = [];
const tests = [];
function addIf(node2) {
statements.push(node2.consequent);
if (node2.test)
tests.push(node2.test);
if (node2.alternate) {
if (node2.alternate.type === "IfStatement")
addIf(node2.alternate);
else
statements.push(node2.alternate);
}
}
addIf(node);
check(statements, tests);
},
WhileStatement(node) {
check([node.body], [node.test]);
},
DoWhileStatement(node) {
check([node.body], [node.test]);
},
ForStatement(node) {
check([node.body]);
},
ForInStatement(node) {
check([node.body]);
},
ForOfStatement(node) {
check([node.body]);
}
};
}
});
const RULE_NAME$6 = "if-newline";
const ifNewline = createEslintRule({
name: RULE_NAME$6,
meta: {
type: "layout",
docs: {
description: "Newline after if"
},
fixable: "whitespace",
schema: [],
messages: {
missingIfNewline: "Expect newline after if"
}
},
defaultOptions: [],
create: (context) => {
return {
IfStatement(node) {
if (!node.consequent)
return;
if (node.consequent.type === "BlockStatement")
return;
if (node.test.loc.end.line === node.consequent.loc.start.line) {
context.report({
node,
loc: {
start: node.test.loc.end,
end: node.consequent.loc.start
},
messageId: "missingIfNewline",
fix(fixer) {
return fixer.replaceTextRange([node.consequent.range[0], node.consequent.range[0]], "\n");
}
});
}
}
};
}
});
const RULE_NAME$5 = "import-dedupe";
const importDedupe = createEslintRule({
name: RULE_NAME$5,
meta: {
type: "problem",
docs: {
description: "Fix duplication in imports"
},
fixable: "code",
schema: [],
messages: {
importDedupe: "Expect no duplication in imports"
}
},
defaultOptions: [],
create: (context) => {
return {
ImportDeclaration(node) {
if (node.specifiers.length <= 1)
return;
const names = /* @__PURE__ */ new Set();
node.specifiers.forEach((n) => {
const id = n.local.name;
if (names.has(id)) {
context.report({
node,
loc: {
start: n.loc.end,
end: n.loc.start
},
messageId: "importDedupe",
fix(fixer) {
const s = n.range[0];
let e = n.range[1];
if (context.sourceCode.text[e] === ",")
e += 1;
return fixer.removeRange([s, e]);
}
});
}
names.add(id);
});
}
};
}
});
const _reFullWs = /^\s*$/;
function unindent(str) {
const lines = (typeof str === "string" ? str : str[0]).split("\n");
const whitespaceLines = lines.map((line) => _reFullWs.test(line));
const commonIndent = lines.reduce((min, line, idx) => {
if (whitespaceLines[idx])
return min;
const indent = line.match(/^\s*/)?.[0].length;
return indent === void 0 ? min : Math.min(min, indent);
}, Number.POSITIVE_INFINITY);
let emptyLinesHead = 0;
while (emptyLinesHead < lines.length && whitespaceLines[emptyLinesHead])
emptyLinesHead++;
let emptyLinesTail = 0;
while (emptyLinesTail < lines.length && whitespaceLines[lines.length - emptyLinesTail - 1])
emptyLinesTail++;
return lines.slice(emptyLinesHead, lines.length - emptyLinesTail).map((line) => line.slice(commonIndent)).join("\n");
}
const indentUnindent = createEslintRule({
name: "indent-unindent",
meta: {
type: "layout",
docs: {
description: "Enforce consistent indentation in `unindent` template tag"
},
fixable: "code",
schema: [
{
type: "object",
properties: {
indent: {
type: "number",
minimum: 0,
default: 2
},
tags: {
type: "array",
items: {
type: "string"
}
}
},
additionalProperties: false
}
],
messages: {
"indent-unindent": "Consistent indentation in unindent tag"
}
},
defaultOptions: [{}],
create(context) {
const {
tags = ["$", "unindent", "unIndent"],
indent = 2
} = context.options?.[0] ?? {};
return {
TaggedTemplateExpression(node) {
const id = node.tag;
if (!id || id.type !== "Identifier")
return;
if (!tags.includes(id.name))
return;
if (node.quasi.quasis.length !== 1)
return;
const quasi = node.quasi.quasis[0];
const value = quasi.value.raw;
const lineStartIndex = context.sourceCode.getIndexFromLoc({
line: node.loc.start.line,
column: 0
});
const baseIndent = context.sourceCode.text.slice(lineStartIndex).match(/^\s*/)?.[0] ?? "";
const targetIndent = baseIndent + " ".repeat(indent);
const pure = unindent([value]);
let final = pure.split("\n").map((line) => targetIndent + line).join("\n");
final = `
${final}
${baseIndent}`;
if (final !== value) {
context.report({
node: quasi,
messageId: "indent-unindent",
fix: (fixer) => fixer.replaceText(quasi, `\`${final}\``)
});
}
}
};
}
});
const RULE_NAME$4 = "no-import-dist";
const noImportDist = createEslintRule({
name: RULE_NAME$4,
meta: {
type: "problem",
docs: {
description: "Prevent importing modules in `dist` folder"
},
schema: [],
messages: {
noImportDist: "Do not import modules in `dist` folder, got {{path}}"
}
},
defaultOptions: [],
create: (context) => {
function isDist(path) {
return Boolean(path.startsWith(".") && path.match(/\/dist(\/|$)/)) || path === "dist";
}
return {
ImportDeclaration: (node) => {
if (isDist(node.source.value)) {
context.report({
node,
messageId: "noImportDist",
data: {
path: node.source.value
}
});
}
}
};
}
});
const RULE_NAME$3 = "no-import-node-modules-by-path";
const noImportNodeModulesByPath = createEslintRule({
name: RULE_NAME$3,
meta: {
type: "problem",
docs: {
description: "Prevent importing modules in `node_modules` folder by relative or absolute path"
},
schema: [],
messages: {
noImportNodeModulesByPath: "Do not import modules in `node_modules` folder by path"
}
},
defaultOptions: [],
create: (context) => {
return {
"ImportDeclaration": (node) => {
if (node.source.value.includes("/node_modules/")) {
context.report({
node,
messageId: "noImportNodeModulesByPath"
});
}
},
'CallExpression[callee.name="require"]': (node) => {
const value = node.arguments[0]?.value;
if (typeof value === "string" && value.includes("/node_modules/")) {
context.report({
node,
messageId: "noImportNodeModulesByPath"
});
}
}
};
}
});
const RULE_NAME$2 = "no-top-level-await";
const noTopLevelAwait = createEslintRule({
name: RULE_NAME$2,
meta: {
type: "problem",
docs: {
description: "Prevent using top-level await"
},
schema: [],
messages: {
NoTopLevelAwait: "Do not use top-level await"
}
},
defaultOptions: [],
create: (context) => {
return {
AwaitExpression: (node) => {
let parent = node.parent;
while (parent) {
if (parent.type === "FunctionDeclaration" || parent.type === "FunctionExpression" || parent.type === "ArrowFunctionExpression") {
return;
}
parent = parent.parent;
}
context.report({
node,
messageId: "NoTopLevelAwait"
});
}
};
}
});
const RULE_NAME$1 = "no-ts-export-equal";
const noTsExportEqual = createEslintRule({
name: RULE_NAME$1,
meta: {
type: "problem",
docs: {
description: "Do not use `exports =`"
},
schema: [],
messages: {
noTsExportEqual: "Use ESM `export default` instead"
}
},
defaultOptions: [],
create: (context) => {
const extension = context.filename.split(".").pop();
if (!extension)
return {};
if (!["ts", "tsx", "mts", "cts"].includes(extension))
return {};
return {
TSExportAssignment(node) {
context.report({
node,
messageId: "noTsExportEqual"
});
}
};
}
});
const RULE_NAME = "top-level-function";
const topLevelFunction = createEslintRule({
name: RULE_NAME,
meta: {
type: "problem",
docs: {
description: "Enforce top-level functions to be declared with function keyword"
},
fixable: "code",
schema: [],
messages: {
topLevelFunctionDeclaration: "Top-level functions should be declared with function keyword"
}
},
defaultOptions: [],
create: (context) => {
return {
VariableDeclaration(node) {
if (node.parent.type !== "Program" && node.parent.type !== "ExportNamedDeclaration")
return;
if (node.declarations.length !== 1)
return;
if (node.kind !== "const")
return;
if (node.declare)
return;
const declaration = node.declarations[0];
if (declaration.init?.type !== "ArrowFunctionExpression" && declaration.init?.type !== "FunctionExpression") {
return;
}
if (declaration.id?.type !== "Identifier")
return;
if (declaration.id.typeAnnotation)
return;
if (declaration.init.body.type !== "BlockStatement" && declaration.id?.loc.start.line === declaration.init?.body.loc.end.line) {
return;
}
const fnExpression = declaration.init;
const body = declaration.init.body;
const id = declaration.id;
context.report({
node,
loc: {
start: id.loc.start,
end: body.loc.start
},
messageId: "topLevelFunctionDeclaration",
fix(fixer) {
const code = context.sourceCode.text;
const textName = code.slice(id.range[0], id.range[1]);
const textArgs = fnExpression.params.length ? code.slice(fnExpression.params[0].range[0], fnExpression.params[fnExpression.params.length - 1].range[1]) : "";
const textBody = body.type === "BlockStatement" ? code.slice(body.range[0], body.range[1]) : `{
return ${code.slice(body.range[0], body.range[1])}
}`;
const textGeneric = fnExpression.typeParameters ? code.slice(fnExpression.typeParameters.range[0], fnExpression.typeParameters.range[1]) : "";
const textTypeReturn = fnExpression.returnType ? code.slice(fnExpression.returnType.range[0], fnExpression.returnType.range[1]) : "";
const textAsync = fnExpression.async ? "async " : "";
const final = `${textAsync}function ${textName} ${textGeneric}(${textArgs})${textTypeReturn} ${textBody}`;
return fixer.replaceTextRange([node.range[0], node.range[1]], final);
}
});
}
};
}
});
const plugin = {
meta: {
name: "antfu",
version
},
// @keep-sorted
rules: {
"consistent-chaining": consistentChaining,
"consistent-list-newline": consistentListNewline,
"curly": curly,
"if-newline": ifNewline,
"import-dedupe": importDedupe,
"indent-unindent": indentUnindent,
"no-import-dist": noImportDist,
"no-import-node-modules-by-path": noImportNodeModulesByPath,
"no-top-level-await": noTopLevelAwait,
"no-ts-export-equal": noTsExportEqual,
"top-level-function": topLevelFunction
}
};
export { plugin as default };