Skip to content

Commit

Permalink
Merge pull request #405 from bchanez/feat/no-important
Browse files Browse the repository at this point in the history
feat(no-important): add rule !important is used
  • Loading branch information
rrd108 authored Oct 14, 2024
2 parents 976859a + 5f2bd89 commit 05f91a4
Show file tree
Hide file tree
Showing 9 changed files with 207 additions and 2 deletions.
1 change: 1 addition & 0 deletions docs/.vitepress/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ export default defineConfig({
{ text: 'If Without Curly Braces', link: '/rules/rrd/if-without-curly-braces' },
{ text: 'Magic Numbers', link: '/rules/rrd/magic-numbers' },
{ text: 'Nested Ternary', link: '/rules/rrd/nested-ternary' },
{ text: 'No !important', link: '/rules/rrd/no-important' },
{ text: 'No Direct Dom Access', link: '/rules/rrd/no-direct-dom-access' },
{ text: 'No Inline Styles', link: '/rules/rrd/no-inline-styles' },
{ text: 'No Props Destructure', link: '/rules/rrd/no-prop-destructure' },
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 @@ -17,6 +17,7 @@ These ruleset is the most opinionated with rules that are not part of the _offic
- [If Without Curly Braces](./if-without-curly-braces.md)
- [Magic Numbers](./magic-numbers.md)
- [Nested Ternary](./nested-ternary.md)
- [No !Important](./no-important.md)
- [No Direct Dom Access](./no-direct-dom-access.md)
- [No Inline Styles](./no-inline-styles.md)
- [No Prop Destructing](./no-prop-destructure.md)
Expand Down
65 changes: 65 additions & 0 deletions docs/rules/rrd/no-important.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# No Direct Dom Access

Checks if a component's styles are using the `!important` declaration to enforce styles.

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

Direct DOM manipulation in Vue components can lead to several issues:

Using `!important` in CSS can lead to several issues:

1. It complicates the cascade of styles, making it harder to understand which styles are being applied.
2. It increases the specificity of rules, leading to maintenance challenges when updating styles.
3. It can make debugging more difficult, as the usual cascade and inheritance rules of CSS are bypassed.
4. It promotes poor coding practices by encouraging developers to rely on `!important` rather than properly managing specificity.

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

::: warning
The following code contains the use of `!important`, which is discouraged:
:::


```vue
<template>
<div class="button">Click Me</div>
</template>
<style scoped>
.button {
background-color: blue !important; /* Disallowed */
color: white;
}
</style>
```

## 🤩 How to fix it?

::: tip
Refactor your CSS to avoid using `!important` by managing specificity through proper selectors. Here's how to fix the previous examples:
:::

```vue
<template>
<div class="button">Click Me</div>
</template>
<style scoped>
.button {
background-color: blue; /* Removed !important */
color: white;
}
.special-button {
background-color: red; /* More specific selector */
}
</style>
```

In this fixed example:

1. We removed the `!important` declaration from the `.button` class.
2. We created a more specific selector (`.special-button`) to override styles when necessary.
3. By relying on proper specificity and structure, we maintain clearer and more manageable CSS.

This approach enhances maintainability, readability, and performance while ensuring that styles are applied correctly without unnecessary overrides.
1 change: 1 addition & 0 deletions src/rules/rrd/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ export * from './ifWithoutCurlyBraces'
export * from './magicNumbers'
export * from './nestedTernary'
export * from './noDirectDomAccess'
export * from './noImportant'
export * from './noInlineStyles'
export * from './noPropDestructure'
export * from './noSkippedTests'
Expand Down
90 changes: 90 additions & 0 deletions src/rules/rrd/noImportant.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
import { describe, expect, it } from 'vitest'
import { checkNoImportant, reportNoImportant } from './noImportant'

describe('importantUsed', () => {
it('should not report templates when there is no use of !important', () => {
const template = {
content: `
<template>
<div style="color: blue"></div>
</template>
<style scoped>
div {
border: solid;
}
</style>
`,
} as SFCTemplateBlock
const filename = 'no-important.vue'
checkNoImportant(template, filename)
const result = reportNoImportant()
expect(result.length).toBe(0)
expect(result).toStrictEqual([])
})

it('should not report css files when there is no use of !important', () => {
const template = {
content: `
div {
color: blue;
}
`,
} as SFCTemplateBlock
const filename = 'no-important.css'
checkNoImportant(template, filename)
const result = reportNoImportant()
expect(result.length).toBe(0)
expect(result).toStrictEqual([])
})

it('should report templates when there is use of !important', () => {
const template = {
content: `
<template>
<div style="color: blue !important"></div>
</template>
<style scoped>
div {
border: solid !important;
}
</style>
`,
} as SFCTemplateBlock
const filename = 'no-important.vue'
checkNoImportant(template, filename)
const result = reportNoImportant()
expect(result.length).toBe(2)
expect(result).toStrictEqual([{
file: filename,
rule: `<text_info>rdd ~ no !important</text_info>`,
description: `👉 <text_warn>Avoid !important as it complicates CSS management and disrupts natural cascading.</text_warn>`,
message: `line #${2} <bg_warn>Found !important</bg_warn> 🚨`,
}, {
file: filename,
rule: `<text_info>rdd ~ no !important</text_info>`,
description: `👉 <text_warn>Avoid !important as it complicates CSS management and disrupts natural cascading.</text_warn>`,
message: `line #${6} <bg_warn>Found !important</bg_warn> 🚨`,
}])
})

it('should report css files when there is use of !important', () => {
const template = {
content: `
div {
color: blue !important;
}
`,
} as SFCTemplateBlock
const filename = 'no-important.css'
checkNoImportant(template, filename)
const result = reportNoImportant()
expect(result.length).toBe(1)
expect(result).toStrictEqual([{
file: filename,
rule: `<text_info>rdd ~ no !important</text_info>`,
description: `👉 <text_warn>Avoid !important as it complicates CSS management and disrupts natural cascading.</text_warn>`,
message: `line #${2} <bg_warn>Found !important</bg_warn> 🚨`,
}])
})
})
44 changes: 44 additions & 0 deletions src/rules/rrd/noImportant.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import type { SFCTemplateBlock } from '@vue/compiler-sfc'
import type { FileCheckResult, Offense } from '../../types'
import getLineNumber from '../getLineNumber'

const results: FileCheckResult[] = []

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

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

const regex = /!important/g

const matches = [...template.content.matchAll(regex)]
let from = 0
matches.forEach((match) => {
const lineNumber = getLineNumber(template.content.trim(), match[0], from)
results.push({ filePath, message: `line #${lineNumber} <bg_warn>Found !important</bg_warn>` })
from = lineNumber
})
}

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

if (results.length > 0) {
results.forEach((result) => {
offenses.push({
file: result.filePath,
rule: `<text_info>rdd ~ no !important</text_info>`,
description: `👉 <text_warn>Avoid !important as it complicates CSS management and disrupts natural cascading.</text_warn>`,
message: `${result.message} 🚨`,
})
})
}

resetResults()

return offenses
}

