routie dev init since i didn't adhere to any proper guidance up until now
This commit is contained in:
+912
@@ -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 };
|
||||
Reference in New Issue
Block a user