routie dev init since i didn't adhere to any proper guidance up until now

This commit is contained in:
2026-04-29 22:27:29 -06:00
commit e1dabb71e2
15301 changed files with 3562618 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
MIT License
Copyright (c) 2021-PRESENT Anthony Fu <https://github.com/antfu>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+27
View File
@@ -0,0 +1,27 @@
# eslint-plugin-antfu
[![npm version][npm-version-src]][npm-version-href]
[![npm downloads][npm-downloads-src]][npm-downloads-href]
Anthony extended ESLint rules. For [antfu/eslint-config](https://github.com/antfu/eslint-config).
[Rules List](./src/rules)
## Sponsors
<p align="center">
<a href="https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg">
<img src='https://cdn.jsdelivr.net/gh/antfu/static/sponsors.svg'/>
</a>
</p>
## License
[MIT](./LICENSE) License © 2023-PRESENT [Anthony Fu](https://github.com/antfu)
<!-- Badges -->
[npm-version-src]: https://img.shields.io/npm/v/eslint-plugin-antfu?style=flat&colorA=080f12&colorB=1fa669
[npm-version-href]: https://npmjs.com/package/eslint-plugin-antfu
[npm-downloads-src]: https://img.shields.io/npm/dm/eslint-plugin-antfu?style=flat&colorA=080f12&colorB=1fa669
[npm-downloads-href]: https://npmjs.com/package/eslint-plugin-antfu
+75
View File
@@ -0,0 +1,75 @@
import { Rule, Linter } from 'eslint';
type RuleModule<T extends readonly unknown[]> = Rule.RuleModule & {
defaultOptions: T;
};
type Options$2 = [
{
indent?: number;
tags?: string[];
}
];
type Options$1 = [
{
ArrayExpression?: boolean;
ArrayPattern?: boolean;
ArrowFunctionExpression?: boolean;
CallExpression?: boolean;
ExportNamedDeclaration?: boolean;
FunctionDeclaration?: boolean;
FunctionExpression?: boolean;
IfStatement?: boolean;
ImportDeclaration?: boolean;
JSONArrayExpression?: boolean;
JSONObjectExpression?: boolean;
JSXOpeningElement?: boolean;
NewExpression?: boolean;
ObjectExpression?: boolean;
ObjectPattern?: boolean;
TSFunctionType?: boolean;
TSInterfaceDeclaration?: boolean;
TSTupleType?: boolean;
TSTypeLiteral?: boolean;
TSTypeParameterDeclaration?: boolean;
TSTypeParameterInstantiation?: boolean;
}
];
type Options = [
{
allowLeadingPropertyAccess?: boolean;
}
];
declare const plugin: {
meta: {
name: string;
version: string;
};
rules: {
'consistent-chaining': RuleModule<Options>;
'consistent-list-newline': RuleModule<Options$1>;
curly: RuleModule<[]>;
'if-newline': RuleModule<[]>;
'import-dedupe': RuleModule<[]>;
'indent-unindent': RuleModule<Options$2>;
'no-import-dist': RuleModule<[]>;
'no-import-node-modules-by-path': RuleModule<[]>;
'no-top-level-await': RuleModule<[]>;
'no-ts-export-equal': RuleModule<[]>;
'top-level-function': RuleModule<[]>;
};
};
type RuleDefinitions = typeof plugin['rules'];
type RuleOptions = {
[K in keyof RuleDefinitions]: RuleDefinitions[K]['defaultOptions'];
};
type Rules = {
[K in keyof RuleOptions]: Linter.RuleEntry<RuleOptions[K]>;
};
export { plugin as default };
export type { RuleOptions, Rules };
+912
View File
@@ -0,0 +1,912 @@
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 };
+72
View File
@@ -0,0 +1,72 @@
{
"name": "eslint-plugin-antfu",
"type": "module",
"version": "3.2.2",
"description": "Anthony's opinionated ESLint rules",
"author": "Anthony Fu <anthonyfu117@hotmail.com>",
"license": "MIT",
"funding": "https://github.com/sponsors/antfu",
"homepage": "https://github.com/antfu/eslint-plugin-antfu#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/antfu/eslint-plugin-antfu.git"
},
"bugs": "https://github.com/antfu/eslint-plugin-antfu/issues",
"keywords": [
"eslint-plugin"
],
"sideEffects": false,
"exports": {
".": "./dist/index.mjs"
},
"main": "./dist/index.mjs",
"module": "./dist/index.mjs",
"types": "./dist/index.d.mts",
"files": [
"dist"
],
"peerDependencies": {
"eslint": "*"
},
"devDependencies": {
"@antfu/eslint-config": "^7.4.2",
"@antfu/ni": "^28.2.0",
"@antfu/utils": "^9.3.0",
"@types/eslint": "^9.6.1",
"@types/node": "^25.2.3",
"@typescript-eslint/typescript-estree": "^8.55.0",
"@typescript-eslint/utils": "^8.55.0",
"bumpp": "^10.4.1",
"eslint": "^10.0.0",
"eslint-vitest-rule-tester": "^3.1.0",
"jsonc-eslint-parser": "^2.4.2",
"lint-staged": "^16.2.7",
"simple-git-hooks": "^2.13.1",
"skills-npm": "^1.0.0",
"tsup": "^8.5.1",
"tsx": "^4.21.0",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"vite": "^7.3.1",
"vitest": "^4.0.18"
},
"resolutions": {
"eslint": "^10.0.0",
"eslint-plugin-antfu": "workspace:*"
},
"simple-git-hooks": {
"pre-commit": "npx lint-staged"
},
"lint-staged": {
"*": "eslint --fix"
},
"scripts": {
"build": "unbuild",
"dev": "unbuild --stub",
"lint": "pnpm run dev && eslint .",
"release": "bumpp",
"start": "tsx src/index.ts",
"test": "vitest",
"typecheck": "tsc --noEmit"
}
}