Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add new rule rrd expressioninvfor #385

Merged
merged 7 commits into from
Oct 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ 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 Expression', link: '/rules/rrd/v-for-expression' },
{ text: 'VFor with Index Key', link: '/rules/rrd/v-for-with-index-key' },
{ text: 'Zero Length Comparison', link: '/rules/rrd/zero-length-comparison' },
] },
Expand Down
2 changes: 1 addition & 1 deletion docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ features:
- title: 🧲 The Rules You Want
details: Run all checks or ignore the ones you don't like, or apply only the ones you want
- title: 🔩 Focus on Important Rules
details: 40+ rules supported
details: 50+ rules supported
---

<span id="morphBlur"></span>
Expand Down
1 change: 1 addition & 0 deletions docs/rules/rrd/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ These ruleset is the most opinionated with rules that are not part of the _offic
- [Script Length](./script-length.md)
- [Short Variable Name](./short-variable-name.md)
- [Too Many Props](./too-many-props.md)
- [VFor Expression](./v-for-expression.md)
- [VFor with Index Key](./v-for-with-index-key.md)
- [Zero Length Comparison](./zero-length-comparison.md)

Expand Down
41 changes: 41 additions & 0 deletions docs/rules/rrd/v-for-expression.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Vfor Expression

Checks if the `v-for` expression is using an expression.

## ❓ Why it's good to follow this rule?

- **Readability:** Expressions in `v-for` make the code harder to read and understand.
- **Maintainability:** Expressions in `v-for` make the code harder to maintain.
- **Performance:** Expressions in `v-for` make the code harder to optimize.

## 😱 Examples of code for which this rule will throw a warning

::: warning
The following code contains an expression in the `v-for` directive.
:::

```vue
<template>
<div v-for="item in items.filter((config) => config.level === 2)" :key="item.id">
{{ item.name }}
</div>
</template>
```

## 🤩 How to fix it?

::: tip
Refactor the code to move the expression to a calculated property.
:::

```vue
<script setup>
const configItems = computed(() => items.filter((config) => config.level === 2))
</script>

<template>
<div v-for="item in configItems" :key="item.id">
{{ item.name }}
</div>
</template>
```
1 change: 1 addition & 0 deletions src/rules/rrd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,5 +25,6 @@ export * from './propsDrilling'
export * from './scriptLength'
export * from './shortVariableName'
export * from './tooManyProps'
export * from './vForExpression'
export * from './vForWithIndexKey'
export * from './zeroLengthComparison'
62 changes: 62 additions & 0 deletions src/rules/rrd/vForExpression.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
import { describe, expect, it } from 'vitest'
import { checkVForExpression, reportVForExpression } from './vForExpression'

describe('checkVforExpression', () => {
it('should not report files with v-for has no expression', () => {
const script = {
content: `<template>
<div v-for="item in items" :key="item.id">
{{ item.name }}
</div>
</template>`,
} as SFCTemplateBlock
const fileName = 'vforNoExpression.vue'
checkVForExpression(script, fileName)
const result = reportVForExpression()
expect(result.length).toBe(0)
expect(result).toStrictEqual([])
})

it('should report files with v-for using an arrow function in expression', () => {
const expression = 'items.filter((config) => config.level === 2)'
const template = {
content: `<template>
<div v-for="item in ${expression}" :key="item.id">
{{ item.name }}
</div>
</template>`,
} as SFCTemplateBlock
const fileName = 'vforWithArrowFunctionExpression.vue'
checkVForExpression(template, fileName)
const result = reportVForExpression()
expect(result.length).toBe(1)
expect(result).toStrictEqual([{
file: fileName,
rule: `<text_info>rrd ~ vfor Expression</text_info>`,
description: `👉 <text_warn>Move out the expression to a calculated property.</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/vfor-expression.html`,
message: `line #2 <bg_warn>expression: ${expression} in v-for</bg_warn> 🚨`,
}])
})

it('should report files with v-for using a normal function in expression', () => {
const expression = 'items.filter(function(config) { return config.level === 2 })'
const template = {
content: `<template>
<div v-for="item in ${expression}" :key="item.id">
{{ item.name }}
</div>
</template>`,
} as SFCTemplateBlock
const fileName = 'vforWithNormalFunctionExpression.vue'
checkVForExpression(template, fileName)
const result = reportVForExpression()
expect(result.length).toBe(1)
expect(result).toStrictEqual([{
file: fileName,
rule: `<text_info>rrd ~ vfor Expression</text_info>`,
description: `👉 <text_warn>Move out the expression to a calculated property.</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/vfor-expression.html`,
message: `line #2 <bg_warn>expression: ${expression} in v-for</bg_warn> 🚨`,
}])
})
})
54 changes: 54 additions & 0 deletions src/rules/rrd/vForExpression.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
import type { FileCheckResult, Offense } from '../../types'
import { char, charNotIn, createRegExp, exactly, global, oneOrMore } from 'magic-regexp'
import { skipComments } from '../../helpers/skipComments'
import getLineNumber from '../getLineNumber'

