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

Issue:
add rule (rrd) comparison to `0`
```js
if (results.length > 0) {
```

should be

```js
if (results.length) {
```


Pull request:
feat: add new rule rrd zero-length-comparison w tests
### Summary
Added a new rule "Zero Length Comparison" with tests and docs.

### Description
- Added zeroLengthComparison file and tests file
- Update docs to include this new rule in its sidebar
- Create rule details page in docs with example codes

### Related Issues
Fixes #152 

### 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 23d2ec4..a901526 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -79,7 +79,8 @@ export default defineConfig({
                 { text: 'Script Length', link: '/rules/rrd/script-length' },
                 { text: 'Short Variable Name', link: '/rules/rrd/short-variable-name' },
                 { text: 'Too Many Props', link: '/rules/rrd/too-many-props' },
-                { text: 'VFor with Index Key', link: '/rules/rrd/v-for-with-index-key' }
+                { text: 'VFor with Index Key', link: '/rules/rrd/v-for-with-index-key' },
+                { text: 'Zero Length Comparison', link: '/rules/rrd/zero-length-comparison' },
               ] },
             ],
           },
diff --git a/docs/rules/rrd/index.md b/docs/rules/rrd/index.md
index 2bca91a..8588660 100644
--- a/docs/rules/rrd/index.md
+++ b/docs/rules/rrd/index.md
@@ -17,6 +17,7 @@ These ruleset is the most opinionated with rules that are not part of the *offic
 - [Short Variable Name](./short-variable-name.md)
 - [Too Many Props](./too-many-props.md)
 - [VFor with Index Key](./v-for-with-index-key.md)
+- [Zero Length Comparison](./zero-length-comparison.md)
 
 ## ❓ Why it's good to follow this ruleset?
 
diff --git a/docs/rules/rrd/zero-length-comparison.md b/docs/rules/rrd/zero-length-comparison.md
new file mode 100644
index 0000000..639ebe2
--- /dev/null
+++ b/docs/rules/rrd/zero-length-comparison.md
@@ -0,0 +1,37 @@
+# Zero Length Comparison
+
+Check if there is an array or string comparing its length to zero.
+
+## 📖 What is Zero Length Comparison?
+
+Zero Length Comparison refers to the practice of checking if an array or string has any elements by comparing its length to zero (e.g.,` array.length > 0`). While this approach is technically correct, it's not the most idiomatic or efficient way to perform this check in JavaScript.
+
+## ❓ Why it's good to follow this rule?
+
+- **Simplicity:** Using `array.length` directly as a condition (e.g., `if (array.length)`) is simpler and more concise than comparing it to zero. In JavaScript, any number greater than 0 is truthy, so this shorter form achieves the same result.
+- **Readability:** The simplified form `if (array.length)` is more idiomatic in JavaScript and immediately conveys the intent to check if the array is non-empty. This makes the code more readable to experienced JavaScript developers.
+- **Consistency:** Adopting this rule promotes consistent coding practices across your project. When all developers follow the same pattern for checking non-empty arrays or strings, it becomes easier to read and maintain the codebase.
+
+## 😱 Examples of code for which this rule will throw a warning
+
+::: warning
+The following code uses an explicit comparison to zero when checking for a non-empty array. This is unnecessarily verbose and less idiomatic in JavaScript.
+:::
+
+```js
+if (results.length > 0) {
+  console.log('You Won')
+}
+```
+
+## 🤩 How to fix it?
+
+::: tip
+Simplify the condition by using the array's length property directly as a boolean condition. In JavaScript, any number greater than 0 is truthy, so this achieves the same result more concisely.
+:::
+
+```js
+if (results.length) {
+  console.log('You Won')
+}
+```
diff --git a/src/rules/rrd/zeroLengthComparison.ts b/src/rules/rrd/zeroLengthComparison.ts
new file mode 100644
index 0000000..02028a2
--- /dev/null
+++ b/src/rules/rrd/zeroLengthComparison.ts
@@ -0,0 +1,47 @@
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import getLineNumber from '../getLineNumber'
+import type { FileCheckResult, Offense } from '../../types'
+
+const results: FileCheckResult[] = []
+
+const checkZeroLengthComparison = (script: SFCScriptBlock | null, filePath: string) => {
+  if (!script) {
+    return
+  }
+
+  const regex = /(\w+(?:\.\w+)*)\.length\s*>\s*0/g
+
+  let match
+  // eslint-disable-next-line no-cond-assign
+  while ((match = regex.exec(script.content)) !== null) {
+    const fullMatch = match[0]
+    const arrayName = match[1]
+
+    const lineNumber = getLineNumber(script.content.trim(), fullMatch)
+    results.push({
+      filePath,
+      message: `line #${lineNumber} zero length comparison found ${BG_WARN}(${arrayName})${BG_RESET}`,
+    })
+  }
+}
+
+const reportZeroLengthComparison = () => {
+  const offenses: Offense[] = []
+
+  if (results.length > 0) {
+    results.forEach((result) => {
+      offenses.push({
+        file: result.filePath,
+        rule: `${TEXT_INFO}rrd ~ Zero Length Comparison${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}In JavaScript, any number greater than 0 is truthy, so you can directly use the length property.${TEXT_RESET} See: https://vue-mess-detector.webmania.cc/rules/rrd/zero-length-comparison.html`,
+        message: `${result.message} 🚨`,
+      })
+    })
+  }
+  return offenses
+}
+
+const resetZeroLengthComparison = () => (results.length = 0)
+
+export { checkZeroLengthComparison, reportZeroLengthComparison, resetZeroLengthComparison }
diff --git a/src/rules/rules.ts b/src/rules/rules.ts
index b303779..c28208d 100644
--- a/src/rules/rules.ts
+++ b/src/rules/rules.ts
@@ -41,7 +41,8 @@ export const RULES = {
     'scriptLength',
     'shortVariableName',
     'tooManyProps',
-    'vForWithIndexKey'
+    'vForWithIndexKey',
+    'zeroLengthComparison'
   ]
 };
 export type RuleType = typeof RULES
