Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
- import { RuleContext, SourceCode } from "@typescript-eslint/utils/ts-eslint";
- const constants = {
- url: "https://example.com/rules/",
- ruleName: "max-lines-per-function",
- messageIds: {
- tooManyLines: "tooManyLines",
- },
- defaultMaxLines: 60,
- };
- const createRule = ESLintUtils.RuleCreator((ruleName: string) => `${constants.url}${ruleName}`);
- type FunctionLikeNode =
- | TSESTree.FunctionDeclaration
- | TSESTree.FunctionExpression
- | TSESTree.ArrowFunctionExpression
- | TSESTree.MethodDefinition;
- interface Options {
- maxLines: number;
- }
- const utils = {
- isFunctionLike(node: TSESTree.Node) {
- return (
- node.type === "FunctionDeclaration" ||
- node.type === "FunctionExpression" ||
- node.type === "ArrowFunctionExpression" ||
- node.type === "MethodDefinition"
- );
- },
- getFunctionBody(node: FunctionLikeNode) {
- let body: TSESTree.BlockStatement | null = null;
- if (node.type === "MethodDefinition") {
- body = node.value.body;
- } else if (node.type === "ArrowFunctionExpression" && node.body?.type === "BlockStatement") {
- body = node.body;
- } else if (node.body?.type === "BlockStatement") {
- body = node.body;
- }
- return body;
- },
- getFunctionParams(node: FunctionLikeNode) {
- const params = node.type === "MethodDefinition" ? node.value.params : node.params;
- return params;
- },
- isMethodFunction(node: TSESTree.Node) {
- return !!node.parent && node.parent.type === "MethodDefinition";
- },
- findLastReturnStatement(body: TSESTree.BlockStatement) {
- let lastReturn: TSESTree.ReturnStatement | undefined;
- const handleIfStatement = (node: TSESTree.IfStatement) => {
- if (node.consequent.type === "BlockStatement") {
- traverse(node.consequent.body);
- }
- if (node.alternate) {
- if (node.alternate.type === "BlockStatement") {
- traverse(node.alternate.body);
- } else if (node.alternate.type === "IfStatement") {
- traverse([node.alternate]);
- }
- }
- };
- const handleLoop = (node: TSESTree.Statement) => {
- if (
- node.type === "WhileStatement" ||
- node.type === "ForStatement" ||
- node.type === "ForInStatement" ||
- node.type === "ForOfStatement"
- ) {
- if (node.body.type === "BlockStatement") {
- traverse(node.body.body);
- }
- }
- };
- const handleTryStatement = (node: TSESTree.TryStatement) => {
- traverse(node.block.body);
- if (node.handler) {
- traverse(node.handler.body.body);
- }
- if (node.finalizer) {
- traverse(node.finalizer.body);
- }
- };
- const traverse = (nodes: TSESTree.Statement[]) => {
- for (const node of nodes) {
- if (node.type === "ReturnStatement") {
- lastReturn = node;
- } else if (node.type === "BlockStatement") {
- traverse(node.body);
- } else if (node.type === "IfStatement") {
- handleIfStatement(node);
- } else if (node.type === "SwitchStatement") {
- for (const switchCase of node.cases) {
- traverse(switchCase.consequent);
- }
- } else if (node.type === "TryStatement") {
- handleTryStatement(node);
- } else {
- handleLoop(node);
- }
- }
- };
- traverse(body.body);
- return lastReturn;
- },
- getParamLines(params: TSESTree.Parameter[]) {
- const paramLines = new Set<number>();
- if (params.length > 0) {
- const firstParam = params[0];
- const lastParam = params[params.length - 1];
- if (
- typeof firstParam !== "undefined" &&
- typeof lastParam !== "undefined" &&
- firstParam.loc &&
- lastParam.loc
- ) {
- for (let i = firstParam.loc.start.line; i <= lastParam.loc.end.line; i++) {
- paramLines.add(i);
- }
- }
- }
- return paramLines;
- },
- getReturnLines(lastReturn?: TSESTree.ReturnStatement) {
- const returnLines = new Set<number>();
- if (lastReturn) {
- const location = lastReturn.loc;
- if (location) {
- for (let i = location.start.line; i <= location.end.line; i++) {
- returnLines.add(i);
- }
- }
- }
- return returnLines;
- },
- getCommentLines(sourceCode: SourceCode, functionStartLine: number, functionEndLine: number) {
- const commentLines = new Set<number>();
- const comments = sourceCode.getAllComments();
- for (const comment of comments) {
- if (comment.loc) {
- const startLine = comment.loc.start.line;
- const endLine = comment.loc.end.line;
- if (startLine >= functionStartLine && endLine <= functionEndLine) {
- for (let i = startLine; i <= endLine; i++) {
- commentLines.add(i);
- }
- }
- }
- }
- return commentLines;
- },
- calculateFunctionLines(node: FunctionLikeNode, sourceCode: SourceCode) {
- const body = utils.getFunctionBody(node);
- if (!body || !node.loc || !body.loc) {
- return 0;
- }
- const functionStartLine = node.loc.start.line;
- const functionEndLine = node.loc.end.line;
- const params = utils.getFunctionParams(node);
- const lastReturn = utils.findLastReturnStatement(body);
- const lines = sourceCode.lines;
- const paramLines = utils.getParamLines(params);
- const returnLines = utils.getReturnLines(lastReturn);
- const commentLines = utils.getCommentLines(sourceCode, functionStartLine, functionEndLine);
- const shouldIgnoreLine = (lineNum: number, lineText: string) => {
- const trimmed = lineText.trim();
- let result = false;
- if (
- !trimmed ||
- paramLines.has(lineNum) ||
- commentLines.has(lineNum) ||
- returnLines.has(lineNum) ||
- trimmed === "{" ||
- trimmed === "}"
- ) {
- result = true;
- } else if (lineNum === functionStartLine) {
- if (
- trimmed.includes("function") ||
- trimmed.includes("=>") ||
- trimmed.includes("()") ||
- trimmed.endsWith("{")
- ) {
- result = true;
- }
- }
- return result;
- };
- let lineCount = 0;
- for (let i = functionStartLine; i <= functionEndLine; i++) {
- const line = lines[i - 1];
- if (line && !shouldIgnoreLine(i, line)) {
- lineCount++;
- }
- }
- return lineCount;
- },
- };
- const maxLinesPerFunction = createRule({
- name: constants.ruleName,
- meta: {
- type: "suggestion" as const,
- docs: {
- description:
- "Enforce a maximum number of lines in a function, " +
- "excluding parameters, return statement, comments, and empty lines",
- },
- messages: {
- tooManyLines: "Function has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}.",
- },
- schema: [
- {
- type: "object",
- properties: {
- maxLines: {
- type: "number",
- minimum: 0,
- },
- },
- additionalProperties: false,
- },
- ],
- },
- create(context: RuleContext<"tooManyLines", [Options]>) {
- const sourceCode = context.sourceCode;
- const options = context.options[0] || { maxLines: constants.defaultMaxLines };
- const maxLines = options.maxLines;
- const checkFunction = (node: FunctionLikeNode) => {
- if (
- (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") &&
- utils.isMethodFunction(node)
- ) {
- return;
- }
- const lineCount = utils.calculateFunctionLines(node, sourceCode);
- if (lineCount > maxLines) {
- context.report({
- node,
- messageId: "tooManyLines",
- data: { lineCount: lineCount.toString(), maxLines: maxLines.toString() },
- });
- }
- };
- return {
- FunctionDeclaration: checkFunction,
- FunctionExpression: checkFunction,
- ArrowFunctionExpression: checkFunction,
- MethodDefinition: checkFunction,
- };
- },
- defaultOptions: [{ maxLines: constants.defaultMaxLines }],
- });
- export default maxLinesPerFunction;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement