Skip to content

Commit e02ddd8

Browse files
authored
Merge pull request #377 from rrd108/david/feature/rrd-amount-of-comments
feat: add new rrd amount of comments rule w tests (#273)
2 parents 3041389 + 1cc4958 commit e02ddd8

10 files changed

+259
-2
lines changed

docs/.vitepress/config.ts

+1
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ export default defineConfig({
6868
{ text: 'Element Selectors in Scoped CSS', link: '/rules/vue-caution/element-selectors-with-scoped' },
6969
] },
7070
{ text: 'rrd', link: '/rules/rrd', collapsed: true, items: [
71+
{ text: 'Amount of Comments', link: '/rules/rrd/amount-of-comments' },
7172
{ text: 'Big v-if', link: '/rules/rrd/big-vif' },
7273
{ text: 'Big v-show', link: '/rules/rrd/big-vshow' },
7374
{ text: 'Complicated Conditions', link: '/rules/rrd/complicated-conditions' },

docs/rules/rrd/amount-of-comments.md

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
# Amount Of Comments
2+
3+
Checks if a component has too many comments relative to its code.
4+
5+
## ❓ Why it's good to follow this rule?
6+
7+
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.
8+
9+
## 😱 Examples of code for which this rule will throw a warning
10+
11+
::: warning
12+
The following code contains too many comments, making it difficult to understand the purpose and functionality of the component:
13+
:::
14+
15+
```vue
16+
<script setup>
17+
/*
18+
Emiting a Policy (3/4)
19+
Third, just run the necessary endpoints...
20+
and trigger policy's action modal
21+
*/
22+
function finallyEmiting() {
23+
if (quote.value.isEmitted) {
24+
isPolicyActionsOpen.value = true;
25+
return;
26+
}
27+
}
28+
29+
/*
30+
Emiting a Policy (4/4)
31+
Lastly, download your certificates and decide where to go next
32+
*/
33+
function afterParty() {}
34+
</script>
35+
```
36+
37+
## 🤩 How to fix it?
38+
39+
::: tip
40+
Add meaningful comments to explain the purpose of complex computations, important business logic, or any non-obvious code.
41+
:::
42+
43+
```vue
44+
<script setup>
45+
// First emit the current quote and trigger policy's action modal
46+
function handleEmitPolicy() {
47+
if (quote.value.isEmitted) {
48+
isPolicyActionsOpen.value = true;
49+
return;
50+
}
51+
}
52+
53+
// Lastly, download your certificates and decide where to go next
54+
function handleDownloadCertificates() {}
55+
</script>
56+
```

docs/rules/rrd/index.md

+1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
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.
44

5+
- [Amount of Comments](./amount-of-comments.md)
56
- [Big v-if](./big-vif.md)
67
- [Big v-show](./big-vshow.md)
78
- [Complicated Conditions](./complicated-conditions.md)

src/helpers/extractComments.ts

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
export function extractComments(code: string): string[] {
2+
const comments: string[] = []
3+
// eslint-disable-next-line regexp/no-useless-lazy
4+
const regex = /\/\/.*?$|\/\*[\s\S]*?\*\//gm
5+
let match
6+
7+
// eslint-disable-next-line no-cond-assign
8+
while ((match = regex.exec(code)) !== null) {
9+
comments.push(match[0])
10+
}
11+
12+
return comments
13+
}
+126
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
import type { SFCScriptBlock } from '@vue/compiler-sfc'
2+
import { describe, expect, it } from 'vitest'
3+
import { checkAmountOfComments, reportAmountOfComments } from './amountOfComments'
4+
5+
describe('checkAmountOfComments', () => {
6+
it('should not report files with comments less than 30% of total content', () => {
7+
const script = {
8+
content: `
9+
const x = 1;
10+
const y = 2;
11+
const z = 3;
12+
// This is a short comment
13+
const result = x + y + z;
14+
`,
15+
} as SFCScriptBlock
16+
const fileName = 'amounOfComments.vue'
17+
checkAmountOfComments(script, fileName)
18+
const result = reportAmountOfComments()
19+
expect(result.length).toBe(0)
20+
expect(result).toStrictEqual([])
21+
})
22+
23+
it('should not report files with no comments', () => {
24+
const script = {
25+
content: `
26+
const x = 1;
27+
const y = 2;
28+
const z = x + y;
29+
`,
30+
} as SFCScriptBlock
31+
const fileName = 'noComments.vue'
32+
checkAmountOfComments(script, fileName)
33+
const result = reportAmountOfComments()
34+
expect(result.length).toBe(0)
35+
expect(result).toStrictEqual([])
36+
})
37+
38+
it('should report files with comments more than 30% of total content', () => {
39+
const script = {
40+
content: `
41+
const x = 1;
42+
// This is a long comment
43+
// that spans multiple lines
44+
// and takes up more than 30%
45+
// of the total content
46+
// in this script block
47+
const y = 2;
48+
`,
49+
} as SFCScriptBlock
50+
const fileName = 'amounOfComments-problem.vue'
51+
checkAmountOfComments(script, fileName)
52+
const result = reportAmountOfComments()
53+
expect(result.length).toBe(1)
54+
expect(result).toStrictEqual([
55+
{
56+
file: fileName,
57+
rule: `<text_info>rrd ~ amount of comments</text_info>`,
58+
description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
59+
message: `The script block contains 62.14% comments, which exceeds the limit of 30%. 🚨`,
60+
},
61+
])
62+
})
63+
64+
it('should report files with comments more than 50% of total content', () => {
65+
const script = {
66+
content: `
67+
<script setup>
68+
/*
69+
Emiting a Policy (3/4)
70+
Third, just run the necessary endpoints...
71+
and trigger policy's action modal
72+
*/
73+
function finallyEmiting() {
74+
if (quote.value.isEmitted) {
75+
isPolicyActionsOpen.value = true;
76+
return;
77+
}
78+
}
79+
80+
/*
81+
Emiting a Policy (4/4)
82+
Lastly, download your certificates and decide where to go next
83+
*/
84+
function afterParty() {}
85+
</script>
86+
`,
87+
} as SFCScriptBlock
88+
const fileName = 'amounOfComments-problem.vue'
89+
checkAmountOfComments(script, fileName)
90+
const result = reportAmountOfComments()
91+
expect(result.length).toBe(1)
92+
expect(result).toStrictEqual([
93+
{
94+
file: fileName,
95+
rule: `<text_info>rrd ~ amount of comments</text_info>`,
96+
description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
97+
message: `The script block contains 51.47% comments, which exceeds the limit of 30%. 🚨`,
98+
},
99+
])
100+
})
101+
102+
it('should report files with only comments', () => {
103+
const script = {
104+
content: `
105+
// This file contains
106+
// only comments
107+
/*
108+
Multi-line comment
109+
test case
110+
*/
111+
`,
112+
} as SFCScriptBlock
113+
const fileName = 'onlyComments.vue'
114+
checkAmountOfComments(script, fileName)
115+
const result = reportAmountOfComments()
116+
expect(result.length).toBe(1)
117+
expect(result).toStrictEqual([
118+
{
119+
file: fileName,
120+
rule: `<text_info>rrd ~ amount of comments</text_info>`,
121+
description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
122+
message: `The script block contains 84.75% comments, which exceeds the limit of 30%. 🚨`,
123+
},
124+
])
125+
})
126+
})

src/rules/rrd/amountOfComments.ts

+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import type { SFCScriptBlock } from '@vue/compiler-sfc'
2+
import type { FileCheckResult, Offense } from '../../types'
3+
import { extractComments } from '../../helpers/extractComments'
4+
5+
const results: FileCheckResult[] = []
6+
7+
const resetResults = () => (results.length = 0)
8+
9+
const COMMENT_THRESHOLD = 30 // hardcoded limit
10+
11+
const checkAmountOfComments = (script: SFCScriptBlock | null, filePath: string) => {
12+
if (!script) {
13+
return
14+
}
15+
16+
// Extract comments from the script content
17+
const content = script.content.trim()
18+
const comments = extractComments(content)
19+
20+
// Calculate total characters in comments
21+
const totalCommentChars = comments.reduce((total, comment) => total + comment.length, 0)
22+
23+
// Calculate the total content length (including comments)
24+
const totalContentChars = content.length
25+
26+
// Calculate the percentage of comments
27+
const commentPercentage = (totalCommentChars / totalContentChars) * 100
28+
29+
if (commentPercentage > COMMENT_THRESHOLD) {
30+
results.push({
31+
filePath,
32+
message: `The script block contains ${commentPercentage.toFixed(2)}% comments, which exceeds the limit of ${COMMENT_THRESHOLD}%.`,
33+
})
34+
}
35+
}
36+
37+
const reportAmountOfComments = () => {
38+
const offenses: Offense[] = []
39+
40+
if (results.length > 0) {
41+
results.forEach((result) => {
42+
offenses.push({
43+
file: result.filePath,
44+
rule: `<text_info>rrd ~ amount of comments</text_info>`,
45+
description: `👉 <text_warn>lots of comments found</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/amount-of-comments.html`,
46+
message: `${result.message} 🚨`,
47+
})
48+
})
49+
}
50+
51+
resetResults()
52+
53+
return offenses
54+
}
55+
56+
export { checkAmountOfComments, reportAmountOfComments }

src/rules/rrd/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export * from './amountOfComments'
12
export * from './bigVif'
23
export * from './bigVshow'
34
export * from './complicatedConditions'

src/rules/rules.ts

+1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ export const RULES = {
2727
'templateSimpleExpression',
2828
],
2929
'rrd': [
30+
'amountOfComments',
3031
'bigVif',
3132
'bigVshow',
3233
'complicatedConditions',

src/rulesCheck.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { SFCDescriptor } from '@vue/compiler-sfc'
22
import type { OverrideConfig } from './types/Override'
33
import { getHasServer, getIsNuxt } from './context'
4-
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'
4+
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'
55
import { RULES } from './rules/rules'
66
import { checkApiWithoutMethod, checkRateLimiter } from './rules/security'
77
import { checkElementSelectorsWithScoped, checkImplicitParentChildCommunication } from './rules/vue-caution'
@@ -45,6 +45,7 @@ export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: s
4545
elementSelectorsWithScoped: () => isVueFile && checkElementSelectorsWithScoped(descriptor.styles, filePath),
4646

4747
// rrd
48+
amountOfComments: () => checkAmountOfComments(script, filePath),
4849
bigVif: () => checkBigVif(descriptor.template, filePath, override.maxVifLines),
4950
bigVShow: () => checkBigVshow(descriptor.template, filePath, override.maxVshowLines),
5051
complicatedConditions: () => checkComplicatedConditions(descriptor, filePath, override.warningThreshold),

src/rulesReport.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import type { GroupBy, Health, Offense, OffensesGrouped, OutputLevel, ReportFunction, SortBy } from './types'
22
import type { OverrideConfig } from './types/Override'
33
import type { ReportOutput } from './types/ReportOutput'
4-
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'
4+
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'
55
import { reportApiWithoutMethod, reportRateLimiter } from './rules/security'
66
import { reportElementSelectorsWithScoped, reportImplicitParentChildCommunication } from './rules/vue-caution'
77
import { reportGlobalStyle, reportSimpleProp, reportSingleNameComponent, reportVforNoKey, reportVifWithVfor } from './rules/vue-essential'
@@ -59,6 +59,7 @@ export const reportRules = (groupBy: GroupBy, sortBy: SortBy, level: OutputLevel
5959
processOffenses(reportElementSelectorsWithScoped)
6060

6161
// rrd rules
62+
processOffenses(reportAmountOfComments)
6263
processOffenses(reportBigVif)
6364
processOffenses(reportBigVshow)
6465
processOffenses(reportComplicatedConditions)

0 commit comments

Comments
 (0)