const results: FileCheckResult[] = []

const resetResults = () => (results.length = 0)

const checkVForExpression = (template: SFCTemplateBlock | null, filePath: string) => {
if (!template) {
return
}

const regex = createRegExp('v-for="', oneOrMore(charNotIn('"')), ' in ', exactly(oneOrMore(char), exactly('=>').or('function'), oneOrMore(charNotIn('"'))).groupedAs('expression'), '"', [global])

const content = skipComments(template.content)

let match
let lastLine = 0
// eslint-disable-next-line no-cond-assign
while ((match = regex.exec(content)) !== null) {
if (match.groups?.expression) {
const lineNumber = getLineNumber(content, match.groups?.expression, lastLine)
results.push({
filePath,
message: `line #${lineNumber} <bg_warn>expression: ${match.groups?.expression} in v-for</bg_warn>`,
})
lastLine = lineNumber
}
}
}

const reportVForExpression = () => {
const offenses: Offense[] = []

if (results.length > 0) {
results.forEach((result) => {
offenses.push({
file: result.filePath,
rule: `<text_info>rrd ~ vfor Expression</text_info>`,
description: `👉 <text_warn>Move out the expression to a calculated property.</text_warn> See: https://vue-mess-detector.webmania.cc/rules/rrd/vfor-expression.html`,
message: `${result.message} 🚨`,
})
})
}

resetResults()

return offenses
}

export { checkVForExpression, reportVForExpression }
1 change: 1 addition & 0 deletions src/rules/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const RULES = {
'shortVariableName',
'tooManyProps',
'vForWithIndexKey',
'vForExpression',
'zeroLengthComparison',
],
'security': [
Expand Down
3 changes: 2 additions & 1 deletion src/rulesCheck.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { SFCDescriptor } from '@vue/compiler-sfc'
import type { OverrideConfig } from './types/Override'
import { getHasServer, getIsNuxt } from './context'
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 { 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, checkVForExpression, checkVForWithIndexKey, checkZeroLengthComparison } from './rules/rrd'
import { RULES } from './rules/rules'
import { checkApiWithoutMethod, checkRateLimiter } from './rules/security'
import { checkElementSelectorsWithScoped, checkImplicitParentChildCommunication } from './rules/vue-caution'
Expand Down Expand Up @@ -72,6 +72,7 @@ export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: s
scriptLength: () => checkScriptLength(script, filePath, override.maxScriptLength),
shortVariableName: () => checkShortVariableName(script, filePath, override.minVariableName),
tooManyProps: () => checkTooManyProps(script, filePath, override.maxPropsCount),
vForExpression: () => checkVForExpression(descriptor.template, filePath),
vForWithIndexKey: () => isVueFile && checkVForWithIndexKey(descriptor.template, filePath),
zeroLengthComparison: () => checkZeroLengthComparison(script, filePath),

Expand Down
3 changes: 2 additions & 1 deletion src/rulesReport.ts
Original file line number Diff line number Diff line change
@@ -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 { 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 { 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, reportVForExpression, 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'
Expand Down Expand Up @@ -86,6 +86,7 @@ export const reportRules = (groupBy: GroupBy, sortBy: SortBy, level: OutputLevel
processOffenses(() => reportScriptLength(override.maxScriptLength))
processOffenses(() => reportShortVariableName(override.minVariableName))
processOffenses(reportTooManyProps)
processOffenses(reportVForExpression)
processOffenses(reportVForWithIndexKey)
processOffenses(reportZeroLengthComparison)

Expand Down