diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts
index f66d0b3a..31f01dea 100644
--- a/docs/.vitepress/config.ts
+++ b/docs/.vitepress/config.ts
@@ -81,6 +81,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 Inline Styles', link: '/rules/rrd/no-inline-styles' },
{ text: 'No Props Destructure', link: '/rules/rrd/no-prop-destructure' },
{ text: 'No Var Declaration', link: '/rules/rrd/no-var-declaration' },
{ text: 'Parameter Count', link: '/rules/rrd/parameter-count' },
diff --git a/docs/rules/rrd/index.md b/docs/rules/rrd/index.md
index 6bfa2ea3..05af096d 100644
--- a/docs/rules/rrd/index.md
+++ b/docs/rules/rrd/index.md
@@ -15,6 +15,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 Inline Styles](./no-inline-styles.md)
- [No Prop Destructing](./no-prop-destructure.md)
- [No Var Declaration](./no-var-declaration.md)
- [Parameter Count](./parameter-count.md)
diff --git a/docs/rules/rrd/no-inline-styles.md b/docs/rules/rrd/no-inline-styles.md
new file mode 100644
index 00000000..b6e6a47c
--- /dev/null
+++ b/docs/rules/rrd/no-inline-styles.md
@@ -0,0 +1,43 @@
+# No Inline Styles
+
+Checks if in an element within the template of an SFC uses a inline style
+
+## ❓ Why it's good to follow this rule?
+
+**Maintainability:** External CSS makes updates easier and keeps the codebase clean.
+**Performance:** Reduces HTML file size and leverages browser caching.
+**Consistency:** Ensures a uniform style across the entire website or application.
+
+## 😱 Examples of code for which this rule will throw a warning
+
+::: warning
+The following code contains inline `styles` which are not recommended because they increase the html code and do not take advantage of browser caching
+:::
+
+```vue
+
+
+
+
+
+```
+
+## 🤩 How to fix it?
+
+::: tip
+Using `style` with the `scoped` attribute and creating the necessary classes for the style you want to apply can be a good option.
+:::
+
+```vue
+
+
+
+
+
+
+
+```
diff --git a/src/rules/rrd/index.ts b/src/rules/rrd/index.ts
index 744c5263..9e1ef29d 100644
--- a/src/rules/rrd/index.ts
+++ b/src/rules/rrd/index.ts
@@ -21,3 +21,4 @@ export * from './shortVariableName'
export * from './tooManyProps'
export * from './vForWithIndexKey'
export * from './zeroLengthComparison'
+export * from './noInlineStyles'
diff --git a/src/rules/rrd/noInlineStyles.test.ts b/src/rules/rrd/noInlineStyles.test.ts
new file mode 100644
index 00000000..140e27bf
--- /dev/null
+++ b/src/rules/rrd/noInlineStyles.test.ts
@@ -0,0 +1,57 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCTemplateBlock } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkNoInlineStyles, reportNoInlineStyles, resetNoInlineStyles } from './noInlineStyles'
+
+describe('checkNoInlineStyles', () => {
+ beforeEach(() => {
+ resetNoInlineStyles()
+ })
+
+ it('should not report files with inline styles', () => {
+ const template = {
+ content: `
+
+ Hello World!
+
+ Hi!
+
+
+ `,
+ } as SFCTemplateBlock
+ const fileName = 'noInlineStyles.vue'
+ checkNoInlineStyles(template, fileName)
+ expect(reportNoInlineStyles().length).toBe(0)
+ expect(reportNoInlineStyles()).toStrictEqual([])
+ })
+
+ it('should report files with inline styles', () => {
+ const template = {
+ content: `
+
+ Hello World!
+
+ Hi!
+
+
+ `,
+ } as SFCTemplateBlock
+ const fileName = 'noInlineStyles-problem.vue'
+ checkNoInlineStyles(template, fileName)
+ expect(reportNoInlineStyles().length).toBe(2)
+ expect(reportNoInlineStyles()).toStrictEqual([
+ {
+ file: fileName,
+ rule: `${TEXT_INFO}rrd ~ no Inline Styles${TEXT_RESET}`,
+ description: `👉 ${TEXT_WARN}Avoid using inline styles. Consider moving the styles to a CSS or SCSS file, or use a Vue scoped style.${TEXT_RESET}`,
+ message: `line #2 ${BG_WARN}Found inline style: style="background: #fff;"${BG_RESET} 🚨`,
+ },
+ {
+ file: fileName,
+ rule: `${TEXT_INFO}rrd ~ no Inline Styles${TEXT_RESET}`,
+ description: `👉 ${TEXT_WARN}Avoid using inline styles. Consider moving the styles to a CSS or SCSS file, or use a Vue scoped style.${TEXT_RESET}`,
+ message: `line #4 ${BG_WARN}Found inline style: style="color: red;"${BG_RESET} 🚨`,
+ },
+ ])
+ })
+})
diff --git a/src/rules/rrd/noInlineStyles.ts b/src/rules/rrd/noInlineStyles.ts
new file mode 100644
index 00000000..38f33a95
--- /dev/null
+++ b/src/rules/rrd/noInlineStyles.ts
@@ -0,0 +1,45 @@
+import type { SFCTemplateBlock } from '@vue/compiler-sfc'
+import getLineNumber from '../getLineNumber'
+import type { FileCheckResult, Offense } from '../../types'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+
+const results: FileCheckResult[] = []
+
+const checkNoInlineStyles = (template: SFCTemplateBlock | null, filePath: string) => {
+ if (!template) {
+ return
+ }
+
+ const regex = /style\s*=\s*['"][^'"]*['"]/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 inline style: ${match[0]}${BG_RESET}`,
+ })
+ from = lineNumber
+ })
+}
+
+const reportNoInlineStyles = () => {
+ const offenses: Offense[] = []
+
+ if (results.length > 0) {
+ results.forEach((result) => {
+ offenses.push({
+ file: result.filePath,
+ rule: `${TEXT_INFO}rrd ~ no Inline Styles${TEXT_RESET}`,
+ description: `👉 ${TEXT_WARN}Avoid using inline styles. Consider moving the styles to a CSS or SCSS file, or use a Vue scoped style.${TEXT_RESET}`,
+ message: `${result.message} 🚨`,
+ })
+ })
+ }
+ return offenses
+}
+
+const resetNoInlineStyles = () => (results.length = 0)
+
+export { checkNoInlineStyles, reportNoInlineStyles, resetNoInlineStyles }
diff --git a/src/rules/rules.ts b/src/rules/rules.ts
index 4003cf46..6b605335 100644
--- a/src/rules/rules.ts
+++ b/src/rules/rules.ts
@@ -40,6 +40,7 @@ export const RULES = {
'ifWithoutCurlyBraces',
'magicNumbers',
'nestedTernary',
+ 'noInlineStyles',
'noPropDestructure',
'noVarDeclaration',
'parameterCount',
diff --git a/src/rulesCheck.ts b/src/rulesCheck.ts
index fce4660f..6e3aa6d0 100644
--- a/src/rulesCheck.ts
+++ b/src/rulesCheck.ts
@@ -5,7 +5,7 @@ import { checkElementSelectorsWithScoped, checkImplicitParentChildCommunication
import { checkGlobalStyle, checkSimpleProp, checkSingleNameComponent, checkVforNoKey, checkVifWithVfor } from './rules/vue-essential'
import { checkElementAttributeOrder, checkTopLevelElementOrder } from './rules/vue-recommended'
import { checkComponentFilenameCasing, checkComponentFiles, checkDirectiveShorthands, checkFullWordComponentName, checkMultiAttributeElements, checkPropNameCasing, checkQuotedAttributeValues, checkSelfClosingComponents, checkSimpleComputed, checkTemplateSimpleExpression } from './rules/vue-strong'
-import { checkBigVif, checkBigVshow, checkComplicatedConditions, checkComputedSideEffects, checkCyclomaticComplexity, checkDeepIndentation, checkElseCondition, checkFunctionSize, checkHtmlImageElements, checkHtmlLink, checkIfWithoutCurlyBraces, checkMagicNumbers, checkNestedTernary, checkNoPropDestructure, checkNoVarDeclaration, checkParameterCount, checkPlainScript, checkPropsDrilling, checkScriptLength, checkShortVariableName, checkTooManyProps, checkVForWithIndexKey, checkZeroLengthComparison } from './rules/rrd'
+import { checkBigVif, checkBigVshow, checkComplicatedConditions, checkComputedSideEffects, checkCyclomaticComplexity, checkDeepIndentation, checkElseCondition, checkFunctionSize, checkHtmlImageElements, checkHtmlLink, checkIfWithoutCurlyBraces, checkMagicNumbers, checkNestedTernary, checkNoInlineStyles, checkNoPropDestructure, checkNoVarDeclaration, checkParameterCount, checkPlainScript, checkPropsDrilling, checkScriptLength, checkShortVariableName, checkTooManyProps, checkVForWithIndexKey, checkZeroLengthComparison } from './rules/rrd'
import { getIsNuxt } from './context'
export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: string[]) => {
@@ -67,6 +67,7 @@ export const checkRules = (descriptor: SFCDescriptor, filePath: string, apply: s
tooManyProps: () => checkTooManyProps(script, filePath),
vForWithIndexKey: () => isVueFile && checkVForWithIndexKey(descriptor.template, filePath),
zeroLengthComparison: () => checkZeroLengthComparison(script, filePath),
+ noInlineStyles: () => checkNoInlineStyles(descriptor.template, filePath),
}
// Run the checks for each applied rule or ruleset
diff --git a/src/rulesReport.ts b/src/rulesReport.ts
index d213caff..baa1a803 100644
--- a/src/rulesReport.ts
+++ b/src/rulesReport.ts
@@ -5,7 +5,7 @@ import { reportElementSelectorsWithScoped, reportImplicitParentChildCommunicatio
import { reportGlobalStyle, reportSimpleProp, reportSingleNameComponent, reportVforNoKey, reportVifWithVfor } from './rules/vue-essential'
import { reportElementAttributeOrder, reportTopLevelElementOrder } from './rules/vue-recommended'
import { reportComponentFilenameCasing, reportComponentFiles, reportDirectiveShorthands, reportFullWordComponentName, reportMultiAttributeElements, reportPropNameCasing, reportQuotedAttributeValues, reportSelfClosingComponents, reportSimpleComputed, reportTemplateSimpleExpression } from './rules/vue-strong'
-import { reportBigVif, reportBigVshow, reportComplicatedConditions, reportComputedSideEffects, reportCyclomaticComplexity, reportDeepIndentation, reportElseCondition, reportFunctionSize, reportHtmlImageElements, reportHtmlLink, reportIfWithoutCurlyBraces, reportMagicNumbers, reportNestedTernary, reportNoPropDestructure, reportNoVarDeclaration, reportParameterCount, reportPlainScript, reportPropsDrilling, reportScriptLength, reportShortVariableName, reportTooManyProps, reportVForWithIndexKey, reportZeroLengthComparison } from './rules/rrd'
+import { reportBigVif, reportBigVshow, reportComplicatedConditions, reportComputedSideEffects, reportCyclomaticComplexity, reportDeepIndentation, reportElseCondition, reportFunctionSize, reportHtmlImageElements, reportHtmlLink, reportIfWithoutCurlyBraces, reportMagicNumbers, reportNestedTernary, reportNoInlineStyles, reportNoPropDestructure, reportNoVarDeclaration, reportParameterCount, reportPlainScript, reportPropsDrilling, reportScriptLength, reportShortVariableName, reportTooManyProps, reportVForWithIndexKey, reportZeroLengthComparison } from './rules/rrd'
// TODO move out to types
interface OutputType {
@@ -85,6 +85,7 @@ export const reportRules = (groupBy: GroupBy, sortBy: SortBy, level: OutputLevel
processOffenses(reportTooManyProps)
processOffenses(reportVForWithIndexKey)
processOffenses(reportZeroLengthComparison)
+ processOffenses(reportNoInlineStyles)
// Sort offenses grouped by key based on the `sortBy` parameter
const sortedKeys = Object.keys(offensesGrouped).sort((a, b) => {