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

Issue:
add rule (rrd) !important is used
No style should use `!important`

For this we have to scan `.css` files also

Pull request:
feat(no-important): add rule !important is used
TODO UPDATE DESCRIPTION

### Summary
<!-- Provide a general summary of your changes in the title above -->

### Description
<!-- Describe your changes in detail -->

### Related Issues
<!-- List any related issues or pull requests, using the format `Fixes #issue_number` -->
Fixes #214

### Type of Change
<!-- Please select the options that best describe your changes -->
- [ ] New feature (non-breaking change which adds functionality)
- [ ] Documentation update

### Screenshots (if applicable)
<!-- Add screenshots to help expalin the changes -->


Patch:
diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index ede64f8..86e18cd 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -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' },
diff --git a/docs/rules/rrd/index.md b/docs/rules/rrd/index.md
index aec754e..7972cf5 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
 - [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)
diff --git a/docs/rules/rrd/no-important.md b/docs/rules/rrd/no-important.md
new file mode 100644
index 0000000..401aa34
--- /dev/null
+++ b/docs/rules/rrd/no-important.md
@@ -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.
diff --git a/src/rules/rrd/index.ts b/src/rules/rrd/index.ts
index 000454d..38b7f70 100644
--- a/src/rules/rrd/index.ts
+++ b/src/rules/rrd/index.ts
@@ -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'
diff --git a/src/rules/rrd/noImportant.ts b/src/rules/rrd/noImportant.ts
new file mode 100644
index 0000000..a0d1d2e
--- /dev/null
+++ b/src/rules/rrd/noImportant.ts
@@ -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 }
diff --git a/src/rules/rules.ts b/src/rules/rules.ts
index dd793d4..0c21ddd 100644
--- a/src/rules/rules.ts
+++ b/src/rules/rules.ts
@@ -43,6 +43,7 @@ export const RULES = {
     'magicNumbers',
     'nestedTernary',
     'noDirectDomAccess',
+    'noImportant',
     'noInlineStyles',
     'noPropDestructure',
     'noSkippedTests',
diff --git a/src/rulesCheck.ts b/src/rulesCheck.ts
index 1020d50..4ec34d2 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 { 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'
@@ -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),
diff --git a/src/rulesReport.ts b/src/rulesReport.ts
index f36f68e..9c695a9 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 { 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'
@@ -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)


Test patch:
diff --git a/src/rules/rrd/noImportant.test.ts b/src/rules/rrd/noImportant.test.ts
new file mode 100644
index 0000000..ba38535
--- /dev/null
+++ b/src/rules/rrd/noImportant.test.ts
@@ -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> 🚨`,
+    }])
+  })
+})