diff --git a/src/rulesCheck.ts b/src/rulesCheck.ts
index 5e7087f..9b1ff61 100644
--- a/src/rulesCheck.ts
+++ b/src/rulesCheck.ts
@@ -34,6 +34,7 @@ import { checkMultiAttributeElements } from './rules/vue-strong/multiAttributeEl
 import { checkIfWithoutCurlyBraces } from './rules/rrd/ifWithoutCurlyBraces'
 import { checkNestedTernary } from './rules/rrd/nestedTernary'
 import { checkVForWithIndexKey } from './rules/rrd/vForWithIndexKey'
+import { checkZeroLengthComparison } from './rules/rrd/zeroLengthComparison'
 
 export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: Array<RuleSetType>) => {
   const script = descriptor.scriptSetup || descriptor.script
@@ -100,6 +101,7 @@ export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: A
     checkScriptLength(script, filePath)
     checkShortVariableName(script, filePath)
     checkTooManyProps(script, filePath)
+    checkZeroLengthComparison(script, filePath)
 
     if (isVueFile) {
       checkHtmlLink(descriptor.template, filePath)
diff --git a/src/rulesReport.ts b/src/rulesReport.ts
index 3c5ea91..f1fd6e6 100644
--- a/src/rulesReport.ts
+++ b/src/rulesReport.ts
@@ -34,6 +34,7 @@ import { reportElementSelectorsWithScoped } from './rules/vue-caution/elementSel
 import { reportNestedTernary } from './rules/rrd/nestedTernary'
 import { reportVForWithIndexKey } from './rules/rrd/vForWithIndexKey'
 import { BG_ERR } from './rules/asceeCodes'
+import { reportZeroLengthComparison } from './rules/rrd/zeroLengthComparison'
 
 export const reportRules = (groupBy: GroupBy, orderBy: OrderBy, level: OutputLevel) => {
   const offensesGrouped: OffensesGrouped = {}
@@ -99,6 +100,7 @@ export const reportRules = (groupBy: GroupBy, orderBy: OrderBy, level: OutputLev
   processOffenses(reportShortVariableName)
   processOffenses(reportTooManyProps)
   processOffenses(reportVForWithIndexKey)
+  processOffenses(reportZeroLengthComparison)
 
   const health: Health[] = []
 
@@ -117,16 +119,18 @@ export const reportRules = (groupBy: GroupBy, orderBy: OrderBy, level: OutputLev
   // Output the report grouped by the sorted keys
   sortedKeys.forEach((key) => {
     console.log(`\n - ${key}`)
-    
+
     offensesGrouped[key].forEach((offense) => {
       const isError = offense.message.includes(BG_ERR)
       // if health already has the file, push the error
       if (health.some(h => h.file === offense.file)) {
         const foundHealth = health.find(h => h.file === offense.file)
         if (foundHealth) {
+          // eslint-disable-next-line ts/no-unused-expressions
           isError ? foundHealth.errors++ : foundHealth.warnings++
         }
-      } else {
+      }
+      else {
         health.push({ file: offense.file, errors: isError ? 1 : 0, warnings: isError ? 0 : 1 })
       }
 
@@ -136,7 +140,8 @@ export const reportRules = (groupBy: GroupBy, orderBy: OrderBy, level: OutputLev
 
       if (groupBy === 'file') {
         console.log(`   Rule: ${offense.rule}`)
-      } else {
+      }
+      else {
         console.log(`   File: ${offense.file}`)
       }
       console.log(`   Description: ${offense.description}`)
@@ -145,4 +150,4 @@ export const reportRules = (groupBy: GroupBy, orderBy: OrderBy, level: OutputLev
   })
 
   return health
-}
\ No newline at end of file
+}


Test patch:
diff --git a/src/rules/rrd/zeroLengthComparison.test.ts b/src/rules/rrd/zeroLengthComparison.test.ts
new file mode 100644
index 0000000..ed637dc
--- /dev/null
+++ b/src/rules/rrd/zeroLengthComparison.test.ts
@@ -0,0 +1,77 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkZeroLengthComparison, reportZeroLengthComparison, resetZeroLengthComparison } from './zeroLengthComparison'
+
+describe('checkZeroLengthComparison', () => {
+  beforeEach(() => {
+    resetZeroLengthComparison()
+  })
+
+  it('should not report files with none zero-length comparison', () => {
+    const script = {
+      content: `
+        <script setup>
+        if (results.length) {
+          console.log('You Won')
+        }
+        </script>
+      `,
+    } as SFCScriptBlock
+    const fileName = 'zeroLengthComparison.vue'
+    checkZeroLengthComparison(script, fileName)
+    expect(reportZeroLengthComparison().length).toBe(0)
+    expect(reportZeroLengthComparison()).toStrictEqual([])
+  })
+
+  it('should report files with with zero-length comparison in script', () => {
+    const script = {
+      content: `
+          <script setup>
+          if (results.length > 0) {
+            console.log('You Won')
+          }
+          </script>
+        `,
+    } as SFCScriptBlock
+    const fileName = 'zeroLengthComparison-problem.vue'
+    checkZeroLengthComparison(script, fileName)
+    expect(reportZeroLengthComparison().length).toBe(1)
+    expect(reportZeroLengthComparison()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ Zero Length Comparison${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}In JavaScript, any number greater than 0 is truthy, so you can directly use the length property.${TEXT_RESET} See: https://vue-mess-detector.webmania.cc/rules/rrd/zero-length-comparison.html`,
+      message: `line #2 zero length comparison found ${BG_WARN}(results)${BG_RESET} 🚨`,
+    }])
+  })
+
+  it('should report files with with zero-length comparison in template', () => {
+    const script = {
+      content: `
+          <script setup>
+          import { ref } from 'vue';
+
+          const title = ref('Welcome, check the winner below')
+          const trophies = ref([])
+
+          /* other logic here... */
+          </script>
+
+          <template>
+            <div v-if="trophies.length > 0">
+              <!-- Some Html Tags -->
+            </div>
+          </template>
+        `,
+    } as SFCScriptBlock
+    const fileName = 'zeroLengthComparison-problem.vue'
+    checkZeroLengthComparison(script, fileName)
+    expect(reportZeroLengthComparison().length).toBe(1)
+    expect(reportZeroLengthComparison()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ Zero Length Comparison${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}In JavaScript, any number greater than 0 is truthy, so you can directly use the length property.${TEXT_RESET} See: https://vue-mess-detector.webmania.cc/rules/rrd/zero-length-comparison.html`,
+      message: `line #11 zero length comparison found ${BG_WARN}(trophies)${BG_RESET} 🚨`,
+    }])
+  })
+})