179 lines
4.2 KiB
JavaScript
179 lines
4.2 KiB
JavaScript
const readability = {
|
|
create(context) {
|
|
const filename = context.filename;
|
|
|
|
function checkParameters(node) {
|
|
const last = node.params.at(-1);
|
|
if (
|
|
last?.type === "Identifier"
|
|
&& !last.optional
|
|
&& last.typeAnnotation?.typeAnnotation.type === "TSUnionType"
|
|
&& last.typeAnnotation.typeAnnotation.types.some(
|
|
type => type.type === "TSUndefinedKeyword",
|
|
)
|
|
)
|
|
context.report({ messageId: "optional", node: last });
|
|
}
|
|
|
|
return {
|
|
ArrowFunctionExpression: checkParameters,
|
|
CallExpression(node) {
|
|
if (
|
|
node.callee.type === "MemberExpression"
|
|
&& ["refine", "superRefine"].includes(node.callee.property.name)
|
|
&& !filename.endsWith(".schema.ts")
|
|
)
|
|
context.report({ messageId: "schema", node });
|
|
},
|
|
FunctionDeclaration: checkParameters,
|
|
FunctionExpression: checkParameters,
|
|
ImportDeclaration(node) {
|
|
if (
|
|
node.source.value === "zod"
|
|
&& node.importKind !== "type"
|
|
&& !filename.endsWith(".schema.ts")
|
|
)
|
|
context.report({ messageId: "schema", node });
|
|
},
|
|
JSXOpeningElement(node) {
|
|
if (
|
|
!filename.includes("/packages/icons/")
|
|
&& node.name.type === "JSXIdentifier"
|
|
&& /^(?:svg|path|circle|ellipse|rect|polygon|polyline|line|g|defs|use|symbol)$/.test(
|
|
node.name.name,
|
|
)
|
|
)
|
|
context.report({ messageId: "svg", node });
|
|
},
|
|
TSUnionType(node) {
|
|
const complex
|
|
= node.types.length > 2
|
|
|| node.types.some(type => type.type === "TSTypeLiteral");
|
|
|
|
if (
|
|
complex
|
|
&& node.types.some(
|
|
(type, index) =>
|
|
index > 0
|
|
&& type.loc.start.line === node.types[index - 1].loc.end.line,
|
|
)
|
|
)
|
|
context.report({ messageId: "union", node });
|
|
},
|
|
VariableDeclarator(node) {
|
|
if (
|
|
!/^[A-Z]/.test(node.id.name ?? "")
|
|
|| node.init?.type !== "ArrowFunctionExpression"
|
|
|| node.init.body.type !== "BlockStatement"
|
|
)
|
|
return;
|
|
|
|
for (const statement of node.init.body.body) {
|
|
if (statement.type === "FunctionDeclaration")
|
|
context.report({ messageId: "helper", node: statement });
|
|
|
|
else if (statement.type === "VariableDeclaration") {
|
|
for (const declaration of statement.declarations) {
|
|
if (
|
|
["ArrowFunctionExpression", "FunctionExpression"].includes(
|
|
declaration.init?.type,
|
|
)
|
|
)
|
|
context.report({ messageId: "helper", node: declaration });
|
|
}
|
|
}
|
|
}
|
|
},
|
|
};
|
|
},
|
|
meta: {
|
|
messages: {
|
|
helper:
|
|
"Move helpers outside components; actions belong to the ViewModel.",
|
|
optional:
|
|
"Use an optional trailing parameter instead of a union with undefined.",
|
|
schema: "Declare Zod schemas in a dedicated *.schema.ts file.",
|
|
svg: "Own SVG markup in packages/icons, and import its public component.",
|
|
union: "Put complex union members on separate lines.",
|
|
},
|
|
schema: [],
|
|
type: "suggestion",
|
|
},
|
|
};
|
|
|
|
function isComponent(node) {
|
|
const pending = [node];
|
|
|
|
while (pending.length > 0) {
|
|
const child = pending.pop();
|
|
if (!child) continue;
|
|
|
|
switch (child.type) {
|
|
case "JSXElement": {
|
|
return true;
|
|
}
|
|
|
|
case "JSXFragment": {
|
|
return true;
|
|
}
|
|
|
|
case "JSXExpressionContainer": {
|
|
pending.push(child.expression);
|
|
continue;
|
|
}
|
|
|
|
case "LogicalExpression": {
|
|
pending.push(child.right);
|
|
continue;
|
|
}
|
|
|
|
case "ConditionalExpression": {
|
|
pending.push(child.consequent, child.alternate);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
const jsxSpacing = {
|
|
create(context) {
|
|
function check(node) {
|
|
let previous;
|
|
|
|
for (const child of node.children) {
|
|
if (child.type === "JSXText" && !child.value.trim()) continue;
|
|
if (isComponent(child)) {
|
|
if (previous) {
|
|
const gap = context.sourceCode.text.slice(
|
|
previous.range[1],
|
|
child.range[0],
|
|
);
|
|
|
|
if (!/\n\s*\n/.test(gap))
|
|
context.report({
|
|
fix: fixer => fixer.insertTextAfter(previous, "\n"),
|
|
messageId: "space",
|
|
node: child,
|
|
});
|
|
}
|
|
|
|
previous = child;
|
|
}
|
|
else previous = undefined;
|
|
}
|
|
}
|
|
|
|
return { JSXElement: check, JSXFragment: check };
|
|
},
|
|
meta: {
|
|
fixable: "whitespace",
|
|
messages: { space: "Separate sibling JSX components with a blank line." },
|
|
schema: [],
|
|
type: "layout",
|
|
},
|
|
};
|
|
|
|
export default { rules: { "jsx-spacing": jsxSpacing, readability } };
|