Repository: rrd108/vue-mess-detector (ts)

Issue:
Add rule rrd amount of comments 
We should check how much comments we have in a file and if it too much (without jsdocs) we should fire a warning 

Any number we decide will be debatablee. My suggestion is more than 5% 

Pull request description:
feat: add new rrd amount of comments rule w tests (#273)
### Summary
Added new rule amountOfComments with tests and docs

### Description
- Added rule file
- Added test file
- Added docs file

### Related Issues
Fixes #273 

### Type of Change
- [ ] Bug fix (non-breaking change which fixes an issue)
- [X] New feature (non-breaking change which adds functionality)
- [ ] Breaking change (fix or feature that would cause existing functionality to change)
- [X] Documentation update


Patch:
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index c904514..87cec67 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -68,6 +68,7 @@ export default defineConfig({
                 { text: 'Element Selectors in Scoped CSS', link: '/rules/vue-caution/element-selectors-with-scoped' },
               ] },
               { text: 'rrd', link: '/rules/rrd', collapsed: true, items: [
+                { text: 'Amount of Comments', link: '/rules/rrd/amount-of-comments' },
                 { text: 'Big v-if', link: '/rules/rrd/big-vif' },
                 { text: 'Big v-show', link: '/rules/rrd/big-vshow' },
                 { text: 'Complicated Conditions', link: '/rules/rrd/complicated-conditions' },
diff --git a/docs/rules/rrd/amount-of-comments.md b/docs/rules/rrd/amount-of-comments.md
new file mode 100644
index 0000000..3c4063e
--- /dev/null
+++ b/docs/rules/rrd/amount-of-comments.md
@@ -0,0 +1,56 @@
+# Amount Of Comments
+
+Checks if a component has too many comments relative to its code.
+
+## ❓ Why it's good to follow this rule?
+
+Maintaining an appropriate balance of comments in your code is crucial for readability and maintainability. Too many comments can make the code difficult to understand, especially for complex logic or non-obvious implementations. On the other hand, too few comments can clutter the code and potentially become outdated, leading to confusion.
+
+## 😱 Examples of code for which this rule will throw a warning
+
+::: warning
+The following code contains too many comments, making it difficult to understand the purpose and functionality of the component:
+:::
+
+```vue
+<script setup>
+/*
+  Emiting a Policy (3/4)
+  Third, just run the necessary endpoints...
+  and trigger policy's action modal
+*/
+function finallyEmiting() {
+  if (quote.value.isEmitted) {
+    isPolicyActionsOpen.value = true;
+    return;
+  }
+}
+
+/*
+  Emiting a Policy (4/4)
+  Lastly, download your certificates and decide where to go next
+*/
+function afterParty() {}
+</script>
+```
+
+## 🤩 How to fix it?
+
+::: tip
+Add meaningful comments to explain the purpose of complex computations, important business logic, or any non-obvious code.
+:::
+
+```vue
+<script setup>
+// First emit the current quote and trigger policy's action modal
+function handleEmitPolicy() {
+  if (quote.value.isEmitted) {
+    isPolicyActionsOpen.value = true;
+    return;
+  }
+}
+
+// Lastly, download your certificates and decide where to go next 
+function handleDownloadCertificates() {}
+</script>
+```
\ No newline at end of file
diff --git a/docs/rules/rrd/index.md b/docs/rules/rrd/index.md
index 1bf19b7..a25da4f 100644
--- a/docs/rules/rrd/index.md
+++ b/docs/rules/rrd/index.md
@@ -2,6 +2,7 @@
 
 These ruleset is the most opinionated with rules that are not part of the _offical_ Vue style guide. They are based on the experience of this repo owner and are meant to help you write better Vue code.
 
+- [Amount of Comments](./amount-of-comments.md)
 - [Big v-if](./big-vif.md)
 - [Big v-show](./big-vshow.md)
 - [Complicated Conditions](./complicated-conditions.md)
diff --git a/src/helpers/extractComments.ts b/src/helpers/extractComments.ts
new file mode 100644
index 0000000..1b1aace
--- /dev/null
+++ b/src/helpers/extractComments.ts
@@ -0,0 +1,13 @@
+export function extractComments(code: string): string[] {
+  const comments: string[] = []
+  // eslint-disable-next-line regexp/no-useless-lazy
+  const regex = /\/\/.*?$|\/\*[\s\S]*?\*\//gm
+  let match
+
+  // eslint-disable-next-line no-cond-assign
+  while ((match = regex.exec(code)) !== null) {
+    comments.push(match[0])
+  }
+
+  return comments
+}
diff --git a/src/rules/rrd/amountOfComments.ts b/src/rules/rrd/amountOfComments.ts
new file mode 100644
index 0000000..ed28bb3
--- /dev/null
+++ b/src/rules/rrd/amountOfComments.ts
@@ -0,0 +1,56 @@
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import type { FileCheckResult, Offense } from '../../types'
+import { extractComments } from '../../helpers/extractComments'
+
+const results: FileCheckResult[] = []
+
+const resetResults = () => (results.length = 0)
+
+const COMMENT_THRESHOLD = 30 // hardcoded limit
+
+const checkAmountOfComments = (script: SFCScriptBlock | null, filePath: string) => {
+  if (!script) {
+    return
+  }
+
+  // Extract comments from the script content
+  const content = script.content.trim()
+  const comments = extractComments(content)
+
+  // Calculate total characters in comments
+  const totalCommentChars = comments.reduce((total, comment) => total + comment.length, 0)
+
+  // Calculate the total content length (including comments)
+  const totalContentChars = content.length
+
+  // Calculate the percentage of comments
+  const commentPercentage = (totalCommentChars / totalContentChars) * 100
+
+  if (commentPercentage > COMMENT_THRESHOLD) {
+    results.push({
+      filePath,
+      message: `The script block contains ${commentPercentage.toFixed(2)}% comments, which exceeds the limit of ${COMMENT_THRESHOLD}%.`,
+    })
+  }
+}
+
+const reportAmountOfComments = () => {
+  const offenses: Offense[] = []
+
+  if (results.length > 0) {
+    results.forEach((result) => {
+      offenses.push({
+        file: result.filePath,
+        rule: `<text_info>rrd ~ amount of comments</text_info>`,
+        description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
+        message: `${result.message} 🚨`,
+      })
+    })
+  }
+
+  resetResults()
+
+  return offenses
+}
+
+export { checkAmountOfComments, reportAmountOfComments }
diff --git a/src/rules/rrd/index.ts b/src/rules/rrd/index.ts
index 07c2670..01802ec 100644
--- a/src/rules/rrd/index.ts
+++ b/src/rules/rrd/index.ts
@@ -1,3 +1,4 @@
+export * from './amountOfComments'
 export * from './bigVif'
 export * from './bigVshow'
 export * from './complicatedConditions'
diff --git a/src/rules/rules.ts b/src/rules/rules.ts
index 9b9eeb6..0623484 100644
--- a/src/rules/rules.ts
+++ b/src/rules/rules.ts
@@ -27,6 +27,7 @@ export const RULES = {
     'templateSimpleExpression',
   ],
   'rrd': [
+    'amountOfComments',
     'bigVif',
     'bigVshow',
     'complicatedConditions',
diff --git a/src/rulesCheck.ts b/src/rulesCheck.ts
index 1c3069c..3143ada 100644
--- a/src/rulesCheck.ts
+++ b/src/rulesCheck.ts
@@ -1,7 +1,7 @@
 import type { SFCDescriptor } from '@vue/compiler-sfc'
 import type { OverrideConfig } from './types/Override'
 import { getHasServer, getIsNuxt } from './context'
-import { checkBigVif, checkBigVshow, checkComplicatedConditions, checkComputedSideEffects, checkCyclomaticComplexity, checkDeepIndentation, checkElseCondition, checkFunctionSize, checkHtmlImageElements, checkHtmlLink, checkHugeFiles, checkIfWithoutCurlyBraces, checkMagicNumbers, checkNestedTernary, checkNoDirectDomAccess, checkNoInlineStyles, checkNoPropDestructure, checkNoSkippedTests, checkNoTsLang, checkNoVarDeclaration, checkParameterCount, checkPlainScript, checkPropsDrilling, checkScriptLength, checkShortVariableName, checkTooManyProps, checkVForWithIndexKey, checkZeroLengthComparison } from './rules/rrd'
+import { checkAmountOfComments, checkBigVif, checkBigVshow, checkComplicatedConditions, checkComputedSideEffects, checkCyclomaticComplexity, checkDeepIndentation, checkElseCondition, checkFunctionSize, checkHtmlImageElements, checkHtmlLink, checkHugeFiles, checkIfWithoutCurlyBraces, checkMagicNumbers, checkNestedTernary, checkNoDirectDomAccess, checkNoInlineStyles, checkNoPropDestructure, checkNoSkippedTests, checkNoTsLang, checkNoVarDeclaration, checkParameterCount, checkPlainScript, checkPropsDrilling, checkScriptLength, checkShortVariableName, checkTooManyProps, checkVForWithIndexKey, checkZeroLengthComparison } from './rules/rrd'
 import { RULES } from './rules/rules'
 import { checkApiWithoutMethod, checkRateLimiter } from './rules/security'
 import { checkElementSelectorsWithScoped, checkImplicitParentChildCommunication } from './rules/vue-caution'
@@ -45,6 +45,7 @@ export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: s
     elementSelectorsWithScoped: () => isVueFile && checkElementSelectorsWithScoped(descriptor.styles, filePath),
 
     // rrd
+    amountOfComments: () => checkAmountOfComments(script, filePath),
     bigVif: () => checkBigVif(descriptor.template, filePath, override.maxVifLines),
     bigVShow: () => checkBigVshow(descriptor.template, filePath, override.maxVshowLines),
     complicatedConditions: () => checkComplicatedConditions(descriptor, filePath, override.warningThreshold),
diff --git a/src/rulesReport.ts b/src/rulesReport.ts
index 7c786ae..7fb8f25 100644
--- a/src/rulesReport.ts
+++ b/src/rulesReport.ts
@@ -1,7 +1,7 @@
 import type { GroupBy, Health, Offense, OffensesGrouped, OutputLevel, ReportFunction, SortBy } from './types'
 import type { OverrideConfig } from './types/Override'
 import type { ReportOutput } from './types/ReportOutput'
-import { reportBigVif, reportBigVshow, reportComplicatedConditions, reportComputedSideEffects, reportCyclomaticComplexity, reportDeepIndentation, reportElseCondition, reportFunctionSize, reportHtmlImageElements, reportHtmlLink, reportHugeFiles, reportIfWithoutCurlyBraces, reportMagicNumbers, reportNestedTernary, reportNoDirectDomAccess, reportNoInlineStyles, reportNoPropDestructure, reportNoSkippedTests, reportNoTsLang, reportNoVarDeclaration, reportParameterCount, reportPlainScript, reportPropsDrilling, reportScriptLength, reportShortVariableName, reportTooManyProps, reportVForWithIndexKey, reportZeroLengthComparison } from './rules/rrd'
+import { reportAmountOfComments, reportBigVif, reportBigVshow, reportComplicatedConditions, reportComputedSideEffects, reportCyclomaticComplexity, reportDeepIndentation, reportElseCondition, reportFunctionSize, reportHtmlImageElements, reportHtmlLink, reportHugeFiles, reportIfWithoutCurlyBraces, reportMagicNumbers, reportNestedTernary, reportNoDirectDomAccess, reportNoInlineStyles, reportNoPropDestructure, reportNoSkippedTests, reportNoTsLang, reportNoVarDeclaration, reportParameterCount, reportPlainScript, reportPropsDrilling, reportScriptLength, reportShortVariableName, reportTooManyProps, reportVForWithIndexKey, reportZeroLengthComparison } from './rules/rrd'
 import { reportApiWithoutMethod, reportRateLimiter } from './rules/security'
 import { reportElementSelectorsWithScoped, reportImplicitParentChildCommunication } from './rules/vue-caution'
 import { reportGlobalStyle, reportSimpleProp, reportSingleNameComponent, reportVforNoKey, reportVifWithVfor } from './rules/vue-essential'
@@ -59,6 +59,7 @@ export const reportRules = (groupBy: GroupBy, sortBy: SortBy, level: OutputLevel
   processOffenses(reportElementSelectorsWithScoped)
 
   // rrd rules
+  processOffenses(reportAmountOfComments)
   processOffenses(reportBigVif)
   processOffenses(reportBigVshow)
   processOffenses(reportComplicatedConditions)


Test patch:
diff --git a/src/rules/rrd/amountOfComments.test.ts b/src/rules/rrd/amountOfComments.test.ts
new file mode 100644
index 0000000..9a69278
--- /dev/null
+++ b/src/rules/rrd/amountOfComments.test.ts
@@ -0,0 +1,126 @@
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { describe, expect, it } from 'vitest'
+import { checkAmountOfComments, reportAmountOfComments } from './amountOfComments'
+
+describe('checkAmountOfComments', () => {
+  it('should not report files with comments less than 30% of total content', () => {
+    const script = {
+      content: `
+        const x = 1;
+        const y = 2;
+        const z = 3;
+        // This is a short comment
+        const result = x + y + z;
+      `,
+    } as SFCScriptBlock
+    const fileName = 'amounOfComments.vue'
+    checkAmountOfComments(script, fileName)
+    const result = reportAmountOfComments()
+    expect(result.length).toBe(0)
+    expect(result).toStrictEqual([])
+  })
+
+  it('should not report files with no comments', () => {
+    const script = {
+      content: `
+        const x = 1;
+        const y = 2;
+        const z = x + y;
+      `,
+    } as SFCScriptBlock
+    const fileName = 'noComments.vue'
+    checkAmountOfComments(script, fileName)
+    const result = reportAmountOfComments()
+    expect(result.length).toBe(0)
+    expect(result).toStrictEqual([])
+  })
+
+  it('should report files with comments more than 30% of total content', () => {
+    const script = {
+      content: `
+        const x = 1;
+        // This is a long comment
+        // that spans multiple lines
+        // and takes up more than 30%
+        // of the total content
+        // in this script block
+        const y = 2;
+      `,
+    } as SFCScriptBlock
+    const fileName = 'amounOfComments-problem.vue'
+    checkAmountOfComments(script, fileName)
+    const result = reportAmountOfComments()
+    expect(result.length).toBe(1)
+    expect(result).toStrictEqual([
+      {
+        file: fileName,
+        rule: `<text_info>rrd ~ amount of comments</text_info>`,
+        description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
+        message: `The script block contains 62.14% comments, which exceeds the limit of 30%. 🚨`,
+      },
+    ])
+  })
+
+  it('should report files with comments more than 50% of total content', () => {
+    const script = {
+      content: `
+        <script setup>
+        /*
+          Emiting a Policy (3/4)
+          Third, just run the necessary endpoints...
+          and trigger policy's action modal
+        */
+        function finallyEmiting() {
+          if (quote.value.isEmitted) {
+            isPolicyActionsOpen.value = true;
+            return;
+          }
+        }
+
+        /*
+          Emiting a Policy (4/4)
+          Lastly, download your certificates and decide where to go next
+        */
+        function afterParty() {}
+        </script>
+      `,
+    } as SFCScriptBlock
+    const fileName = 'amounOfComments-problem.vue'
+    checkAmountOfComments(script, fileName)
+    const result = reportAmountOfComments()
+    expect(result.length).toBe(1)
+    expect(result).toStrictEqual([
+      {
+        file: fileName,
+        rule: `<text_info>rrd ~ amount of comments</text_info>`,
+        description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
+        message: `The script block contains 51.47% comments, which exceeds the limit of 30%. 🚨`,
+      },
+    ])
+  })
+
+  it('should report files with only comments', () => {
+    const script = {
+      content: `
+        // This file contains
+        // only comments
+        /* 
+          Multi-line comment
+          test case
+        */
+      `,
+    } as SFCScriptBlock
+    const fileName = 'onlyComments.vue'
+    checkAmountOfComments(script, fileName)
+    const result = reportAmountOfComments()
+    expect(result.length).toBe(1)
+    expect(result).toStrictEqual([
+      {
+        file: fileName,
+        rule: `<text_info>rrd ~ amount of comments</text_info>`,
+        description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
+        message: `The script block contains 84.75% comments, which exceeds the limit of 30%. 🚨`,
+      },
+    ])
+  })
+})