export { checkNoImportant, reportNoImportant }
1 change: 1 addition & 0 deletions src/rules/rules.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ export const RULES = {
'magicNumbers',
'nestedTernary',
'noDirectDomAccess',
'noImportant',
'noInlineStyles',
'noPropDestructure',
'noSkippedTests',
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, checkVForExpression, checkVForWithIndexKey, checkZeroLengthComparison } from './rules/rrd'
import { checkAmountOfComments, checkBigVif, checkBigVshow, checkComplicatedConditions, checkComputedSideEffects, checkCyclomaticComplexity, checkDeepIndentation, checkElseCondition, checkFunctionSize, checkHtmlImageElements, checkHtmlLink, checkHugeFiles, checkIfWithoutCurlyBraces, checkMagicNumbers, checkNestedTernary, checkNoDirectDomAccess, checkNoImportant, checkNoInlineStyles, checkNoPropDestructure, checkNoSkippedTests, checkNoTsLang, checkNoVarDeclaration, checkParameterCount, checkPlainScript, checkPropsDrilling, checkScriptLength, checkShortVariableName, checkTooManyProps, checkVForExpression, checkVForWithIndexKey, checkZeroLengthComparison } from './rules/rrd'
import { checkApiWithoutMethod, checkRateLimiter } from './rules/security'
import { checkElementSelectorsWithScoped, checkImplicitParentChildCommunication } from './rules/vue-caution'
import { checkGlobalStyle, checkSimpleProp, checkSingleNameComponent, checkVforNoKey, checkVifWithVfor } from './rules/vue-essential'
Expand Down Expand Up @@ -60,6 +60,7 @@ export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: s
magicNumbers: () => checkMagicNumbers(script, filePath),
nestedTernary: () => checkNestedTernary(script, filePath),
noDirectDomAccess: () => checkNoDirectDomAccess(script, filePath),
noImportant: () => checkNoImportant(descriptor.template, filePath),
noInlineStyles: () => checkNoInlineStyles(descriptor.template, filePath),
noPropDestructure: () => checkNoPropDestructure(script, filePath),
noSkippedTests: () => checkNoSkippedTests(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, reportVForExpression, reportVForWithIndexKey, reportZeroLengthComparison } from './rules/rrd'
import { reportAmountOfComments, reportBigVif, reportBigVshow, reportComplicatedConditions, reportComputedSideEffects, reportCyclomaticComplexity, reportDeepIndentation, reportElseCondition, reportFunctionSize, reportHtmlImageElements, reportHtmlLink, reportHugeFiles, reportIfWithoutCurlyBraces, reportMagicNumbers, reportNestedTernary, reportNoDirectDomAccess, reportNoImportant, 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 @@ -75,6 +75,7 @@ export const reportRules = (groupBy: GroupBy, sortBy: SortBy, level: OutputLevel
processOffenses(reportMagicNumbers)
processOffenses(reportNestedTernary)
processOffenses(reportNoDirectDomAccess)
processOffenses(reportNoImportant)
processOffenses(reportNoInlineStyles)
processOffenses(reportNoPropDestructure)
processOffenses(reportNoSkippedTests)
Expand Down

0 comments on commit 05f91a4

Please sign in to comment.