Repository: rrd108/vue-mess-detector
Pull request #60 (2024-07-16 05:28:38)

feat: add rrd parameter-count rule


Patch:
diff --git a/README.md b/README.md
index ab085f9..deaf51a 100644
--- a/README.md
+++ b/README.md
@@ -149,3 +149,7 @@ Checks if the component got more then 5 props.
 ### Function Size (rrd)
 
 Checks if functions inside `<script setup>` block are less than 20 lines of code. It handles regular and arrow functions.
+
+### Parameter Count (rrd)
+
+Checks if functions inside `<script setup>` block have less than 4 parameters. It handles regular and arrow functions.
diff --git a/src/analyzer.ts b/src/analyzer.ts
index df41bfd..ef68f42 100644
--- a/src/analyzer.ts
+++ b/src/analyzer.ts
@@ -19,6 +19,7 @@ import { checkSelfClosingComponents, reportSelfClosingComponents } from './rules
 import { checkDirectiveShorthands, reportDirectiveShorthands } from './rules/vue-strong/directiveShorthands'
 import { checkTooManyProps, reportTooManyProps } from './rules/rrd/tooManyProps'
 import { checkFunctionSize, reportFunctionSize } from './rules/rrd/functionSize'
+import { checkParameterCount, reportParameterCount } from './rules/rrd/parameterCount'
 
 let filesCount = 0
 
@@ -84,6 +85,7 @@ export const analyze = (dir: string) => {
       checkElseCondition(script, filePath)
       checkTooManyProps(script, filePath)
       checkFunctionSize(script, filePath)
+      checkParameterCount(script, filePath)
     }
 
     descriptor.styles.forEach(style => {
@@ -128,6 +130,7 @@ export const analyze = (dir: string) => {
   errors += reportElseCondition()
   errors += reportTooManyProps()
   errors += reportFunctionSize()
+  errors += reportParameterCount()
 
   if (!errors) {
     console.log(`${BG_OK}No code smells detected!${BG_RESET}`)
diff --git a/src/rules/rrd/parameterCount.ts b/src/rules/rrd/parameterCount.ts
new file mode 100644
index 0000000..44ee16a
--- /dev/null
+++ b/src/rules/rrd/parameterCount.ts
@@ -0,0 +1,44 @@
+import { SFCScriptBlock } from '@vue/compiler-sfc';
+import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
+
+const parameterCountFiles: { filename: string, funcName: string, paramsCount: number }[] = [];
+
+const MAX_PARAMETER_COUNT = 3 // completely rrd made-up number
+
+// Function used in both scenarios (regular and arrow function) to count parameters
+const checkParameters = (funcName: string, params: string, filePath: string) => {
+  const paramsArray = params.split(',').map(param => param.trim()).filter(param => param.length > 0);
+  if (paramsArray.length > MAX_PARAMETER_COUNT) {
+    parameterCountFiles.push({ filename: filePath, funcName, paramsCount: paramsArray.length });
+  }
+}
+
+const checkParameterCount = (script: SFCScriptBlock, filePath: string) => {
+  // regular expression to match both regular and arrow functions and capture their params
+  const regex = /function\s+([a-zA-Z0-9_$]+)\s*\(([^)]*)\)\s*{|const\s+([a-zA-Z0-9_$]+)\s*=\s*\(([^)]*)\)\s*=>\s*{/g;
+  let match;
+
+  while ((match = regex.exec(script.content)) !== null) {
+    if (match[1]) { // Regular function
+      checkParameters(match[1], match[2], filePath); // match[2] are the params for current regular function
+    } else if (match[3]) { // Arrow function
+      checkParameters(match[3], match[4], filePath) // match[4] are the params for current arrow function
+    }
+  }
+}
+
+const reportParameterCount = () => {
+  if (parameterCountFiles.length > 0) {
+    console.log(
+      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}parameter count${BG_RESET} exceeds recommended limit in ${parameterCountFiles.length} files.`
+    )
+    console.log(`👉 ${TEXT_WARN}Max number of function parameters should be ${MAX_PARAMETER_COUNT}${TEXT_RESET}`);
+    parameterCountFiles.forEach(file => {
+      console.log(`- ${BG_WARN}${file.funcName}${BG_RESET} in file ${file.filename} 🚨 ${BG_WARN}(${file.paramsCount})${BG_RESET}`);
+    })
+  }
+  
+  return parameterCountFiles.length;
+}
+
+export { checkParameterCount, reportParameterCount };
\ No newline at end of file


Test patch:
diff --git a/src/rules/rrd/parameterCount.test.ts b/src/rules/rrd/parameterCount.test.ts
new file mode 100644
index 0000000..328036e
--- /dev/null
+++ b/src/rules/rrd/parameterCount.test.ts
@@ -0,0 +1,73 @@
+import { describe, expect, it, vi } from 'vitest'
+import { SFCScriptBlock } from '@vue/compiler-sfc'
+import { checkParameterCount, reportParameterCount } from './parameterCount'
+import { BG_RESET, BG_WARN } from '../asceeCodes'
+
+const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+
+describe('checkParameterCount', () => {
+  it('should not report files where functions do not exceed the recommended limit', () => {
+    const script = {
+      content: `
+        <script setup>
+          function dummyFuncOne() {
+            return 'One'
+          }
+        </script>
+      `
+    } as SFCScriptBlock;
+    const filename = 'no-parameters.vue';
+    checkParameterCount(script, filename);
+    expect(reportParameterCount()).toBe(0);
+    expect(mockConsoleLog).not.toHaveBeenCalled();
+  })
+
+  it('should report files where one function exceeds the recommended limit', () => {
+    const script = {
+      content: `
+        <script setup>
+          function dummyFuncOne() {
+            return 'One'
+          }
+
+          const dummyFuncTwo = (param1, param2, param3, param4) => {}
+        </script>
+      `
+    } as SFCScriptBlock;
+    const filename = 'one-parameter.vue';
+    const funcName = 'dummyFuncTwo';
+    const paramsCount = 4;
+    checkParameterCount(script, filename);
+    expect(reportParameterCount()).toBe(1);
+    expect(mockConsoleLog).toHaveBeenCalled();
+    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${BG_WARN}${funcName}${BG_RESET} in file ${filename} 🚨 ${BG_WARN}(${paramsCount})${BG_RESET}`)
+    
+  })
+
+  it('should report files where multiple functions exceed the recommended limit', () => {
+    const script = {
+      content: `
+        <script setup>
+          function dummyFuncOne(param1, param2, param3, param4, param5) {
+            return 'One'
+          }
+
+          const dummyFuncTwo = (param1, param2, param3, param4) => {
+            return 'Two'
+          }
+
+          const dummyFuncThree = (param1, param2, param3) => {
+            return 'Three'
+          }
+        </script>
+      `
+    } as SFCScriptBlock;
+    const filename = 'multiple-parameters.vue';
+    const funcName = 'dummyFuncTwo';
+    const paramsCount = 4;
+    checkParameterCount(script, filename);
+    expect(reportParameterCount()).toBe(3);
+    expect(mockConsoleLog).toHaveBeenCalled();
+    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${BG_WARN}${funcName}${BG_RESET} in file ${filename} 🚨 ${BG_WARN}(${paramsCount})${BG_RESET}`)
+  })
+})
\ No newline at end of file