Advertisement
djbob2000

Untitled

Jul 8th, 2025
98
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";
  2. import { RuleContext, SourceCode } from "@typescript-eslint/utils/ts-eslint";
  3.  
  4. const constants = {
  5.     url: "https://example.com/rules/",
  6.     ruleName: "max-lines-per-function",
  7.     messageIds: {
  8.         tooManyLines: "tooManyLines",
  9.     },
  10.     defaultMaxLines: 60,
  11. };
  12.  
  13. const createRule = ESLintUtils.RuleCreator((ruleName: string) => `${constants.url}${ruleName}`);
  14.  
  15. type FunctionLikeNode =
  16.     | TSESTree.FunctionDeclaration
  17.     | TSESTree.FunctionExpression
  18.     | TSESTree.ArrowFunctionExpression
  19.     | TSESTree.MethodDefinition;
  20.  
  21. interface Options {
  22.     maxLines: number;
  23. }
  24.  
  25. const utils = {
  26.     isFunctionLike(node: TSESTree.Node) {
  27.         return (
  28.             node.type === "FunctionDeclaration" ||
  29.             node.type === "FunctionExpression" ||
  30.             node.type === "ArrowFunctionExpression" ||
  31.             node.type === "MethodDefinition"
  32.         );
  33.     },
  34.  
  35.     getFunctionBody(node: FunctionLikeNode) {
  36.         let body: TSESTree.BlockStatement | null = null;
  37.  
  38.         if (node.type === "MethodDefinition") {
  39.             body = node.value.body;
  40.         } else if (node.type === "ArrowFunctionExpression" && node.body?.type === "BlockStatement") {
  41.             body = node.body;
  42.         } else if (node.body?.type === "BlockStatement") {
  43.             body = node.body;
  44.         }
  45.  
  46.         return body;
  47.     },
  48.  
  49.     getFunctionParams(node: FunctionLikeNode) {
  50.         const params = node.type === "MethodDefinition" ? node.value.params : node.params;
  51.         return params;
  52.     },
  53.  
  54.     isMethodFunction(node: TSESTree.Node) {
  55.         return !!node.parent && node.parent.type === "MethodDefinition";
  56.     },
  57.  
  58.     findLastReturnStatement(body: TSESTree.BlockStatement) {
  59.         let lastReturn: TSESTree.ReturnStatement | undefined;
  60.  
  61.         const handleIfStatement = (node: TSESTree.IfStatement) => {
  62.             if (node.consequent.type === "BlockStatement") {
  63.                 traverse(node.consequent.body);
  64.             }
  65.  
  66.             if (node.alternate) {
  67.                 if (node.alternate.type === "BlockStatement") {
  68.                     traverse(node.alternate.body);
  69.                 } else if (node.alternate.type === "IfStatement") {
  70.                     traverse([node.alternate]);
  71.                 }
  72.             }
  73.         };
  74.  
  75.         const handleLoop = (node: TSESTree.Statement) => {
  76.             if (
  77.                 node.type === "WhileStatement" ||
  78.                 node.type === "ForStatement" ||
  79.                 node.type === "ForInStatement" ||
  80.                 node.type === "ForOfStatement"
  81.             ) {
  82.                 if (node.body.type === "BlockStatement") {
  83.                     traverse(node.body.body);
  84.                 }
  85.             }
  86.         };
  87.  
  88.         const handleTryStatement = (node: TSESTree.TryStatement) => {
  89.             traverse(node.block.body);
  90.  
  91.             if (node.handler) {
  92.                 traverse(node.handler.body.body);
  93.             }
  94.  
  95.             if (node.finalizer) {
  96.                 traverse(node.finalizer.body);
  97.             }
  98.         };
  99.  
  100.         const traverse = (nodes: TSESTree.Statement[]) => {
  101.             for (const node of nodes) {
  102.                 if (node.type === "ReturnStatement") {
  103.                     lastReturn = node;
  104.                 } else if (node.type === "BlockStatement") {
  105.                     traverse(node.body);
  106.                 } else if (node.type === "IfStatement") {
  107.                     handleIfStatement(node);
  108.                 } else if (node.type === "SwitchStatement") {
  109.                     for (const switchCase of node.cases) {
  110.                         traverse(switchCase.consequent);
  111.                     }
  112.                 } else if (node.type === "TryStatement") {
  113.                     handleTryStatement(node);
  114.                 } else {
  115.                     handleLoop(node);
  116.                 }
  117.             }
  118.         };
  119.  
  120.         traverse(body.body);
  121.         return lastReturn;
  122.     },
  123.  
  124.     getParamLines(params: TSESTree.Parameter[]) {
  125.         const paramLines = new Set<number>();
  126.  
  127.         if (params.length > 0) {
  128.             const firstParam = params[0];
  129.             const lastParam = params[params.length - 1];
  130.  
  131.             if (
  132.                 typeof firstParam !== "undefined" &&
  133.                 typeof lastParam !== "undefined" &&
  134.                 firstParam.loc &&
  135.                 lastParam.loc
  136.             ) {
  137.                 for (let i = firstParam.loc.start.line; i <= lastParam.loc.end.line; i++) {
  138.                     paramLines.add(i);
  139.                 }
  140.             }
  141.         }
  142.  
  143.         return paramLines;
  144.     },
  145.  
  146.     getReturnLines(lastReturn?: TSESTree.ReturnStatement) {
  147.         const returnLines = new Set<number>();
  148.  
  149.         if (lastReturn) {
  150.             const location = lastReturn.loc;
  151.  
  152.             if (location) {
  153.                 for (let i = location.start.line; i <= location.end.line; i++) {
  154.                     returnLines.add(i);
  155.                 }
  156.             }
  157.         }
  158.  
  159.         return returnLines;
  160.     },
  161.  
  162.     getCommentLines(sourceCode: SourceCode, functionStartLine: number, functionEndLine: number) {
  163.         const commentLines = new Set<number>();
  164.         const comments = sourceCode.getAllComments();
  165.  
  166.         for (const comment of comments) {
  167.             if (comment.loc) {
  168.                 const startLine = comment.loc.start.line;
  169.                 const endLine = comment.loc.end.line;
  170.  
  171.                 if (startLine >= functionStartLine && endLine <= functionEndLine) {
  172.                     for (let i = startLine; i <= endLine; i++) {
  173.                         commentLines.add(i);
  174.                     }
  175.                 }
  176.             }
  177.         }
  178.  
  179.         return commentLines;
  180.     },
  181.  
  182.     calculateFunctionLines(node: FunctionLikeNode, sourceCode: SourceCode) {
  183.         const body = utils.getFunctionBody(node);
  184.  
  185.         if (!body || !node.loc || !body.loc) {
  186.             return 0;
  187.         }
  188.  
  189.         const functionStartLine = node.loc.start.line;
  190.         const functionEndLine = node.loc.end.line;
  191.         const params = utils.getFunctionParams(node);
  192.         const lastReturn = utils.findLastReturnStatement(body);
  193.         const lines = sourceCode.lines;
  194.  
  195.         const paramLines = utils.getParamLines(params);
  196.         const returnLines = utils.getReturnLines(lastReturn);
  197.         const commentLines = utils.getCommentLines(sourceCode, functionStartLine, functionEndLine);
  198.  
  199.         const shouldIgnoreLine = (lineNum: number, lineText: string) => {
  200.             const trimmed = lineText.trim();
  201.             let result = false;
  202.  
  203.             if (
  204.                 !trimmed ||
  205.                 paramLines.has(lineNum) ||
  206.                 commentLines.has(lineNum) ||
  207.                 returnLines.has(lineNum) ||
  208.                 trimmed === "{" ||
  209.                 trimmed === "}"
  210.             ) {
  211.                 result = true;
  212.             } else if (lineNum === functionStartLine) {
  213.                 if (
  214.                     trimmed.includes("function") ||
  215.                     trimmed.includes("=>") ||
  216.                     trimmed.includes("()") ||
  217.                     trimmed.endsWith("{")
  218.                 ) {
  219.                     result = true;
  220.                 }
  221.             }
  222.  
  223.             return result;
  224.         };
  225.  
  226.         let lineCount = 0;
  227.  
  228.         for (let i = functionStartLine; i <= functionEndLine; i++) {
  229.             const line = lines[i - 1];
  230.  
  231.             if (line && !shouldIgnoreLine(i, line)) {
  232.                 lineCount++;
  233.             }
  234.         }
  235.  
  236.         return lineCount;
  237.     },
  238. };
  239.  
  240. const maxLinesPerFunction = createRule({
  241.     name: constants.ruleName,
  242.     meta: {
  243.         type: "suggestion" as const,
  244.         docs: {
  245.             description:
  246.                 "Enforce a maximum number of lines in a function, " +
  247.                 "excluding parameters, return statement, comments, and empty lines",
  248.         },
  249.         messages: {
  250.             tooManyLines: "Function has too many lines ({{lineCount}}). Maximum allowed is {{maxLines}}.",
  251.         },
  252.         schema: [
  253.             {
  254.                 type: "object",
  255.                 properties: {
  256.                     maxLines: {
  257.                         type: "number",
  258.                         minimum: 0,
  259.                     },
  260.                 },
  261.                 additionalProperties: false,
  262.             },
  263.         ],
  264.     },
  265.     create(context: RuleContext<"tooManyLines", [Options]>) {
  266.         const sourceCode = context.sourceCode;
  267.         const options = context.options[0] || { maxLines: constants.defaultMaxLines };
  268.         const maxLines = options.maxLines;
  269.  
  270.         const checkFunction = (node: FunctionLikeNode) => {
  271.             if (
  272.                 (node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression") &&
  273.                 utils.isMethodFunction(node)
  274.             ) {
  275.                 return;
  276.             }
  277.  
  278.             const lineCount = utils.calculateFunctionLines(node, sourceCode);
  279.  
  280.             if (lineCount > maxLines) {
  281.                 context.report({
  282.                     node,
  283.                     messageId: "tooManyLines",
  284.                     data: { lineCount: lineCount.toString(), maxLines: maxLines.toString() },
  285.                 });
  286.             }
  287.         };
  288.  
  289.         return {
  290.             FunctionDeclaration: checkFunction,
  291.             FunctionExpression: checkFunction,
  292.             ArrowFunctionExpression: checkFunction,
  293.             MethodDefinition: checkFunction,
  294.         };
  295.     },
  296.     defaultOptions: [{ maxLines: constants.defaultMaxLines }],
  297. });
  298.  
  299. export default maxLinesPerFunction;
  300.  
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement