(feat): improved mobile notifications
(feat): add lint rules (feat): improve markdown inline code box
This commit is contained in:
parent
336b9464c9
commit
4a841de073
35 changed files with 777 additions and 287 deletions
|
|
@ -72,3 +72,211 @@ export const noWindowLocationReload: Rule.RuleModule = {
|
|||
};
|
||||
},
|
||||
};
|
||||
|
||||
interface AstNode {
|
||||
type: string;
|
||||
parent: AstNode | null;
|
||||
range: [number, number];
|
||||
}
|
||||
|
||||
interface TypeAliasDeclaration extends AstNode {
|
||||
type: "TSTypeAliasDeclaration";
|
||||
typeAnnotation: AstNode;
|
||||
typeParameters?: unknown;
|
||||
}
|
||||
|
||||
interface FunctionDeclaration extends AstNode {
|
||||
type: "FunctionDeclaration";
|
||||
async: boolean;
|
||||
generator: boolean;
|
||||
params: AstNode[];
|
||||
body: AstNode & { body: AstNode[] };
|
||||
}
|
||||
|
||||
interface ReturnStatement extends AstNode {
|
||||
type: "ReturnStatement";
|
||||
argument: AstNode | null;
|
||||
}
|
||||
|
||||
function isExported(node: AstNode): boolean {
|
||||
return (
|
||||
node.parent?.type === "ExportNamedDeclaration" ||
|
||||
node.parent?.type === "ExportDefaultDeclaration"
|
||||
);
|
||||
}
|
||||
|
||||
function containsContextSensitiveNode(value: unknown): boolean {
|
||||
if (!value || typeof value !== "object") return false;
|
||||
|
||||
const node = value as { type?: string; [key: string]: unknown };
|
||||
if (
|
||||
node.type === "ThisExpression" ||
|
||||
node.type === "Super" ||
|
||||
node.type === "MetaProperty"
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return Object.entries(node).some(
|
||||
([key, child]) =>
|
||||
key !== "parent" &&
|
||||
key !== "loc" &&
|
||||
key !== "range" &&
|
||||
containsContextSensitiveNode(child),
|
||||
);
|
||||
}
|
||||
|
||||
const unambiguousInlineTypes = new Set([
|
||||
"TSAnyKeyword",
|
||||
"TSBigIntKeyword",
|
||||
"TSBooleanKeyword",
|
||||
"TSIntrinsicKeyword",
|
||||
"TSLiteralType",
|
||||
"TSNeverKeyword",
|
||||
"TSNullKeyword",
|
||||
"TSNumberKeyword",
|
||||
"TSObjectKeyword",
|
||||
"TSStringKeyword",
|
||||
"TSSymbolKeyword",
|
||||
"TSThisType",
|
||||
"TSTupleType",
|
||||
"TSTypeLiteral",
|
||||
"TSTypeReference",
|
||||
"TSUndefinedKeyword",
|
||||
"TSUnknownKeyword",
|
||||
"TSVoidKeyword",
|
||||
]);
|
||||
|
||||
export const inlineSingleUseDeclarations: Rule.RuleModule = {
|
||||
meta: {
|
||||
type: "suggestion",
|
||||
docs: {
|
||||
description: "Inline local types and functions that are used only once",
|
||||
},
|
||||
fixable: "code",
|
||||
messages: {
|
||||
function: "Inline this function at its only call site.",
|
||||
type: "Inline this type at its only use site.",
|
||||
},
|
||||
schema: [],
|
||||
},
|
||||
create(context) {
|
||||
const sourceCode = context.sourceCode;
|
||||
|
||||
return {
|
||||
TSTypeAliasDeclaration(untypedNode: Rule.Node) {
|
||||
const node = untypedNode as unknown as TypeAliasDeclaration;
|
||||
if (node.typeParameters || isExported(node)) return;
|
||||
|
||||
const eslintNode = node as unknown as Rule.Node;
|
||||
const [variable] = sourceCode.getDeclaredVariables(eslintNode);
|
||||
if (!variable || variable.references.length !== 1) return;
|
||||
|
||||
const reference = variable.references[0]
|
||||
.identifier as unknown as AstNode;
|
||||
if (
|
||||
reference.range[0] >= node.range[0] &&
|
||||
reference.range[1] <= node.range[1]
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const referenceParent = reference.parent;
|
||||
if (
|
||||
!referenceParent ||
|
||||
referenceParent.type !== "TSTypeReference" ||
|
||||
referenceParent.parent?.type === "TSClassImplements" ||
|
||||
referenceParent.parent?.type === "TSInterfaceHeritage"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: eslintNode,
|
||||
messageId: "type",
|
||||
fix(fixer) {
|
||||
const annotation = sourceCode.getText(
|
||||
node.typeAnnotation as unknown as Rule.Node,
|
||||
);
|
||||
const replacement = unambiguousInlineTypes.has(
|
||||
node.typeAnnotation.type,
|
||||
)
|
||||
? annotation
|
||||
: `(${annotation})`;
|
||||
|
||||
return [
|
||||
fixer.replaceText(
|
||||
referenceParent as unknown as Rule.Node,
|
||||
replacement,
|
||||
),
|
||||
fixer.remove(eslintNode),
|
||||
];
|
||||
},
|
||||
});
|
||||
},
|
||||
|
||||
FunctionDeclaration(untypedNode: Rule.Node) {
|
||||
const node = untypedNode as unknown as FunctionDeclaration;
|
||||
if (
|
||||
node.async ||
|
||||
node.generator ||
|
||||
node.params.length !== 0 ||
|
||||
node.body.body.length !== 1 ||
|
||||
isExported(node)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const statement = node.body.body[0] as ReturnStatement;
|
||||
if (statement.type !== "ReturnStatement" || !statement.argument) return;
|
||||
|
||||
const eslintNode = node as unknown as Rule.Node;
|
||||
const functionScope = sourceCode.getScope(eslintNode);
|
||||
if (
|
||||
functionScope.references.length !== 0 ||
|
||||
functionScope.through.length !== 0 ||
|
||||
containsContextSensitiveNode(statement.argument)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [variable] = sourceCode.getDeclaredVariables(eslintNode);
|
||||
if (!variable || variable.references.length !== 1) return;
|
||||
|
||||
const reference = variable.references[0]
|
||||
.identifier as unknown as AstNode;
|
||||
const call = reference.parent;
|
||||
if (
|
||||
!call ||
|
||||
call.type !== "CallExpression" ||
|
||||
(call as AstNode & { callee: AstNode }).callee !== reference ||
|
||||
(call as AstNode & { arguments: AstNode[] }).arguments.length !== 0 ||
|
||||
(call as AstNode & { optional?: boolean }).optional ||
|
||||
(reference.range[0] >= node.range[0] &&
|
||||
reference.range[1] <= node.range[1])
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: eslintNode,
|
||||
messageId: "function",
|
||||
fix(fixer) {
|
||||
const expression = sourceCode.getText(
|
||||
statement.argument! as unknown as Rule.Node,
|
||||
);
|
||||
const replacement =
|
||||
statement.argument!.type === "Literal"
|
||||
? expression
|
||||
: `(${expression})`;
|
||||
|
||||
return [
|
||||
fixer.replaceText(call as unknown as Rule.Node, replacement),
|
||||
fixer.remove(eslintNode),
|
||||
];
|
||||
},
|
||||
});
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ for (const targetDir of targetDirs) {
|
|||
const entry = relative(rootDir, fullPath);
|
||||
console.log(`Linting ${entry}...`);
|
||||
try {
|
||||
execSync("pnpm run lint", { cwd: fullPath, stdio: "inherit" });
|
||||
execSync("pnpm run lint --fix", { cwd: fullPath, stdio: "inherit" });
|
||||
console.log(`${entry} linted successfully.`);
|
||||
} catch {
|
||||
console.error(`Failed to lint ${entry}.`);
|
||||
|
|
|
|||
Loading…
Reference in a new issue