Repository: rrd108/vue-mess-detector
Pull request #108 (2024-08-07 18:23:51)

feat: enable --group cli option to group results by rule or file
This PR includes:
- [X] refactor `rulesReport` file to handle `groupBy` option
- [X] refactor `report` functions for each rule to have the new structure
- [X] add interfaces/types for new structure
- [X] add cli option as a namespaced arg
- [X]  refactor all output messages to remove `filePath` duplicates
- [X] update all tests


Patch:
diff --git a/src/analyzer.ts b/src/analyzer.ts
index e3c9baa..a0b3025 100644
--- a/src/analyzer.ts
+++ b/src/analyzer.ts
@@ -7,6 +7,7 @@ import { BG_INFO, BG_OK, BG_RESET } from './rules/asceeCodes'
 import { RULESETS, type RuleSetType } from './rules/rules'
 import { reportRules } from './rulesReport'
 import { checkRules } from './rulesCheck'
+import type { GroupBy } from './types'
 
 let filesCount = 0
 
@@ -39,7 +40,7 @@ const walkSync = (dir: string, callback: (arg0: string) => void) => {
   }
 }
 
-export const analyze = (dir: string, apply: Array<RuleSetType> = []) => {
+export const analyze = (dir: string, apply: Array<RuleSetType> = [], groupBy: GroupBy) => {
   console.log(`\n\n${BG_INFO}Analyzing Vue files in ${dir}${BG_RESET}`)
   const ignore = RULESETS.filter(rule => !apply.includes(rule))
   console.log(`Applying ${apply.length} rulesets ${BG_INFO}${apply}${BG_RESET} and ignoring ${ignore.length} rulesets ${BG_INFO}${ignore}${BG_RESET} `)
@@ -56,7 +57,7 @@ export const analyze = (dir: string, apply: Array<RuleSetType> = []) => {
 
   console.log(`Found ${BG_INFO}${filesCount}${BG_RESET} Vue files`)
 
-  if (!reportRules()) {
+  if (!reportRules(groupBy)) {
     console.log(`${BG_OK}No code smells detected!${BG_RESET}`)
   }
 }
diff --git a/src/index.ts b/src/index.ts
index c968620..b5d938d 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -4,6 +4,7 @@ import { analyze } from './analyzer'
 import { BG_ERR, BG_RESET, TEXT_RESET, TEXT_WARN } from './rules/asceeCodes'
 import type { RuleSetType } from './rules/rules'
 import { RULESETS } from './rules/rules'
+import type { GroupBy } from './types'
 
 // eslint-disable-next-line ts/no-unused-expressions, node/prefer-global/process
 yargs(hideBin(process.argv))
@@ -27,6 +28,12 @@ yargs(hideBin(process.argv))
           type: 'string',
           coerce: coerceRules('apply'),
         })
+        .option('group', {
+          describe: 'Group results by rule or file',
+          type: 'string',
+          coerce: value => customGroupType(value),
+          default: 'rule',
+        })
         .check((argv) => {
           if (argv.ignore && argv.apply) {
             console.error(
@@ -46,7 +53,7 @@ yargs(hideBin(process.argv))
       if (argv.ignore) {
         rules = RULESETS.filter(rule => !argv.ignore!.includes(rule))
       }
-      analyze(argv.path as string, rules)
+      analyze(argv.path as string, rules, argv.group)
     },
   )
   .help().argv
@@ -67,3 +74,12 @@ function coerceRules(optionName: 'ignore' | 'apply') {
     return values as RuleSetType[]
   }
 }
+
+function customGroupType(value: GroupBy) {
+  const validChoices: GroupBy[] = ['rule', 'file']
+  if (!validChoices.includes(value)) {
+    // eslint-disable-next-line node/prefer-global/process
+    process.exit(1)
+  }
+  return value
+}
diff --git a/src/rules/rrd/cyclomaticComplexity.ts b/src/rules/rrd/cyclomaticComplexity.ts
index 2c691ab..954e784 100644
--- a/src/rules/rrd/cyclomaticComplexity.ts
+++ b/src/rules/rrd/cyclomaticComplexity.ts
@@ -1,6 +1,7 @@
 import type { SFCScriptBlock } from '@vue/compiler-sfc'
 import { caseInsensitive, createRegExp, global, wordBoundary } from 'magic-regexp'
-import { BG_ERR, BG_INFO, BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { BG_ERR, BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 /**
  * Defines complexity thresholds.
@@ -53,20 +54,23 @@ const checkCyclomaticComplexity = (script: SFCScriptBlock | null, file: string)
  * @returns {number} The number of files with high cyclomatic complexity.
  */
 const reportCyclomaticComplexity = () => {
+  const offenses: Offense[] = []
+
   if (cyclomaticComplexityFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_INFO}cyclomaticComplexity${BG_RESET} is above moderate in ${cyclomaticComplexityFiles.length} files.`,
-    )
-    console.log(`👉 ${TEXT_WARN}Try to reduce complexity.${TEXT_RESET}`)
     cyclomaticComplexityFiles.forEach((file) => {
-      console.log(
-        `- ${file.fileName} ${file.cyclomaticComplexity > COMPLEXITY_HIGH ? BG_ERR : BG_WARN}(${
+      offenses.push({
+        file: file.fileName,
+        rule: `${TEXT_INFO}rrd ~ cyclomatic complexity${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Try to reduce complexity.${TEXT_RESET}`,
+        message: `${file.cyclomaticComplexity > COMPLEXITY_HIGH ? BG_ERR : BG_WARN}(${
           file.cyclomaticComplexity
-        })${BG_RESET}`,
-      )
+        })${BG_RESET} 🚨`,
+      })
     })
   }
-  return cyclomaticComplexityFiles.length
+  return offenses
 }
 
-export { checkCyclomaticComplexity, reportCyclomaticComplexity }
+const resetCyclomaticComplexity = () => (cyclomaticComplexityFiles.length = 0)
+
+export { checkCyclomaticComplexity, reportCyclomaticComplexity, resetCyclomaticComplexity }
diff --git a/src/rules/rrd/deepIndentation.ts b/src/rules/rrd/deepIndentation.ts
index d3581a1..b21ea37 100644
--- a/src/rules/rrd/deepIndentation.ts
+++ b/src/rules/rrd/deepIndentation.ts
@@ -1,10 +1,10 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
 import { caseInsensitive, createRegExp, global, tab, whitespace } from 'magic-regexp'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 import getLineNumber from '../getLineNumber'
-import { getUniqueFilenameCount } from '../../helpers'
+import type { Offense } from '../../types'
 
-type DeepIndentationFile = {
+interface DeepIndentationFile {
   filePath: string
   message: string
 }
@@ -22,28 +22,31 @@ const checkDeepIndentation = (script: SFCScriptBlock | null, filePath: string) =
   ])
   const matches = script.content.match(regex)
 
-  matches?.forEach(match => {
+  matches?.forEach((match) => {
     const lineNumber = getLineNumber(script.content, match)
     deepIndentationFiles.push({
       filePath,
-      message: `${filePath}#${lineNumber} ${BG_WARN}indentation: ${match.length}${BG_RESET}`,
+      message: `line #${lineNumber} ${BG_WARN}indentation: ${match.length}${BG_RESET}`,
     })
   })
 }
 
 const reportDeepIndentation = () => {
-  if (deepIndentationFiles.length > 0) {
-    const fileCount = getUniqueFilenameCount<DeepIndentationFile>(deepIndentationFiles, 'filePath')
+  const offenses: Offense[] = []
 
-    console.log(`\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}deep indentation${BG_RESET} found in ${fileCount} files.`)
-    console.log(
-      `👉 ${TEXT_WARN}Try to refactor your component to child components, to avoid deep indentations..${TEXT_RESET}`
-    )
-    deepIndentationFiles.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+  if (deepIndentationFiles.length > 0) {
+    deepIndentationFiles.forEach((file) => {
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}rrd ~ deep indentation${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Try to refactor your component to child components, to avoid deep indentations..${TEXT_RESET}`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return deepIndentationFiles.length
+  return offenses
 }
 
-export { checkDeepIndentation, reportDeepIndentation }
+const resetDeepIndentation = () => (deepIndentationFiles.length = 0)
+
+export { checkDeepIndentation, reportDeepIndentation, resetDeepIndentation }
diff --git a/src/rules/rrd/elseCondition.ts b/src/rules/rrd/elseCondition.ts
index 2410f53..e2d90a3 100644
--- a/src/rules/rrd/elseCondition.ts
+++ b/src/rules/rrd/elseCondition.ts
@@ -1,8 +1,9 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
 import { caseInsensitive, createRegExp, global, wordBoundary } from 'magic-regexp'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
-const elseConditionFiles: { fileName: string; elseCount: number }[] = []
+const elseConditionFiles: { fileName: string, elseCount: number }[] = []
 
 const checkElseCondition = (script: SFCScriptBlock | null, file: string) => {
   if (!script) {
@@ -17,16 +18,21 @@ const checkElseCondition = (script: SFCScriptBlock | null, file: string) => {
 }
 
 const reportElseCondition = () => {
+  const offenses: Offense[] = []
+
   if (elseConditionFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}else conditions${BG_RESET} are used in ${elseConditionFiles.length} files.`
-    )
-    console.log(`👉 ${TEXT_WARN}Try to rewrite the conditions in a way that the else clause is not necessary.${TEXT_RESET}`)
-    elseConditionFiles.forEach(file => {
-      console.log(`- ${file.fileName} ${BG_WARN}(${file.elseCount})${BG_RESET}`)
+    elseConditionFiles.forEach((file) => {
+      offenses.push({
+        file: file.fileName,
+        rule: `${TEXT_INFO}rrd ~ else conditions${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Try to rewrite the conditions in a way that the else clause is not necessary.${TEXT_RESET}`,
+        message: `else clauses found ${BG_ERR}(${file.elseCount})${BG_RESET} 🚨`,
+      })
     })
   }
-  return elseConditionFiles.length
+  return offenses
 }
 
-export { checkElseCondition, reportElseCondition }
+const resetElseCondition = () => (elseConditionFiles.length = 0)
+
+export { checkElseCondition, reportElseCondition, resetElseCondition }
diff --git a/src/rules/rrd/functionSize.ts b/src/rules/rrd/functionSize.ts
index 43041ce..3f5888d 100644
--- a/src/rules/rrd/functionSize.ts
+++ b/src/rules/rrd/functionSize.ts
@@ -1,25 +1,26 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
-import { getUniqueFilenameCount } from '../../helpers'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
-type FunctionSizeFile = {
+interface FunctionSizeFile {
   filename: string
   funcName: string
 }
 
 const functionSizeFiles: FunctionSizeFile[] = []
 
-const MAX_FUNCTION_LENGTH = 20 // completely rrd made-up number
+export const MAX_FUNCTION_LENGTH = 20 // completely rrd made-up number
 
 const checkFunctionSize = (script: SFCScriptBlock | null, filePath: string) => {
   if (!script) {
     return
   }
   // Regular expression to match function definitions (both regular and arrow functions)
-  const regex =
-    /function\s+([a-zA-Z0-9_$]+)\s*\([^)]*\)\s*{([^{}]*(([^{}]*{[^{}]*}[^{}]*)*[^{}]*))}|const\s+([a-zA-Z0-9_$]+)\s*=\s*\([^)]*\)\s*=>\s*{([^{}]*(([^{}]*{[^{}]*}[^{}]*)*[^{}]*))}/g
+  const regex
+    = /function\s+([\w$]+)\s*\([^)]*\)\s*\{([^{}]*(([^{}]*\{[^{}]*\}[^{}]*)*[^{}]*))\}|const\s+([\w$]+)\s*=\s*\([^)]*\)\s*=>\s*\{([^{}]*(([^{}]*\{[^{}]*\}[^{}]*)*[^{}]*))\}/g
   let match
 
+  // eslint-disable-next-line no-cond-assign
   while ((match = regex.exec(script.content)) !== null) {
     /*
       We use match[1] and match[2] for regular functions
@@ -37,19 +38,21 @@ const checkFunctionSize = (script: SFCScriptBlock | null, filePath: string) => {
 }
 
 const reportFunctionSize = () => {
+  const offenses: Offense[] = []
+
   if (functionSizeFiles.length > 0) {
-    // Count only non duplicated objects (by its `filename` property)
-    const fileCount = getUniqueFilenameCount<FunctionSizeFile>(functionSizeFiles, 'filename')
-
-    console.log(
-      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}function size${BG_RESET} exceeds recommended limit in ${fileCount} files.`
-    )
-    console.log(`👉 ${TEXT_WARN}Functions must be shorter than ${MAX_FUNCTION_LENGTH} lines${TEXT_RESET}`)
-    functionSizeFiles.forEach(file => {
-      console.log(`- ${file.filename} 🚨 ${BG_WARN}(${file.funcName})${BG_RESET}`)
+    functionSizeFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}rrd ~ function size${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Functions must be shorter than ${MAX_FUNCTION_LENGTH} lines${TEXT_RESET}`,
+        message: `function ${BG_ERR}(${file.funcName})${BG_RESET} 🚨`,
+      })
     })
   }
-  return functionSizeFiles.length
+  return offenses
 }
 
-export { checkFunctionSize, reportFunctionSize }
+const resetFunctionSize = () => (functionSizeFiles.length = 0)
+
+export { checkFunctionSize, reportFunctionSize, resetFunctionSize }
diff --git a/src/rules/rrd/parameterCount.ts b/src/rules/rrd/parameterCount.ts
index b3b68b1..ec0846f 100644
--- a/src/rules/rrd/parameterCount.ts
+++ b/src/rules/rrd/parameterCount.ts
@@ -1,9 +1,10 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
-const parameterCountFiles: { filename: string; funcName: string; paramsCount: number }[] = []
+const parameterCountFiles: { filename: string, funcName: string, paramsCount: number }[] = []
 
-const MAX_PARAMETER_COUNT = 3 // completely rrd made-up number
+export const MAX_PARAMETER_COUNT = 3 // completely rrd made-up number
 
 // Function used in both scenarios (regular and arrow function) to count parameters
 const checkParameters = (funcName: string, params: string, filePath: string) => {
@@ -21,14 +22,16 @@ const checkParameterCount = (script: SFCScriptBlock | null, filePath: string) =>
     return
   }
   // regular expression to match both regular and arrow functions and capture their params
-  const regex = /function\s+([a-zA-Z0-9_$]+)\s*\(([^)]*)\)\s*{|const\s+([a-zA-Z0-9_$]+)\s*=\s*\(([^)]*)\)\s*=>\s*{/g
+  const regex = /function\s+([\w$]+)\s*\(([^)]*)\)\s*\{|const\s+([\w$]+)\s*=\s*\(([^)]*)\)\s*=>\s*\{/g
   let match
 
+  // eslint-disable-next-line no-cond-assign
   while ((match = regex.exec(script.content)) !== null) {
     if (match[1]) {
       // Regular function
       checkParameters(match[1], match[2], filePath) // match[2] are the params for current regular function
-    } else if (match[3]) {
+    }
+    else if (match[3]) {
       // Arrow function
       checkParameters(match[3], match[4], filePath) // match[4] are the params for current arrow function
     }
@@ -36,19 +39,22 @@ const checkParameterCount = (script: SFCScriptBlock | null, filePath: string) =>
 }
 
 const reportParameterCount = () => {
+  const offenses: Offense[] = []
+
   if (parameterCountFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}parameter count${BG_RESET} exceeds recommended limit in ${parameterCountFiles.length} files.`
-    )
-    console.log(`👉 ${TEXT_WARN}Max number of function parameters should be ${MAX_PARAMETER_COUNT}${TEXT_RESET}`)
-    parameterCountFiles.forEach(file => {
-      console.log(
-        `- ${BG_WARN}${file.funcName}${BG_RESET} in file ${file.filename} 🚨 ${BG_WARN}(${file.paramsCount})${BG_RESET}`
-      )
+    parameterCountFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}rrd ~ parameter count${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Max number of function parameters should be ${MAX_PARAMETER_COUNT}${TEXT_RESET}`,
+        message: `function ${BG_WARN}${file.funcName}${BG_RESET} has ${BG_WARN}${file.paramsCount}${BG_RESET} parameters 🚨`,
+      })
     })
   }
 
-  return parameterCountFiles.length
+  return offenses
 }
 
-export { checkParameterCount, reportParameterCount }
+const resetParameterCount = () => (parameterCountFiles.length = 0)
+
+export { checkParameterCount, reportParameterCount, resetParameterCount }
diff --git a/src/rules/rrd/plainScript.ts b/src/rules/rrd/plainScript.ts
index d0ff77f..e1c8898 100644
--- a/src/rules/rrd/plainScript.ts
+++ b/src/rules/rrd/plainScript.ts
@@ -1,5 +1,6 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const plainScriptFiles: string[] = []
 
@@ -11,16 +12,19 @@ const checkPlainScript = (script: SFCScriptBlock | null, filePath: string) => {
 }
 
 const reportPlainScript = () => {
+  const offenses: Offense[] = []
+
   if (plainScriptFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}Plain <script> blocks${BG_RESET} in ${plainScriptFiles.length} files.`
-    )
-    console.log(`👉 ${TEXT_WARN} Consider using <script setup> to leverage the new SFC <script> syntax.${TEXT_RESET}`)
-    plainScriptFiles.forEach(file => {
-      console.log(`- ${file}`)
+    plainScriptFiles.forEach((file) => {
+      offenses.push({
+        file,
+        rule: `${TEXT_INFO}rrd ~ Plain <script> blocks${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN} Consider using <script setup> to leverage the new SFC <script> syntax.${TEXT_RESET}`,
+        message: `🚨`,
+      })
     })
   }
-  return plainScriptFiles.length
+  return offenses
 }
 
 export { checkPlainScript, reportPlainScript }
diff --git a/src/rules/rrd/scriptLength.ts b/src/rules/rrd/scriptLength.ts
index 165b592..c4c9e23 100644
--- a/src/rules/rrd/scriptLength.ts
+++ b/src/rules/rrd/scriptLength.ts
@@ -1,9 +1,10 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_ERR, BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
-const MAX_SCRIPT_LENGTH = 100
+export const MAX_SCRIPT_LENGTH = 100
 
-const longScriptFiles: { fileName: string; scriptLength: number }[] = []
+const longScriptFiles: { fileName: string, scriptLength: number }[] = []
 
 const checkScriptLength = (script: SFCScriptBlock | null, file: string) => {
   if (!script) {
@@ -16,22 +17,23 @@ const checkScriptLength = (script: SFCScriptBlock | null, file: string) => {
 }
 
 const reportScriptLength = () => {
+  const offenses: Offense[] = []
+
   if (longScriptFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_ERR}Long <script> blocks${BG_RESET} in ${longScriptFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Try to refactor out the logic into composable functions or other files and keep the script block's length under ${MAX_SCRIPT_LENGTH} lines.${TEXT_RESET}`
-    )
-    longScriptFiles.forEach(file => {
-      console.log(
-        `- ${file.fileName} ${file.scriptLength > MAX_SCRIPT_LENGTH * 2 ? BG_ERR : BG_WARN}(${
+    longScriptFiles.forEach((file) => {
+      offenses.push({
+        file: file.fileName,
+        rule: `${TEXT_INFO}rrd ~ Long <script> blocks${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Try to refactor out the logic into composable functions or other files and keep the script block's length under ${MAX_SCRIPT_LENGTH} lines.${TEXT_RESET}`,
+        message: `${file.scriptLength > MAX_SCRIPT_LENGTH * 2 ? BG_ERR : BG_WARN}(${
           file.scriptLength
-        } lines)${BG_RESET}`
-      )
+        } lines)${BG_RESET} 🚨`,
+      })
     })
   }
-  return longScriptFiles.length
+  return offenses
 }
 
-export { checkScriptLength, reportScriptLength }
+const resetScriptLength = () => (longScriptFiles.length = 0)
+
+export { checkScriptLength, reportScriptLength, resetScriptLength }
diff --git a/src/rules/rrd/shortVariableName.ts b/src/rules/rrd/shortVariableName.ts
index c6189a8..4b44d68 100644
--- a/src/rules/rrd/shortVariableName.ts
+++ b/src/rules/rrd/shortVariableName.ts
@@ -1,13 +1,13 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
-import { getUniqueFilenameCount } from '../../helpers'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
-type ShortVariableNameFile = {
+interface ShortVariableNameFile {
   filename: string
   variable: string
 }
 
-const MIN_VARIABLE_NAME = 4 // completely rrd made-up number
+export const MIN_VARIABLE_NAME = 4 // completely rrd made-up number
 
 const shortVariableNameFile: ShortVariableNameFile[] = []
 
@@ -16,9 +16,10 @@ const checkShortVariableName = (script: SFCScriptBlock | null, filePath: string)
     return
   }
   // Regular expression to match variable names
-  const regex = /\b(?:const|var|let)\s+([a-zA-Z_$][a-zA-Z0-9_$]*)/g
+  const regex = /\b(?:const|var|let)\s+([a-zA-Z_$][\w$]*)/g
 
   let match
+  // eslint-disable-next-line no-cond-assign
   while ((match = regex.exec(script.content)) !== null) {
     const variable = match[1]
 
@@ -29,17 +30,21 @@ const checkShortVariableName = (script: SFCScriptBlock | null, filePath: string)
 }
 
 const reportShortVariableName = () => {
-  if (shortVariableNameFile.length > 0) {
-    // Count only non duplicated objects (by its `filename` property)
-    const fileCount = getUniqueFilenameCount<ShortVariableNameFile>(shortVariableNameFile, 'filename')
+  const offenses: Offense[] = []
 
-    console.log(`\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}variable names${BG_RESET} are too short in ${fileCount} files.`)
-    console.log(`👉 ${TEXT_WARN}Variable names must have a minimum length of ${MIN_VARIABLE_NAME}${TEXT_RESET}`)
-    shortVariableNameFile.forEach(file => {
-      console.log(`- ${file.filename} 🚨 ${BG_WARN}(${file.variable})${BG_RESET}`)
+  if (shortVariableNameFile.length > 0) {
+    shortVariableNameFile.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}rrd ~ short variable names${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Variable names must have a minimum length of ${MIN_VARIABLE_NAME}${TEXT_RESET}`,
+        message: `${BG_ERR}(${file.variable})${BG_RESET} 🚨`,
+      })
     })
   }
-  return shortVariableNameFile.length
+  return offenses
 }
 
-export { checkShortVariableName, reportShortVariableName }
+const resetShortVariableName = () => (shortVariableNameFile.length = 0)
+
+export { checkShortVariableName, reportShortVariableName, resetShortVariableName }
diff --git a/src/rules/rrd/tooManyProps.ts b/src/rules/rrd/tooManyProps.ts
index ab53d55..7051607 100644
--- a/src/rules/rrd/tooManyProps.ts
+++ b/src/rules/rrd/tooManyProps.ts
@@ -1,8 +1,9 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
-import { char, charNotIn, createRegExp, exactly, maybe, oneOrMore, whitespace } from 'magic-regexp'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { char, createRegExp, maybe, oneOrMore } from 'magic-regexp'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
-const tooManyPropsFiles: { fileName: string; propsCount: number }[] = []
+const tooManyPropsFiles: { fileName: string, propsCount: number }[] = []
 const TOO_MANY_PROPS = 5
 
 const checkTooManyProps = (script: SFCScriptBlock | null, file: string) => {
@@ -14,22 +15,27 @@ const checkTooManyProps = (script: SFCScriptBlock | null, file: string) => {
   if (matches?.length) {
     const propsCount = matches[0].split(',').length
     if (propsCount > TOO_MANY_PROPS) {
-      tooManyPropsFiles.push({ fileName: file, propsCount: propsCount })
+      tooManyPropsFiles.push({ fileName: file, propsCount })
     }
   }
 }
 
 const reportTooManyProps = () => {
+  const offenses: Offense[] = []
+
   if (tooManyPropsFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}rrd${TEXT_RESET} ${BG_WARN}too many props${BG_RESET} are used in ${tooManyPropsFiles.length} files.`
-    )
-    console.log(`👉 ${TEXT_WARN}Try to refactor your code to use less properties.${TEXT_RESET}`)
-    tooManyPropsFiles.forEach(file => {
-      console.log(`- ${file.fileName} ${BG_WARN}(${file.propsCount})${BG_RESET}`)
+    tooManyPropsFiles.forEach((file) => {
+      offenses.push({
+        file: file.fileName,
+        rule: `${TEXT_INFO}rrd ~ too many props${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Try to refactor your code to use less properties.${TEXT_RESET}`,
+        message: `props found ${BG_ERR}(${file.propsCount})${BG_RESET} 🚨`,
+      })
     })
   }
-  return tooManyPropsFiles.length
+  return offenses
 }
 
-export { checkTooManyProps, reportTooManyProps }
+const resetTooManyProps = () => (tooManyPropsFiles.length = 0)
+
+export { checkTooManyProps, reportTooManyProps, resetTooManyProps }
diff --git a/src/rules/vue-caution/implicitParentChildCommunication.ts b/src/rules/vue-caution/implicitParentChildCommunication.ts
index 8fd0c2b..c841d5e 100644
--- a/src/rules/vue-caution/implicitParentChildCommunication.ts
+++ b/src/rules/vue-caution/implicitParentChildCommunication.ts
@@ -1,10 +1,10 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
-import getLineNumber from '../getLineNumber'
-import { getUniqueFilenameCount } from '../../helpers'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
 import { createRegExp, exactly, global } from 'magic-regexp'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import getLineNumber from '../getLineNumber'
+import type { Offense } from '../../types'
 
-type ImplicitParentChildCommunicationFile = { filename: string; message: string }
+interface ImplicitParentChildCommunicationFile { filename: string, message: string }
 
 const implicitParentChildCommunicationFiles: ImplicitParentChildCommunicationFile[] = []
 
@@ -14,7 +14,7 @@ const checkImplicitParentChildCommunication = (script: SFCScriptBlock | null, fi
   }
   const propsRegex = /defineProps\(([^)]+)\)/
   const vModelRegex = /v-model\s*=\s*"([^"]+)"/
-  const parentRegex = createRegExp(exactly("$parent").or("getCurrentInstance"), [global]);
+  const parentRegex = createRegExp(exactly('$parent').or('getCurrentInstance'), [global])
 
   // Extract defined props
   const propsMatch = script.content.match(propsRegex)
@@ -30,7 +30,7 @@ const checkImplicitParentChildCommunication = (script: SFCScriptBlock | null, fi
       const lineNumber = getLineNumber(script.content.trim(), definedProps)
       implicitParentChildCommunicationFiles.push({
         filename: filePath,
-        message: `${filePath}#${lineNumber} ${BG_WARN}(${vModelProp})${BG_RESET}`,
+        message: `line #${lineNumber} ${BG_WARN}(${vModelProp})${BG_RESET}`,
       })
     }
   }
@@ -41,30 +41,28 @@ const checkImplicitParentChildCommunication = (script: SFCScriptBlock | null, fi
     const lineNumber = getLineNumber(script.content.trim(), parentMatch[0])
     implicitParentChildCommunicationFiles.push({
       filename: filePath,
-      message: `${filePath}#${lineNumber} ${BG_WARN}(${parentMatch[0]})${BG_RESET}`,
+      message: `line #${lineNumber} ${BG_WARN}(${parentMatch[0]})${BG_RESET}`,
     })
   }
 }
 
 const reportImplicitParentChildCommunication = () => {
-  if (implicitParentChildCommunicationFiles.length > 0) {
-    // Count only non duplicated objects (by its `filename` property)
-    const fileCount = getUniqueFilenameCount<ImplicitParentChildCommunicationFile>(
-      implicitParentChildCommunicationFiles,
-      'filename'
-    )
+  const offenses: Offense[] = []
 
-    console.log(
-      `\n${TEXT_INFO}vue-caution${TEXT_RESET} ${BG_WARN}implicit parent-child communication${BG_RESET} detected in ${fileCount} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Avoid implicit parent-child communication to maintain clear and predictable component behavior.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-use-with-caution.html#implicit-parent-child-communication`
-    )
-    implicitParentChildCommunicationFiles.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+  if (implicitParentChildCommunicationFiles.length > 0) {
+    implicitParentChildCommunicationFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-caution ~ implicit parent-child communication${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Avoid implicit parent-child communication to maintain clear and predictable component behavior.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-use-with-caution.html#implicit-parent-child-communication`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return implicitParentChildCommunicationFiles.length
+
+  return offenses
 }
 
-export { checkImplicitParentChildCommunication, reportImplicitParentChildCommunication }
+const resetImplicitParentChildCommunicationFiles = () => (implicitParentChildCommunicationFiles.length = 0)
+
+export { checkImplicitParentChildCommunication, reportImplicitParentChildCommunication, resetImplicitParentChildCommunicationFiles }
diff --git a/src/rules/vue-essential/globalStyle.ts b/src/rules/vue-essential/globalStyle.ts
index 96e24bf..2ff11c2 100644
--- a/src/rules/vue-essential/globalStyle.ts
+++ b/src/rules/vue-essential/globalStyle.ts
@@ -1,5 +1,6 @@
-import { SFCStyleBlock } from '@vue/compiler-sfc'
-import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { SFCStyleBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const globalStyleFiles: { filePath: string }[] = []
 
@@ -7,7 +8,7 @@ const checkGlobalStyle = (styles: SFCStyleBlock[] | null, filePath: string) => {
   if (!styles) {
     return
   }
-  styles.forEach(style => {
+  styles.forEach((style) => {
     if (!style.scoped) {
       globalStyleFiles.push({ filePath })
     }
@@ -15,18 +16,21 @@ const checkGlobalStyle = (styles: SFCStyleBlock[] | null, filePath: string) => {
 }
 
 const reportGlobalStyle = () => {
+  const offenses: Offense[] = []
+
   if (globalStyleFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-essential${TEXT_RESET} ${BG_ERR}Global style ${BG_RESET} is used in ${globalStyleFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Use <style scoped>.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-component-scoped-styling`
-    )
-    globalStyleFiles.forEach(file => {
-      console.log(`- ${file.filePath} 🚨`)
+    globalStyleFiles.forEach((file) => {
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}vue-essential ~ global style${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Use <style scoped>.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-component-scoped-styling`,
+        message: `🚨`,
+      })
     })
   }
-  return globalStyleFiles.length
+  return offenses
 }
 
-export { checkGlobalStyle, reportGlobalStyle }
+const resetGlobalStyle = () => (globalStyleFiles.length = 0)
+
+export { checkGlobalStyle, reportGlobalStyle, resetGlobalStyle }
diff --git a/src/rules/vue-essential/simpleProp.ts b/src/rules/vue-essential/simpleProp.ts
index 3f52b94..c09ab3b 100644
--- a/src/rules/vue-essential/simpleProp.ts
+++ b/src/rules/vue-essential/simpleProp.ts
@@ -1,6 +1,7 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO } from '../asceeCodes'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
 import { caseInsensitive, createRegExp, global } from 'magic-regexp'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const simplePropFiles: { filePath: string }[] = []
 
@@ -17,18 +18,21 @@ const checkSimpleProp = (script: SFCScriptBlock | null, filePath: string) => {
 }
 
 const reportSimpleProp = () => {
+  const offenses: Offense[] = []
+
   if (simplePropFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-essential${TEXT_RESET} ${BG_ERR}simple prop${BG_RESET} is used in ${simplePropFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Add at least type definition.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions`
-    )
-    simplePropFiles.forEach(file => {
-      console.log(`- ${file.filePath} 🚨`)
+    simplePropFiles.forEach((file) => {
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}vue-essential ~ simple prop${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Add at least type definition.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions`,
+        message: `🚨`,
+      })
     })
   }
-  return simplePropFiles.length
+  return offenses
 }
 
-export { checkSimpleProp, reportSimpleProp }
+const resetSimpleProp = () => (simplePropFiles.length = 0)
+
+export { checkSimpleProp, reportSimpleProp, resetSimpleProp }
diff --git a/src/rules/vue-essential/singleNameComponent.ts b/src/rules/vue-essential/singleNameComponent.ts
index cec93c1..a113af0 100644
--- a/src/rules/vue-essential/singleNameComponent.ts
+++ b/src/rules/vue-essential/singleNameComponent.ts
@@ -1,6 +1,7 @@
-import path from 'path'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO } from '../asceeCodes'
+import path from 'node:path'
 import { createRegExp, letter } from 'magic-regexp'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const singleNameComponentFiles: { filePath: string }[] = []
 
@@ -11,7 +12,8 @@ const checkSingleNameComponent = (filePath: string) => {
   }
 
   const fileName = path.basename(filePath)
-  if (fileName === 'App.vue') return
+  if (fileName === 'App.vue')
+    return
 
   const regex = createRegExp(letter.uppercase)
   const matches = fileName.slice(1).match(regex) // ignore the first character
@@ -22,18 +24,21 @@ const checkSingleNameComponent = (filePath: string) => {
 }
 
 const reportSingleNameComponent = () => {
+  const offenses: Offense[] = []
+
   if (singleNameComponentFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-essential${TEXT_RESET} ${BG_ERR}single name component${BG_RESET} is used in ${singleNameComponentFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Rename the component to use multi-word name.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-multi-word-component-names`
-    )
-    singleNameComponentFiles.forEach(file => {
-      console.log(`- ${file.filePath} 🚨`)
+    singleNameComponentFiles.forEach((file) => {
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}vue-essential ~ single name component${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Rename the component to use multi-word name.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-multi-word-component-names`,
+        message: `🚨`,
+      })
     })
   }
-  return singleNameComponentFiles.length
+  return offenses
 }
 
-export { checkSingleNameComponent, reportSingleNameComponent }
+const resetSingleNameComponent = () => (singleNameComponentFiles.length = 0)
+
+export { checkSingleNameComponent, reportSingleNameComponent, resetSingleNameComponent }
diff --git a/src/rules/vue-essential/vforNoKey.ts b/src/rules/vue-essential/vforNoKey.ts
index 987454a..9118408 100644
--- a/src/rules/vue-essential/vforNoKey.ts
+++ b/src/rules/vue-essential/vforNoKey.ts
@@ -1,6 +1,7 @@
-import { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO } from '../asceeCodes'
+import type { SFCTemplateBlock } from '@vue/compiler-sfc'
 import { caseInsensitive, charNotIn, createRegExp, global, oneOrMore } from 'magic-regexp'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const vforNoKeyFiles: { filePath: string }[] = []
 
@@ -23,18 +24,21 @@ const checkVforNoKey = (template: SFCTemplateBlock | null, filePath: string) =>
 }
 
 const reportVforNoKey = () => {
+  const offenses: Offense[] = []
+
   if (vforNoKeyFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-essential${TEXT_RESET} ${BG_ERR}v-for has no key${BG_RESET} in ${vforNoKeyFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Add a \`:key\` property to all v-for.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-keyed-v-for`
-    )
-    vforNoKeyFiles.forEach(file => {
-      console.log(`- ${file.filePath} 🚨`)
+    vforNoKeyFiles.forEach((file) => {
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}vue-essential ~ v-for has no key${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Add a \`:key\` property to all v-for.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-keyed-v-for`,
+        message: `🚨`,
+      })
     })
   }
-  return vforNoKeyFiles.length
+  return offenses
 }
 
-export { checkVforNoKey, reportVforNoKey }
+const resetReportVForNoKey = () => (vforNoKeyFiles.length = 0)
+
+export { checkVforNoKey, reportVforNoKey, resetReportVForNoKey }
diff --git a/src/rules/vue-essential/vifWithVfor.ts b/src/rules/vue-essential/vifWithVfor.ts
index 80a0de8..6ad2601 100644
--- a/src/rules/vue-essential/vifWithVfor.ts
+++ b/src/rules/vue-essential/vifWithVfor.ts
@@ -1,6 +1,7 @@
-import { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO } from '../asceeCodes'
+import type { SFCTemplateBlock } from '@vue/compiler-sfc'
 import { caseInsensitive, charNotIn, createRegExp, global, oneOrMore } from 'magic-regexp'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const vifWithVforFiles: { filePath: string }[] = []
 
@@ -16,7 +17,7 @@ const checkVifWithVfor = (template: SFCTemplateBlock | null, filePath: string) =
     ' v-for',
     oneOrMore(charNotIn('>')),
     '>',
-    [global, caseInsensitive]
+    [global, caseInsensitive],
   )
   const regex2 = createRegExp(
     '<',
@@ -26,7 +27,7 @@ const checkVifWithVfor = (template: SFCTemplateBlock | null, filePath: string) =
     ' v-if',
     oneOrMore(charNotIn('>')),
     '>',
-    [global, caseInsensitive]
+    [global, caseInsensitive],
   )
   const matches1 = template.content.match(regex1)
   const matches2 = template.content.match(regex2)
@@ -37,18 +38,21 @@ const checkVifWithVfor = (template: SFCTemplateBlock | null, filePath: string) =
 }
 
 const reportVifWithVfor = () => {
+  const offenses: Offense[] = []
+
   if (vifWithVforFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-essential${TEXT_RESET} ${BG_ERR}v-if used with v-for${BG_RESET} in ${vifWithVforFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Move out the v-if to a computed property.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for`
-    )
-    vifWithVforFiles.forEach(file => {
-      console.log(`- ${file.filePath} 🚨`)
+    vifWithVforFiles.forEach((file) => {
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}vue-essential ~ v-if used with v-for${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Move out the v-if to a computed property.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for`,
+        message: `🚨`,
+      })
     })
   }
-  return vifWithVforFiles.length
+  return offenses
 }
 
-export { checkVifWithVfor, reportVifWithVfor }
+const resetVIfWithVFor = () => (vifWithVforFiles.length = 0)
+
+export { checkVifWithVfor, reportVifWithVfor, resetVIfWithVFor }
diff --git a/src/rules/vue-recommended/elementAttributeOrder.ts b/src/rules/vue-recommended/elementAttributeOrder.ts
index 55d3790..c73cf81 100644
--- a/src/rules/vue-recommended/elementAttributeOrder.ts
+++ b/src/rules/vue-recommended/elementAttributeOrder.ts
@@ -1,7 +1,7 @@
 /* eslint-disable no-cond-assign */
 import type { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { BG_ERR, BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
-import { getUniqueFilenameCount } from '../../helpers'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 interface ElementAttributeOrder { filename: string, message: string }
 
@@ -54,7 +54,7 @@ const checkElementAttributeOrder = (template: SFCTemplateBlock | null, filePath:
         if (currIdx !== -1 && currIdx < lastIdx) {
           elementAttributeOrderFiles.push({
             filename: filePath,
-            message: `${filePath} tag has attributes out of order ${BG_WARN}(${tagName})${BG_RESET}`,
+            message: `tag has attributes out of order ${BG_WARN}(${tagName})${BG_RESET}`,
           })
           break
         }
@@ -65,19 +65,21 @@ const checkElementAttributeOrder = (template: SFCTemplateBlock | null, filePath:
 }
 
 const reportElementAttributeOrder = () => {
-  if (elementAttributeOrderFiles.length > 0) {
-    // count only non duplicated objects (by its `filename` property)
-    const fileCount = getUniqueFilenameCount<ElementAttributeOrder>(elementAttributeOrderFiles, 'filename')
+  const offenses: Offense[] = []
 
-    console.log(`\n${TEXT_INFO}vue-recommended${TEXT_RESET} ${BG_ERR}element attribute order ${BG_RESET} detected in ${fileCount} files.`)
-    console.log(
-            `👉 ${TEXT_WARN}The attributes of elements (including components) should be ordered consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#element-attribute-order`,
-    )
+  if (elementAttributeOrderFiles.length > 0) {
     elementAttributeOrderFiles.forEach((file) => {
-      console.log(`- ${file.message} 🚨`)
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-recommended ~ element attribute order${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}The attributes of elements (including components) should be ordered consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#element-attribute-order`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return elementAttributeOrderFiles.length
+  return offenses
 }
 
-export { checkElementAttributeOrder, reportElementAttributeOrder }
+const resetElementAttributeOrder = () => (elementAttributeOrderFiles.length = 0)
+
+export { checkElementAttributeOrder, reportElementAttributeOrder, resetElementAttributeOrder }
diff --git a/src/rules/vue-recommended/topLevelElementOrder.ts b/src/rules/vue-recommended/topLevelElementOrder.ts
index e1e6ef8..5a980c9 100644
--- a/src/rules/vue-recommended/topLevelElementOrder.ts
+++ b/src/rules/vue-recommended/topLevelElementOrder.ts
@@ -1,48 +1,56 @@
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO } from '../asceeCodes'
+import type { Offense } from '../../types'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 
-type TopLevelElementOrder = { filename: string }
+interface TopLevelElementOrder { filename: string }
 
 const topLevelElementOrderFiles: TopLevelElementOrder[] = []
 
 /* The opinionated correct order is: script, template, style */
 const checkTopLevelElementOrder = (source: string, filePath: string) => {
   // Apply `toString()` because it throws `indexOf` error otherwise
-  const content = source.toString();
+  const content = source.toString()
 
-  const scriptIdx = content.indexOf('<script setup>');
-  const templateIdx = content.indexOf('<template>');
-  const styleIdx = content.indexOf('<style>');
+  const scriptIdx = content.indexOf('<script setup>')
+  const templateIdx = content.indexOf('<template>')
+  const styleIdx = content.indexOf('<style>')
 
   // Create an array of present elements and their indices
   const elements = [
     { name: 'script', index: scriptIdx },
     { name: 'template', index: templateIdx },
-    { name: 'style', index: styleIdx }
-  ].filter(el => el.index !== -1);
+    { name: 'style', index: styleIdx },
+  ].filter(el => el.index !== -1)
 
   // Check if the order is correct
   const isCorrectOrder = elements.every((el, idx) => {
-    if (idx === 0) return true;
+    if (idx === 0)
+      return true
     return elements[idx - 1].index < el.index
-  });
+  })
 
-  if (isCorrectOrder) return; // If it's correct, do nothing
+  if (isCorrectOrder)
+    return // If it's correct, do nothing
 
-  topLevelElementOrderFiles.push({ filename: filePath });
+  topLevelElementOrderFiles.push({ filename: filePath })
 }
 
 const reportTopLevelElementOrder = () => {
+  const offenses: Offense[] = []
+
   if (topLevelElementOrderFiles.length > 0) {
-    console.log(`\n${TEXT_INFO}vue-recommended${TEXT_RESET} ${BG_WARN}SFC top-level element order${BG_RESET} detected in ${topLevelElementOrderFiles.length} files.`)
-    console.log(
-      `👉 ${TEXT_WARN}Single-File Components should always order <script>, <template>, and <style> tags consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#single-file-component-top-level-element-order`
-    )
-    topLevelElementOrderFiles.forEach(file => {
-      console.log(` - ${file.filename} 🚨`)
+    topLevelElementOrderFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-recommended ~ top level element order${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Single-File Components should always order <script>, <template>, and <style> tags consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#single-file-component-top-level-element-order`,
+        message: '🚨',
+      })
     })
   }
-  return topLevelElementOrderFiles.length;
+
+  return offenses
 }
 
-export { checkTopLevelElementOrder, reportTopLevelElementOrder }
+const resetTopLevelElementOrder = () => (topLevelElementOrderFiles.length = 0)
 
+export { checkTopLevelElementOrder, reportTopLevelElementOrder, resetTopLevelElementOrder }
diff --git a/src/rules/vue-strong/componentFilenameCasing.ts b/src/rules/vue-strong/componentFilenameCasing.ts
index 4b91f82..95b54ca 100644
--- a/src/rules/vue-strong/componentFilenameCasing.ts
+++ b/src/rules/vue-strong/componentFilenameCasing.ts
@@ -1,13 +1,16 @@
-import path from 'path'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO } from '../asceeCodes'
+import path from 'node:path'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const componentFilenameCasingFiles: { fileName: string }[] = []
 
 const checkComponentFilenameCasing = (filePath: string) => {
-  if (filePath.includes('pages') || filePath.includes('layouts')) return
+  if (filePath.includes('pages') || filePath.includes('layouts'))
+    return
 
   const fileName = path.basename(filePath)
 
+  // eslint-disable-next-line regexp/optimal-quantifier-concatenation, regexp/no-useless-assertions, regexp/no-optional-assertion
   const regexPascalCase = /^[A-Z][a-zA-Z0-9]+(?:(?<!^)(?=[A-Z]))*.vue$/
   const matchesPascalCase = fileName.match(regexPascalCase)
 
@@ -20,18 +23,19 @@ const checkComponentFilenameCasing = (filePath: string) => {
 }
 
 const reportComponentFilenameCasing = () => {
+  const offenses: Offense[] = []
+
   if (componentFilenameCasingFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}component name is not PascalCase and not kebab-case${BG_RESET} in ${componentFilenameCasingFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Rename the component to use PascalCase or kebab-case file name.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#single-file-component-filename-casing`
-    )
-    componentFilenameCasingFiles.forEach(file => {
-      console.log(`- ${BG_ERR}${file.fileName}${BG_RESET}`)
+    componentFilenameCasingFiles.forEach((file) => {
+      offenses.push({
+        file: file.fileName,
+        rule: `${TEXT_INFO}vue-strong ~ component name is not PascalCase and not kebab-case${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Rename the component to use PascalCase or kebab-case file name.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#single-file-component-filename-casing`,
+        message: `🚨`,
+      })
     })
   }
-  return componentFilenameCasingFiles.length
+  return offenses
 }
 
 const resetComponentFilenameCasing = () => (componentFilenameCasingFiles.length = 0)
diff --git a/src/rules/vue-strong/componentFiles.ts b/src/rules/vue-strong/componentFiles.ts
index 8701daa..162f0b2 100644
--- a/src/rules/vue-strong/componentFiles.ts
+++ b/src/rules/vue-strong/componentFiles.ts
@@ -1,9 +1,9 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO, BG_ERR } from '../asceeCodes'
-import { getUniqueFilenameCount } from '../../helpers'
+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 { Offense } from '../../types'
 
-type ComponentFiles = { filename: string; message: string }
+interface ComponentFiles { filename: string, message: string }
 
 const componentFiles: ComponentFiles[] = []
 
@@ -16,27 +16,29 @@ const checkComponentFiles = (script: SFCScriptBlock | null, filePath: string) =>
 
   const matches = [...script.content.matchAll(regex)].map(match => match[1].trim())
 
-  matches.forEach(match => {
+  matches.forEach((match) => {
     const lineNumber = getLineNumber(script.content.trim(), match)
     const firstPart = match.split('\n').at(0)?.trim() || ''
-    componentFiles.push({ filename: filePath, message: `${filePath}#${lineNumber} ${BG_WARN}(${firstPart})${BG_RESET}` })
+    componentFiles.push({ filename: filePath, message: `line #${lineNumber} ${BG_WARN}(${firstPart})${BG_RESET}` })
   })
 }
 
 const reportComponentFiles = () => {
+  const offenses: Offense[] = []
+
   if (componentFiles.length > 0) {
-    // count only non duplicated objects (by its `filename` property)
-    const fileCount = getUniqueFilenameCount<ComponentFiles>(componentFiles, 'filename')
-
-    console.log(`\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}component files${BG_RESET} detected in ${fileCount} files.`)
-    console.log(
-      `👉 ${TEXT_WARN}Whenever a build system is available to concatenate files, each component should be in its own file.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files`
-    )
-    componentFiles.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+    componentFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-strong ~ component files${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Whenever a build system is available to concatenate files, each component should be in its own file.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return componentFiles.length
+  return offenses
 }
 
-export { checkComponentFiles, reportComponentFiles }
+const resetComponentFiles = () => (componentFiles.length = 0)
+
+export { checkComponentFiles, reportComponentFiles, resetComponentFiles }
diff --git a/src/rules/vue-strong/directiveShorthands.ts b/src/rules/vue-strong/directiveShorthands.ts
index 20dc661..0ca4731 100644
--- a/src/rules/vue-strong/directiveShorthands.ts
+++ b/src/rules/vue-strong/directiveShorthands.ts
@@ -1,8 +1,9 @@
-import { SFCDescriptor } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO, BG_WARN } from '../asceeCodes'
+import type { SFCDescriptor } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 import getLineNumber from '../getLineNumber'
+import type { Offense } from '../../types'
 
-const directiveShorthandsTargets: { message: string }[] = []
+const directiveShorthandsTargets: { filename: string, message: string }[] = []
 const directiveShorthandsFiles: { filePath: string }[] = []
 
 const directivesToCheck = ['v-slot', 'v-bind', 'v-on']
@@ -13,31 +14,37 @@ const checkDirectiveShorthands = (descriptor: SFCDescriptor | null, filePath: st
   }
   const template = descriptor.template! // it is exists otherwise the function is not called at all
 
-  directivesToCheck.forEach(directive => {
+  directivesToCheck.forEach((directive) => {
     if (template.content.includes(`${directive}:`)) {
       const lineNumber = getLineNumber(descriptor.source, directive)
-      directiveShorthandsTargets.push({ message: `${filePath}:${lineNumber} ${BG_WARN}${directive}${BG_RESET}` })
+      directiveShorthandsTargets.push({ filename: filePath, message: `line #${lineNumber} ${BG_WARN}${directive}${BG_RESET}` })
 
       if (!directiveShorthandsFiles.some(file => file.filePath === filePath)) {
-        directiveShorthandsFiles.push({ filePath: filePath })
+        directiveShorthandsFiles.push({ filePath })
       }
     }
   })
 }
 
 const reportDirectiveShorthands = () => {
+  const offenses: Offense[] = []
+
   if (directiveShorthandsTargets.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}Directive shorthands not used${BG_RESET} in ${directiveShorthandsFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Use ":" for v-bind:, "@" for v-on: and "#" for v-slot.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands`
-    )
-    directiveShorthandsTargets.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+    directiveShorthandsTargets.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-strong ~ directive shorthands not used${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Use ":" for v-bind:, "@" for v-on: and "#" for v-slot.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return directiveShorthandsTargets.length
+  return offenses
+}
+
+const resetDirectiveShorthands = () => {
+  directiveShorthandsFiles.length = 0
+  directiveShorthandsTargets.length = 0
 }
 
-export { checkDirectiveShorthands, reportDirectiveShorthands }
+export { checkDirectiveShorthands, reportDirectiveShorthands, resetDirectiveShorthands }
diff --git a/src/rules/vue-strong/fullWordComponentName.ts b/src/rules/vue-strong/fullWordComponentName.ts
index d395615..d851903 100644
--- a/src/rules/vue-strong/fullWordComponentName.ts
+++ b/src/rules/vue-strong/fullWordComponentName.ts
@@ -1,6 +1,6 @@
 import { charIn, charNotIn, createRegExp, exactly, global, oneOrMore } from 'magic-regexp'
-import { BG_ERR, BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
-import { getUniqueFilenameCount } from '../../helpers'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 interface FullWordComponentNames { filename: string, filePath: string }
 
@@ -34,19 +34,21 @@ const checkFullWordComponentName = (filePath: string) => {
 }
 
 const reportFullWordComponentName = () => {
-  if (fullWordComponentNames.length > 0) {
-    // count only non duplicated objects (by its `filename` property)
-    const fileCount = getUniqueFilenameCount<FullWordComponentNames>(fullWordComponentNames, 'filename')
+  const offenses: Offense[] = []
 
-    console.log(`\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}full-word component names${BG_RESET} detected in ${fileCount} files.`)
-    console.log(
-      `👉 ${TEXT_WARN}Component names should prefer full words over abbreviations.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#full-word-component-names`,
-    )
+  if (fullWordComponentNames.length > 0) {
     fullWordComponentNames.forEach((file) => {
-      console.log(`- ${file.filePath} 🚨 ${BG_WARN}(${file.filename})${BG_RESET}`)
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}vue-strong ~ full-word component names${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Component names should prefer full words over abbreviations.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#full-word-component-names`,
+        message: `${BG_WARN}(${file.filename})${BG_RESET} 🚨`,
+      })
     })
   }
-  return fullWordComponentNames.length
+  return offenses
 }
 
-export { checkFullWordComponentName, reportFullWordComponentName }
+const resetFullWordComponentName = () => (fullWordComponentNames.length = 0)
+
+export { checkFullWordComponentName, reportFullWordComponentName, resetFullWordComponentName }
diff --git a/src/rules/vue-strong/propNameCasing.ts b/src/rules/vue-strong/propNameCasing.ts
index 958ab6c..0bf3efc 100644
--- a/src/rules/vue-strong/propNameCasing.ts
+++ b/src/rules/vue-strong/propNameCasing.ts
@@ -1,20 +1,23 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO } from '../asceeCodes'
-import { global, charNotIn, createRegExp, letter, oneOrMore } from 'magic-regexp'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { createRegExp, letter, oneOrMore } from 'magic-regexp'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import type { Offense } from '../../types'
 
 const propNameCasingFiles: { filePath: string }[] = []
 
 const camelCasePattern = createRegExp(
   oneOrMore(letter.lowercase).at.lineStart(),
-  oneOrMore(letter.uppercase, letter.lowercase.times.any().grouped()).at.lineEnd()
+  oneOrMore(letter.uppercase, letter.lowercase.times.any().grouped()).at.lineEnd(),
 )
 
 const checkPropNameCasing = (script: SFCScriptBlock | null, filePath: string) => {
   if (!script) {
     return
   }
+  // eslint-disable-next-line regexp/strict
   const regex = /defineProps\({([^}]+)/g
   let match
+  // eslint-disable-next-line no-cond-assign
   while ((match = regex.exec(script.content)) !== null) {
     const propNames = match[1]
       .replace(/\s+/g, '')
@@ -31,18 +34,21 @@ const checkPropNameCasing = (script: SFCScriptBlock | null, filePath: string) =>
 }
 
 const reportPropNameCasing = () => {
+  const offenses: Offense[] = []
+
   if (propNameCasingFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}prop names are not camelCased${BG_RESET} in ${propNameCasingFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Rename the props to camelCase.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#prop-name-casing`
-    )
-    propNameCasingFiles.forEach(file => {
-      console.log(`- ${file.filePath} 🚨`)
+    propNameCasingFiles.forEach((file) => {
+      offenses.push({
+        file: file.filePath,
+        rule: `${TEXT_INFO}vue-strong ~ prop names are not camelCased${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Rename the props to camelCase.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#prop-name-casing`,
+        message: `🚨`,
+      })
     })
   }
-  return propNameCasingFiles.length
+  return offenses
 }
 
-export { checkPropNameCasing, reportPropNameCasing }
+const resetPropNameCasing = () => (propNameCasingFiles.length = 0)
+
+export { checkPropNameCasing, reportPropNameCasing, resetPropNameCasing }
diff --git a/src/rules/vue-strong/quotedAttributeValues.ts b/src/rules/vue-strong/quotedAttributeValues.ts
index 1db9926..117de91 100644
--- a/src/rules/vue-strong/quotedAttributeValues.ts
+++ b/src/rules/vue-strong/quotedAttributeValues.ts
@@ -1,9 +1,10 @@
-import { SFCDescriptor } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, BG_WARN, TEXT_INFO } from '../asceeCodes'
-import { createRegExp, charIn, charNotIn, oneOrMore, maybe, wordChar } from 'magic-regexp'
+import type { SFCDescriptor } from '@vue/compiler-sfc'
+import { charIn, charNotIn, createRegExp, maybe, oneOrMore, wordChar } from 'magic-regexp'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 import getLineNumber from '../getLineNumber'
+import type { Offense } from '../../types'
 
-const unquotedAttributeValuesFiles: { message: string }[] = []
+const unquotedAttributeValuesFiles: { filename: string, message: string }[] = []
 
 const checkQuotedAttributeValues = (descriptor: SFCDescriptor | null, filePath: string) => {
   if (!descriptor) {
@@ -19,40 +20,45 @@ const checkQuotedAttributeValues = (descriptor: SFCDescriptor | null, filePath:
     maybe(oneOrMore(charIn(' \t\n\r'))),
     maybe('/'),
     '>',
-    ['g']
+    ['g'],
   )
   const matches = template.content.match(regexTempltaTag)
 
-  if (matches === null) return
+  if (matches === null)
+    return
 
   const regexUnquotedAttributeValue = createRegExp(':', oneOrMore(wordChar), maybe(' '), '=', maybe(' '), charNotIn('\'"'), [
     'g',
   ])
 
-  matches.forEach(templateTag => {
-    if (!templateTag.includes(':')) return // check if it contains a colon
+  matches.forEach((templateTag) => {
+    if (!templateTag.includes(':'))
+      return // check if it contains a colon
 
     const match = templateTag.match(regexUnquotedAttributeValue)
     if (match?.length) {
       const lineNumber = getLineNumber(descriptor.source, templateTag)
-      unquotedAttributeValuesFiles.push({ message: `${filePath}#${lineNumber} ${BG_WARN}${match}${BG_RESET}` })
+      unquotedAttributeValuesFiles.push({ filename: filePath, message: `line #${lineNumber} ${BG_WARN}${match}${BG_RESET}` })
     }
   })
 }
 
 const reportQuotedAttributeValues = () => {
+  const offenses: Offense[] = []
+
   if (unquotedAttributeValuesFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}Attribute value is not quoted${BG_RESET} in ${unquotedAttributeValuesFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Use quotes for attribute values.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#quoted-attribute-values`
-    )
-    unquotedAttributeValuesFiles.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+    unquotedAttributeValuesFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-strong ~ attribute value is not quoted${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Use quotes for attribute values.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#quoted-attribute-values`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return unquotedAttributeValuesFiles.length
+  return offenses
 }
 
-export { checkQuotedAttributeValues, reportQuotedAttributeValues }
+const resetQuotedAttributeValues = () => (unquotedAttributeValuesFiles.length = 0)
+
+export { checkQuotedAttributeValues, reportQuotedAttributeValues, resetQuotedAttributeValues }
diff --git a/src/rules/vue-strong/selfClosingComponents.ts b/src/rules/vue-strong/selfClosingComponents.ts
index 96fa0d8..8a0bde3 100644
--- a/src/rules/vue-strong/selfClosingComponents.ts
+++ b/src/rules/vue-strong/selfClosingComponents.ts
@@ -1,9 +1,10 @@
-import { SFCDescriptor } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, BG_WARN, TEXT_INFO } from '../asceeCodes'
+import type { SFCDescriptor } from '@vue/compiler-sfc'
 import { charNotIn, createRegExp, letter, linefeed, maybe, oneOrMore, tab, wordChar } from 'magic-regexp'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 import getLineNumber from '../getLineNumber'
+import type { Offense } from '../../types'
 
-const selfClosingComponentsFiles: { message: string }[] = []
+const selfClosingComponentsFiles: { filename: string, message: string }[] = []
 
 const checkSelfClosingComponents = (descriptor: SFCDescriptor | null, filePath: string) => {
   if (!descriptor) {
@@ -19,32 +20,34 @@ const checkSelfClosingComponents = (descriptor: SFCDescriptor | null, filePath:
     '></',
     oneOrMore(wordChar),
     '>',
-    ['g']
+    ['g'],
   )
   const matches = template.content.match(regexSelfClosingComponent)
 
-  if (matches === null) return
+  if (matches === null)
+    return
 
-  matches.forEach(componentTag => {
+  matches.forEach((componentTag) => {
     const lineNumber = getLineNumber(descriptor.source, componentTag)
     const lastPart = componentTag.split('\n').at(-1)?.trim() || ''
-    selfClosingComponentsFiles.push({ message: `${filePath}#${lineNumber} ${BG_WARN}${lastPart}${BG_RESET}` })
+    selfClosingComponentsFiles.push({ filename: filePath, message: `line #${lineNumber} ${BG_WARN}${lastPart}${BG_RESET}` })
   })
 }
 
 const reportSelfClosingComponents = () => {
+  const offenses: Offense[] = []
+
   if (selfClosingComponentsFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-strong${TEXT_RESET} - ${BG_ERR}Component is not self closing${BG_RESET} in ${selfClosingComponentsFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Components with no content should be self-closing.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#self-closing-components`
-    )
-    selfClosingComponentsFiles.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+    selfClosingComponentsFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-strong ~ component is not self closing${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Components with no content should be self-closing.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#self-closing-components`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return selfClosingComponentsFiles.length
+  return offenses
 }
 
 const resetSelfClosingComponents = () => (selfClosingComponentsFiles.length = 0)
diff --git a/src/rules/vue-strong/simpleComputed.ts b/src/rules/vue-strong/simpleComputed.ts
index 89d1ba3..0e29f8f 100644
--- a/src/rules/vue-strong/simpleComputed.ts
+++ b/src/rules/vue-strong/simpleComputed.ts
@@ -1,8 +1,9 @@
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO, BG_WARN } from '../asceeCodes'
+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 { Offense } from '../../types'
 
-const complicatedComputedTargets: { message: string }[] = []
+const complicatedComputedTargets: { filename: string, message: string }[] = []
 const complicatedComputedFiles: { filePath: string }[] = []
 
 const MAX_COMPUTED_LENGTH = 5 // completely rrd made-up number
@@ -12,18 +13,19 @@ const checkSimpleComputed = (script: SFCScriptBlock | null, filePath: string) =>
     return
   }
 
+  // eslint-disable-next-line regexp/prefer-w, regexp/strict, regexp/no-useless-flag
   const regex = /const\s+([a-zA-Z0-9_$]+)\s*=\s*computed\(\s*\(\)\s*=>\s*{([^{}]*(?:{[^{}]*}[^{}]*)*)}\s*\)/gs
 
   const matches = script.content.match(regex)
   if (matches?.length) {
-    matches.forEach(match => {
+    matches.forEach((match) => {
       if (match.split('\n').length > MAX_COMPUTED_LENGTH) {
         const firstLine = match.split('\n')[0]
         const lineNumber = getLineNumber(script.content, firstLine)
-        complicatedComputedTargets.push({ message: `${filePath}:${lineNumber} ${BG_WARN}computed${BG_RESET}` })
+        complicatedComputedTargets.push({ filename: filePath, message: `line #${lineNumber} ${BG_WARN}computed${BG_RESET}` })
         complicatedComputedFiles.push({ filePath })
         if (!complicatedComputedFiles.some(file => file.filePath === filePath)) {
-          complicatedComputedFiles.push({ filePath: filePath })
+          complicatedComputedFiles.push({ filePath })
         }
       }
     })
@@ -31,18 +33,24 @@ const checkSimpleComputed = (script: SFCScriptBlock | null, filePath: string) =>
 }
 
 const reportSimpleComputed = () => {
+  const offenses: Offense[] = []
+
   if (complicatedComputedFiles.length > 0) {
-    console.log(
-      `\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}complicated computed property ${BG_RESET} in ${complicatedComputedFiles.length} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Refactor the computed properties to smaller ones.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#simple-computed-properties`
-    )
-    complicatedComputedTargets.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+    complicatedComputedTargets.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-strong ~ complicated computed property${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Refactor the computed properties to smaller ones.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#simple-computed-properties`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return complicatedComputedTargets.length
+  return offenses
+}
+
+const resetSimpleComputed = () => {
+  complicatedComputedTargets.length = 0
+  complicatedComputedFiles.length = 0
 }
 
-export { checkSimpleComputed, reportSimpleComputed }
+export { checkSimpleComputed, reportSimpleComputed, resetSimpleComputed }
diff --git a/src/rules/vue-strong/templateSimpleExpression.ts b/src/rules/vue-strong/templateSimpleExpression.ts
index f5f8bff..312d2f2 100644
--- a/src/rules/vue-strong/templateSimpleExpression.ts
+++ b/src/rules/vue-strong/templateSimpleExpression.ts
@@ -1,9 +1,9 @@
-import { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { BG_RESET, TEXT_WARN, TEXT_RESET, BG_ERR, TEXT_INFO, BG_WARN } from '../asceeCodes'
+import type { SFCTemplateBlock } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 import getLineNumber from '../getLineNumber'
-import { getUniqueFilenameCount } from '../../helpers'
+import type { Offense } from '../../types'
 
-type TemplateSimpleExpressionFile = { filename: string; message: string }
+interface TemplateSimpleExpressionFile { filename: string, message: string }
 
 const templateSimpleExpressionFiles: TemplateSimpleExpressionFile[] = []
 
@@ -13,37 +13,38 @@ const checkTemplateSimpleExpression = (template: SFCTemplateBlock | null, filePa
   if (!template) {
     return
   }
+  // eslint-disable-next-line regexp/strict, regexp/no-super-linear-backtracking
   const regex = /{{\s*([\s\S]*?)\s*}}/g
   const matches = [...template.content.matchAll(regex)].map(match => match[1].trim())
 
-  matches.forEach(expression => {
+  matches.forEach((expression) => {
     if (expression.length > MAX_EXPRESSION_LENGTH) {
       const lineNumber = getLineNumber(template.content, expression)
       const firstPart = expression.split('\n').at(0)?.trim() || ''
       templateSimpleExpressionFiles.push({
         filename: filePath,
-        message: `${filePath}#${lineNumber} ${BG_WARN}${firstPart}${BG_RESET}`,
+        message: `line #${lineNumber} ${BG_WARN}${firstPart}${BG_RESET}`,
       })
     }
   })
 }
 
 const reportTemplateSimpleExpression = () => {
+  const offenses: Offense[] = []
+
   if (templateSimpleExpressionFiles.length > 0) {
-    // Count only non duplicated objects (by its `filename` property)
-    const fileCount = getUniqueFilenameCount<TemplateSimpleExpressionFile>(templateSimpleExpressionFiles, 'filename')
-
-    console.log(
-      `\n${TEXT_INFO}vue-strong${TEXT_RESET} ${BG_ERR}Lengthy template expression${BG_RESET} found in ${fileCount} files.`
-    )
-    console.log(
-      `👉 ${TEXT_WARN}Refactor the expression into a computed property.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#simple-expressions-in-templates`
-    )
-    templateSimpleExpressionFiles.forEach(file => {
-      console.log(`- ${file.message} 🚨`)
+    templateSimpleExpressionFiles.forEach((file) => {
+      offenses.push({
+        file: file.filename,
+        rule: `${TEXT_INFO}vue-strong ~ lengthy template expression${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Refactor the expression into a computed property.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#simple-expressions-in-templates`,
+        message: `${file.message} 🚨`,
+      })
     })
   }
-  return templateSimpleExpressionFiles.length
+  return offenses
 }
 
-export { checkTemplateSimpleExpression, reportTemplateSimpleExpression }
+const resetTemplateSimpleExpression = () => (templateSimpleExpressionFiles.length = 0)
+
+export { checkTemplateSimpleExpression, reportTemplateSimpleExpression, resetTemplateSimpleExpression }
diff --git a/src/rulesReport.ts b/src/rulesReport.ts
index 9c998b3..c34e8a5 100644
--- a/src/rulesReport.ts
+++ b/src/rulesReport.ts
@@ -24,47 +24,81 @@ import { reportSimpleComputed } from './rules/vue-strong/simpleComputed'
 import { reportComponentFiles } from './rules/vue-strong/componentFiles'
 import { reportImplicitParentChildCommunication } from './rules/vue-caution/implicitParentChildCommunication'
 import { reportDeepIndentation } from './rules/rrd/deepIndentation'
-import { reportElementSelectorsWithScoped } from './rules/vue-caution/elementSelectorsWithScoped'
+import type { GroupBy, Offense, OffensesGrouped, ReportFunction } from './types'
 
-export const reportRules = () => {
+export const reportRules = (groupBy: GroupBy) => {
   let errors = 0
+  const offensesGrouped: OffensesGrouped = {}
+
+  // Helper function to add offenses
+  const addOffense = ({ file, rule, title, description, message }: Offense) => {
+    const groupKey = groupBy === 'rule' ? rule : file
+
+    if (!offensesGrouped[groupKey]) {
+      offensesGrouped[groupKey] = []
+    }
+    offensesGrouped[groupKey].push({ file, rule, title, description, message })
+  }
+
+  // Helper function to process offenses from a report function
+  const processOffenses = (reportFunction: ReportFunction) => {
+    const offenses = reportFunction()
+    offenses.forEach((offense) => {
+      addOffense(offense)
+      errors++
+    })
+  }
 
   // vue-essential rules
-  errors += reportSingleNameComponent()
-  errors += reportSimpleProp()
-  errors += reportVforNoKey()
-  errors += reportVifWithVfor()
-  errors += reportGlobalStyle()
+  processOffenses(reportSingleNameComponent)
+  processOffenses(reportSimpleProp)
+  processOffenses(reportVforNoKey)
+  processOffenses(reportVifWithVfor)
+  processOffenses(reportGlobalStyle)
 
   // vue-strong rules
-  errors += reportComponentFilenameCasing()
-  errors += reportSelfClosingComponents()
-  errors += reportPropNameCasing()
-  errors += reportTemplateSimpleExpression()
-  errors += reportQuotedAttributeValues()
-  errors += reportDirectiveShorthands()
-  errors += reportSimpleComputed()
-  errors += reportComponentFiles()
-  errors += reportFullWordComponentName()
+  processOffenses(reportComponentFilenameCasing)
+  processOffenses(reportSelfClosingComponents)
+  processOffenses(reportPropNameCasing)
+  processOffenses(reportTemplateSimpleExpression)
+  processOffenses(reportQuotedAttributeValues)
+  processOffenses(reportDirectiveShorthands)
+  processOffenses(reportSimpleComputed)
+  processOffenses(reportComponentFiles)
+  processOffenses(reportFullWordComponentName)
 
   // vue-recommended rules
-  errors += reportTopLevelElementOrder()
-  errors += reportElementAttributeOrder()
+  processOffenses(reportTopLevelElementOrder)
+  processOffenses(reportElementAttributeOrder)
 
   // vue-caution rules
-  errors += reportImplicitParentChildCommunication()
-  errors += reportElementSelectorsWithScoped()
+  processOffenses(reportImplicitParentChildCommunication)
 
   // rrd rules
-  errors += reportCyclomaticComplexity()
-  errors += reportDeepIndentation()
-  errors += reportElseCondition()
-  errors += reportFunctionSize()
-  errors += reportParameterCount()
-  errors += reportPlainScript()
-  errors += reportScriptLength()
-  errors += reportShortVariableName()
-  errors += reportTooManyProps()
+  processOffenses(reportCyclomaticComplexity)
+  processOffenses(reportDeepIndentation)
+  processOffenses(reportElseCondition)
+  processOffenses(reportFunctionSize)
+  processOffenses(reportParameterCount)
+  processOffenses(reportPlainScript)
+  processOffenses(reportScriptLength)
+  processOffenses(reportShortVariableName)
+  processOffenses(reportTooManyProps)
+
+  // Output the report grouped by file
+  Object.keys(offensesGrouped).forEach((key) => {
+    console.log(`\n - ${key}`)
+    offensesGrouped[key].forEach((offense) => {
+      if (groupBy === 'file') {
+        console.log(`   Rule: ${offense.rule}`)
+      }
+      else {
+        console.log(`   File: ${offense.file}`)
+      }
+      console.log(`   Description: ${offense.description}`)
+      console.log(`   Message: ${offense.message || '🚨'}\n`)
+    })
+  })
 
   return errors
 }
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..fcd5d14
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,15 @@
+export type GroupBy = 'rule' | 'file'
+
+export interface Offense {
+  file: string
+  rule: string
+  title?: string
+  description: string
+  message: string
+}
+
+export type ReportFunction = () => Offense[]
+
+export interface OffensesGrouped {
+  [key: string]: Offense[]
+}


Test patch:
diff --git a/src/rules/rrd/cyclomaticComplexity.test.ts b/src/rules/rrd/cyclomaticComplexity.test.ts
index 3f64bb8..a87aafa 100644
--- a/src/rules/rrd/cyclomaticComplexity.test.ts
+++ b/src/rules/rrd/cyclomaticComplexity.test.ts
@@ -1,11 +1,13 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkCyclomaticComplexity, reportCyclomaticComplexity } from './cyclomaticComplexity'
-import { BG_ERR, BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkCyclomaticComplexity, reportCyclomaticComplexity, resetCyclomaticComplexity } from './cyclomaticComplexity'
 
 describe('checkCyclomaticComplexity', () => {
+  beforeEach(() => {
+    resetCyclomaticComplexity()
+  })
+
   it('should not report simple scripts', () => {
     const content = `if (condition) {
         console.log("True");
@@ -13,8 +15,8 @@ describe('checkCyclomaticComplexity', () => {
     const script = { content } as SFCScriptBlock
     const fileName = 'simple.vue'
     checkCyclomaticComplexity(script, fileName)
-    expect(reportCyclomaticComplexity()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportCyclomaticComplexity().length).toBe(0)
+    expect(reportCyclomaticComplexity()).toStrictEqual([])
   })
 
   it('should report scripts with moderate complexity', () => {
@@ -24,9 +26,13 @@ describe('checkCyclomaticComplexity', () => {
     const script = { content } as SFCScriptBlock
     const fileName = 'moderate.vue'
     checkCyclomaticComplexity(script, fileName)
-    expect(reportCyclomaticComplexity()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} ${BG_WARN}(6)${BG_RESET}`)
+    expect(reportCyclomaticComplexity().length).toBe(1)
+    expect(reportCyclomaticComplexity()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ cyclomatic complexity${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to reduce complexity.${TEXT_RESET}`,
+      message: `${BG_WARN}(6)${BG_RESET} 🚨`,
+    }])
   })
 
   it('should report scripts with high complexity', () => {
@@ -36,8 +42,12 @@ describe('checkCyclomaticComplexity', () => {
     const script = { content } as SFCScriptBlock
     const fileName = 'high.vue'
     checkCyclomaticComplexity(script, fileName)
-    expect(reportCyclomaticComplexity()).toBe(2)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} ${BG_ERR}(11)${BG_RESET}`)
+    expect(reportCyclomaticComplexity().length).toBe(1)
+    expect(reportCyclomaticComplexity()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ cyclomatic complexity${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to reduce complexity.${TEXT_RESET}`,
+      message: `${BG_ERR}(11)${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/rrd/deepIndentation.test.ts b/src/rules/rrd/deepIndentation.test.ts
index 4aa999a..0cbeffc 100644
--- a/src/rules/rrd/deepIndentation.test.ts
+++ b/src/rules/rrd/deepIndentation.test.ts
@@ -1,38 +1,50 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkDeepIndentation, reportDeepIndentation } from './deepIndentation'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+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 { checkDeepIndentation, reportDeepIndentation, resetDeepIndentation } from './deepIndentation'
 
 describe('checkDeepIndentation', () => {
+  beforeEach(() => {
+    resetDeepIndentation()
+  })
+
   it('should not report files without deep indentations', () => {
     let script = { content: '<script setup>\n\t\tconsole.log("Hello")\n</script>' } as SFCScriptBlock
     let fileName = 'no-deep-indentation-tab.vue'
     checkDeepIndentation(script, fileName)
-    expect(reportDeepIndentation()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportDeepIndentation().length).toBe(0)
+    expect(reportDeepIndentation()).toStrictEqual([])
 
     script = { content: '<script setup>\n        console.log("Hello")\n</script>' } as SFCScriptBlock
     fileName = 'no-deep-indentation-space.vue'
     checkDeepIndentation(script, fileName)
-    expect(reportDeepIndentation()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportDeepIndentation().length).toBe(0)
+    expect(reportDeepIndentation()).toStrictEqual([])
   })
 
   it('should report files with deep indentation', () => {
-    let script = { content: '\t\t\t\t\tif (true) { ... } else { ... }' } as SFCScriptBlock
-    let fileName = 'with-deep-indentation-tab.vue'
+    const script = { content: '\t\t\t\t\tif (true) { ... } else { ... }' } as SFCScriptBlock
+    const fileName = 'with-deep-indentation-tab.vue'
     checkDeepIndentation(script, fileName)
-    expect(reportDeepIndentation()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName}#1 ${BG_WARN}indentation: 5${BG_RESET} 🚨`)
+    expect(reportDeepIndentation().length).toBe(1)
+    expect(reportDeepIndentation()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ deep indentation${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to refactor your component to child components, to avoid deep indentations..${TEXT_RESET}`,
+      message: `line #1 ${BG_WARN}indentation: 5${BG_RESET} 🚨`,
+    }])
+  })
 
-    script = { content: '               if (true) { ... } else { ... }' } as SFCScriptBlock
-    fileName = 'with-deep-indentation-space.vue'
+  it('should report files with deep indentation 2', () => {
+    const script = { content: '               if (true) { ... } else { ... }' } as SFCScriptBlock
+    const fileName = 'with-deep-indentation-space.vue'
     checkDeepIndentation(script, fileName)
-    expect(reportDeepIndentation()).toBe(2)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName}#1 ${BG_WARN}indentation: 15${BG_RESET} 🚨`)
+    expect(reportDeepIndentation().length).toBe(1)
+    expect(reportDeepIndentation()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ deep indentation${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to refactor your component to child components, to avoid deep indentations..${TEXT_RESET}`,
+      message: `line #1 ${BG_WARN}indentation: 15${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/rrd/elseCondition.test.ts b/src/rules/rrd/elseCondition.test.ts
index 32ed784..52a2626 100644
--- a/src/rules/rrd/elseCondition.test.ts
+++ b/src/rules/rrd/elseCondition.test.ts
@@ -1,25 +1,31 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkElseCondition, reportElseCondition } from './elseCondition'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkElseCondition, reportElseCondition, resetElseCondition } from './elseCondition'
 
 describe('checkElseCondition', () => {
+  beforeEach(() => {
+    resetElseCondition()
+  })
+
   it('should not report files without else conditions', () => {
     const script = { content: '<script setup>\nconsole.log("Hello")\n</script>' } as SFCScriptBlock
     const fileName = 'no-else.vue'
     checkElseCondition(script, fileName)
-    expect(reportElseCondition()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportElseCondition().length).toBe(0)
+    expect(reportElseCondition()).toStrictEqual([])
   })
 
   it('should report files with one else condition', () => {
     const script = { content: 'if (true) { ... } else { ... }' } as SFCScriptBlock
     const fileName = 'with-else.vue'
     checkElseCondition(script, fileName)
-    expect(reportElseCondition()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- with-else.vue ${BG_WARN}(1)${BG_RESET}`)
+    expect(reportElseCondition().length).toBe(1)
+    expect(reportElseCondition()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ else conditions${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to rewrite the conditions in a way that the else clause is not necessary.${TEXT_RESET}`,
+      message: `else clauses found ${BG_ERR}(1)${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/rrd/functionSize.test.ts b/src/rules/rrd/functionSize.test.ts
index 88fa0ae..6368846 100644
--- a/src/rules/rrd/functionSize.test.ts
+++ b/src/rules/rrd/functionSize.test.ts
@@ -1,13 +1,16 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkFunctionSize, reportFunctionSize } from './functionSize'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
+import { beforeEach, describe, expect, it } from 'vitest'
 
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { MAX_FUNCTION_LENGTH, checkFunctionSize, reportFunctionSize, resetFunctionSize } from './functionSize'
 
 describe('checkFunctionSize', () => {
+  beforeEach(() => {
+    resetFunctionSize()
+  })
+
   it('should not report files where functions do not exceed the recommended limit', () => {
-    const script = { 
+    const script = {
       content: `
         <script setup>
           function greeting(msg) {
@@ -20,16 +23,16 @@ describe('checkFunctionSize', () => {
             }
           }
         </script>
-      `
-    } as SFCScriptBlock;
-    const fileName = 'function-size.vue';
-    checkFunctionSize(script, fileName);
-    expect(reportFunctionSize()).toBe(0);
-    expect(mockConsoleLog).not.toHaveBeenCalled();
+      `,
+    } as SFCScriptBlock
+    const fileName = 'function-size.vue'
+    checkFunctionSize(script, fileName)
+    expect(reportFunctionSize().length).toBe(0)
+    expect(reportFunctionSize()).toStrictEqual([])
   })
 
   it('should report files with one function exceeding the limit', () => {
-    const script = { 
+    const script = {
       content: `
         <script setup>
           function dummyRegularFunction() {
@@ -62,18 +65,22 @@ describe('checkFunctionSize', () => {
             console.log(getGreeting());
           }
         </script>
-      `
-    } as SFCScriptBlock;
-    const fileName = 'function-size.vue';
-    const funcName = 'dummyRegularFunction';
-    checkFunctionSize(script, fileName);
-    expect(reportFunctionSize()).toBe(1);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} 🚨 ${BG_WARN}(${funcName})${BG_RESET}`)
+      `,
+    } as SFCScriptBlock
+    const fileName = 'function-size.vue'
+    const funcName = 'dummyRegularFunction'
+    checkFunctionSize(script, fileName)
+    expect(reportFunctionSize().length).toBe(1)
+    expect(reportFunctionSize()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ function size${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Functions must be shorter than ${MAX_FUNCTION_LENGTH} lines${TEXT_RESET}`,
+      message: `function ${BG_ERR}(${funcName})${BG_RESET} 🚨`,
+    }])
   })
 
   it('should report files with two functions exceeding the limit', () => {
-    const script = { 
+    const script = {
       content: `
         <script setup>
           function dummyRegularFunction(name = 'Hi') {
@@ -134,13 +141,21 @@ describe('checkFunctionSize', () => {
             return 'Function execution complete!';
           }
         </script>
-      `
-    } as SFCScriptBlock;
-    const fileName = 'function-size.vue';
-    const funcName = 'dummyArrowFunction';
-    checkFunctionSize(script, fileName);
-    expect(reportFunctionSize()).toBe(3);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} 🚨 ${BG_WARN}(${funcName})${BG_RESET}`)
+      `,
+    } as SFCScriptBlock
+    const fileName = 'function-size.vue'
+    checkFunctionSize(script, fileName)
+    expect(reportFunctionSize().length).toBe(2)
+    expect(reportFunctionSize()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ function size${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Functions must be shorter than ${MAX_FUNCTION_LENGTH} lines${TEXT_RESET}`,
+      message: `function ${BG_ERR}(dummyRegularFunction)${BG_RESET} 🚨`,
+    }, {
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ function size${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Functions must be shorter than ${MAX_FUNCTION_LENGTH} lines${TEXT_RESET}`,
+      message: `function ${BG_ERR}(dummyArrowFunction)${BG_RESET} 🚨`,
+    }])
   })
-})
\ No newline at end of file
+})
diff --git a/src/rules/rrd/parameterCount.test.ts b/src/rules/rrd/parameterCount.test.ts
index 328036e..3720a1d 100644
--- a/src/rules/rrd/parameterCount.test.ts
+++ b/src/rules/rrd/parameterCount.test.ts
@@ -1,11 +1,14 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkParameterCount, reportParameterCount } from './parameterCount'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
+import { beforeEach, describe, expect, it } from 'vitest'
 
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { MAX_PARAMETER_COUNT, checkParameterCount, reportParameterCount, resetParameterCount } from './parameterCount'
 
 describe('checkParameterCount', () => {
+  beforeEach(() => {
+    resetParameterCount()
+  })
+
   it('should not report files where functions do not exceed the recommended limit', () => {
     const script = {
       content: `
@@ -14,12 +17,12 @@ describe('checkParameterCount', () => {
             return 'One'
           }
         </script>
-      `
-    } as SFCScriptBlock;
-    const filename = 'no-parameters.vue';
-    checkParameterCount(script, filename);
-    expect(reportParameterCount()).toBe(0);
-    expect(mockConsoleLog).not.toHaveBeenCalled();
+      `,
+    } as SFCScriptBlock
+    const filename = 'no-parameters.vue'
+    checkParameterCount(script, filename)
+    expect(reportParameterCount().length).toBe(0)
+    expect(reportParameterCount()).toStrictEqual([])
   })
 
   it('should report files where one function exceeds the recommended limit', () => {
@@ -32,16 +35,19 @@ describe('checkParameterCount', () => {
 
           const dummyFuncTwo = (param1, param2, param3, param4) => {}
         </script>
-      `
-    } as SFCScriptBlock;
-    const filename = 'one-parameter.vue';
-    const funcName = 'dummyFuncTwo';
-    const paramsCount = 4;
-    checkParameterCount(script, filename);
-    expect(reportParameterCount()).toBe(1);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${BG_WARN}${funcName}${BG_RESET} in file ${filename} 🚨 ${BG_WARN}(${paramsCount})${BG_RESET}`)
-    
+      `,
+    } as SFCScriptBlock
+    const filename = 'one-parameter.vue'
+    const funcName = 'dummyFuncTwo'
+    const paramsCount = 4
+    checkParameterCount(script, filename)
+    expect(reportParameterCount().length).toBe(1)
+    expect(reportParameterCount()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}rrd ~ parameter count${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Max number of function parameters should be ${MAX_PARAMETER_COUNT}${TEXT_RESET}`,
+      message: `function ${BG_WARN}${funcName}${BG_RESET} has ${BG_WARN}${paramsCount}${BG_RESET} parameters 🚨`,
+    }])
   })
 
   it('should report files where multiple functions exceed the recommended limit', () => {
@@ -60,14 +66,21 @@ describe('checkParameterCount', () => {
             return 'Three'
           }
         </script>
-      `
-    } as SFCScriptBlock;
-    const filename = 'multiple-parameters.vue';
-    const funcName = 'dummyFuncTwo';
-    const paramsCount = 4;
-    checkParameterCount(script, filename);
-    expect(reportParameterCount()).toBe(3);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${BG_WARN}${funcName}${BG_RESET} in file ${filename} 🚨 ${BG_WARN}(${paramsCount})${BG_RESET}`)
+      `,
+    } as SFCScriptBlock
+    const filename = 'multiple-parameters.vue'
+    checkParameterCount(script, filename)
+    expect(reportParameterCount().length).toBe(2)
+    expect(reportParameterCount()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}rrd ~ parameter count${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Max number of function parameters should be ${MAX_PARAMETER_COUNT}${TEXT_RESET}`,
+      message: `function ${BG_WARN}dummyFuncOne${BG_RESET} has ${BG_WARN}5${BG_RESET} parameters 🚨`,
+    }, {
+      file: filename,
+      rule: `${TEXT_INFO}rrd ~ parameter count${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Max number of function parameters should be ${MAX_PARAMETER_COUNT}${TEXT_RESET}`,
+      message: `function ${BG_WARN}dummyFuncTwo${BG_RESET} has ${BG_WARN}4${BG_RESET} parameters 🚨`,
+    }])
   })
-})
\ No newline at end of file
+})
diff --git a/src/rules/rrd/scriptLength.test.ts b/src/rules/rrd/scriptLength.test.ts
index c4baf92..a3061ff 100644
--- a/src/rules/rrd/scriptLength.test.ts
+++ b/src/rules/rrd/scriptLength.test.ts
@@ -1,17 +1,19 @@
-import { describe, it, expect, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkScriptLength, reportScriptLength } from './scriptLength'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+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 { MAX_SCRIPT_LENGTH, checkScriptLength, reportScriptLength, resetScriptLength } from './scriptLength'
 
 describe('checkScriptLength', () => {
+  beforeEach(() => {
+    resetScriptLength()
+  })
+
   it('should not report "long scripts" for a short script', () => {
     const shortScript = { content: '<script setup>\nconsole.log("Hello")\n</script>' } as SFCScriptBlock
     const fileName = 'short.vue'
     checkScriptLength(shortScript, fileName)
-    expect(reportScriptLength()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportScriptLength().length).toBe(0)
+    expect(reportScriptLength()).toStrictEqual([])
   })
 
   it('should report long scripts for a long script', () => {
@@ -23,8 +25,12 @@ describe('checkScriptLength', () => {
     const longScript = { content } as SFCScriptBlock
     const fileName = 'long.vue'
     checkScriptLength(longScript, fileName)
-    expect(reportScriptLength()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- long.vue ${BG_WARN}(104 lines)${BG_RESET}`)
+    expect(reportScriptLength().length).toBe(1)
+    expect(reportScriptLength()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ Long <script> blocks${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to refactor out the logic into composable functions or other files and keep the script block's length under ${MAX_SCRIPT_LENGTH} lines.${TEXT_RESET}`,
+      message: `${BG_WARN}(104 lines)${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/rrd/shortVariableName.test.ts b/src/rules/rrd/shortVariableName.test.ts
index eb24800..bf5950f 100644
--- a/src/rules/rrd/shortVariableName.test.ts
+++ b/src/rules/rrd/shortVariableName.test.ts
@@ -1,11 +1,14 @@
-import { describe, expect, it, vi } from 'vitest';
-import { SFCScriptBlock } from '@vue/compiler-sfc';
-import { checkShortVariableName, reportShortVariableName } from './shortVariableName';
-import { BG_RESET, BG_WARN } from '../asceeCodes';
+import { beforeEach, describe, expect, it } from 'vitest'
 
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { MIN_VARIABLE_NAME, checkShortVariableName, reportShortVariableName, resetShortVariableName } from './shortVariableName'
 
 describe('shortVariableName', () => {
+  beforeEach(() => {
+    resetShortVariableName()
+  })
+
   it('should not report files where variable names are not too short', () => {
     const script = {
       content: `
@@ -13,12 +16,12 @@ describe('shortVariableName', () => {
         import { ref } from 'vue';
         const isLoading = ref(false);
         </script>
-      `
-    } as SFCScriptBlock;
-    const filename = 'not-short-variable-name.vue';
-    checkShortVariableName(script, filename);
-    expect(reportShortVariableName()).toBe(0);
-    expect(mockConsoleLog).not.toHaveBeenCalled();
+      `,
+    } as SFCScriptBlock
+    const filename = 'not-short-variable-name.vue'
+    checkShortVariableName(script, filename)
+    expect(reportShortVariableName().length).toBe(0)
+    expect(reportShortVariableName()).toStrictEqual([])
   })
 
   it('should report files where one variable is too short', () => {
@@ -27,16 +30,20 @@ describe('shortVariableName', () => {
         <script setup>
         let age = 10;
         </script>
-      `
-    } as SFCScriptBlock;
-    const filename = 'short-variable-name.vue';
-    const variable = 'age';
-    checkShortVariableName(script, filename);
-    expect(reportShortVariableName()).toBe(1);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${filename} 🚨 ${BG_WARN}(${variable})${BG_RESET}`);
+      `,
+    } as SFCScriptBlock
+    const filename = 'short-variable-name.vue'
+    const variable = 'age'
+    checkShortVariableName(script, filename)
+    expect(reportShortVariableName().length).toBe(1)
+    expect(reportShortVariableName()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}rrd ~ short variable names${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Variable names must have a minimum length of ${MIN_VARIABLE_NAME}${TEXT_RESET}`,
+      message: `${BG_ERR}(${variable})${BG_RESET} 🚨`,
+    }])
   })
-  
+
   it('should report files where more than one variable are too short', () => {
     const script = {
       content: `
@@ -47,13 +54,26 @@ describe('shortVariableName', () => {
         var isLoading = false;
         const lng = 5;
         </script>
-      `
-    } as SFCScriptBlock;
-    const filename = 'multiple-short-variable-name.vue';
-    const variable = 'lng';
-    checkShortVariableName(script, filename);
-    expect(reportShortVariableName()).toBe(4);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${filename} 🚨 ${BG_WARN}(${variable})${BG_RESET}`);
+      `,
+    } as SFCScriptBlock
+    const filename = 'multiple-short-variable-name.vue'
+    checkShortVariableName(script, filename)
+    expect(reportShortVariableName().length).toBe(3)
+    expect(reportShortVariableName()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}rrd ~ short variable names${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Variable names must have a minimum length of ${MIN_VARIABLE_NAME}${TEXT_RESET}`,
+      message: `${BG_ERR}(age)${BG_RESET} 🚨`,
+    }, {
+      file: filename,
+      rule: `${TEXT_INFO}rrd ~ short variable names${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Variable names must have a minimum length of ${MIN_VARIABLE_NAME}${TEXT_RESET}`,
+      message: `${BG_ERR}(gps)${BG_RESET} 🚨`,
+    }, {
+      file: filename,
+      rule: `${TEXT_INFO}rrd ~ short variable names${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Variable names must have a minimum length of ${MIN_VARIABLE_NAME}${TEXT_RESET}`,
+      message: `${BG_ERR}(lng)${BG_RESET} 🚨`,
+    }])
   })
-})
\ No newline at end of file
+})
diff --git a/src/rules/rrd/tooManyProps.test.ts b/src/rules/rrd/tooManyProps.test.ts
index fde7cf9..0ef4b0e 100644
--- a/src/rules/rrd/tooManyProps.test.ts
+++ b/src/rules/rrd/tooManyProps.test.ts
@@ -1,33 +1,35 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkTooManyProps, reportTooManyProps } from './tooManyProps'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { BG_ERR, BG_RESET, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkTooManyProps, reportTooManyProps, resetTooManyProps } from './tooManyProps'
 
 describe('checkTooManyProps', () => {
+  beforeEach(() => {
+    resetTooManyProps()
+  })
+
   it('should not report files with less then or exactly 5 props without annotation', () => {
     const script = { content: `<script setup>defineProps({title: String, likes: Number})\n</script>` } as SFCScriptBlock
     const fileName = '2-props-no-annotate.vue'
     checkTooManyProps(script, fileName)
-    expect(reportTooManyProps()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportTooManyProps().length).toBe(0)
+    expect(reportTooManyProps()).toStrictEqual([])
 
     const script2 = {
       content: `<script setup>\nconst props = defineProps({\n\ttitle: String,\n\tlikes: Number})\n</script>`,
     } as SFCScriptBlock
     const fileName2 = '2-props-newline-no-annotate.vue'
     checkTooManyProps(script2, fileName2)
-    expect(reportTooManyProps()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportTooManyProps().length).toBe(0)
+    expect(reportTooManyProps()).toStrictEqual([])
   })
 
   it('should not report files with less then or exactly 5 props with annotations', () => {
     const script = { content: `<script setup>\ndefineProps<{title: string,likes: number}>()\n</script>` } as SFCScriptBlock
     const fileName = '2-props-annotate.vue'
     checkTooManyProps(script, fileName)
-    expect(reportTooManyProps()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportTooManyProps().length).toBe(0)
+    expect(reportTooManyProps()).toStrictEqual([])
   })
 
   it('should report files with more then 5 props without annotations', () => {
@@ -36,9 +38,13 @@ describe('checkTooManyProps', () => {
     } as SFCScriptBlock
     const fileName = '6-props-no-annotate.vue'
     checkTooManyProps(script, fileName)
-    expect(reportTooManyProps()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} ${BG_WARN}(6)${BG_RESET}`)
+    expect(reportTooManyProps().length).toBe(1)
+    expect(reportTooManyProps()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ too many props${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to refactor your code to use less properties.${TEXT_RESET}`,
+      message: `props found ${BG_ERR}(6)${BG_RESET} 🚨`,
+    }])
   })
 
   it('should report files with more then 5 props with annotations', () => {
@@ -47,8 +53,12 @@ describe('checkTooManyProps', () => {
     } as SFCScriptBlock
     const fileName = '6-props-annotate.vue'
     checkTooManyProps(script, fileName)
-    expect(reportTooManyProps()).toBe(2)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} ${BG_WARN}(6)${BG_RESET}`)
+    expect(reportTooManyProps().length).toBe(1)
+    expect(reportTooManyProps()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}rrd ~ too many props${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Try to refactor your code to use less properties.${TEXT_RESET}`,
+      message: `props found ${BG_ERR}(6)${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-caution/implicitParentChildCommunication.test.ts b/src/rules/vue-caution/implicitParentChildCommunication.test.ts
index 6b07c4d..0970a76 100644
--- a/src/rules/vue-caution/implicitParentChildCommunication.test.ts
+++ b/src/rules/vue-caution/implicitParentChildCommunication.test.ts
@@ -1,13 +1,15 @@
-import { describe, expect, it, vi } from 'vitest';
-import { SFCScriptBlock } from '@vue/compiler-sfc';
-import { BG_ERR, BG_RESET, BG_WARN } from '../asceeCodes'
-import { checkImplicitParentChildCommunication, reportImplicitParentChildCommunication } from './implicitParentChildCommunication';
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
+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 { checkImplicitParentChildCommunication, reportImplicitParentChildCommunication, resetImplicitParentChildCommunicationFiles } from './implicitParentChildCommunication'
 
 describe('checkImplicitParentChildCommunication', () => {
+  beforeEach(() => {
+    resetImplicitParentChildCommunicationFiles()
+  })
+
   it('should not report files where there is no implicit parent-child communication', () => {
-    const script = { 
+    const script = {
       content: `
         <script setup>
         defineProps({
@@ -23,16 +25,16 @@ describe('checkImplicitParentChildCommunication', () => {
         <template>
           <input :value="todo.text" @input="emit('input', $event.target.value)" />
         </template>
-      `
-    } as SFCScriptBlock;
+      `,
+    } as SFCScriptBlock
     const filename = 'no-implicit-pcc.vue'
     checkImplicitParentChildCommunication(script, filename)
-    expect(reportImplicitParentChildCommunication()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportImplicitParentChildCommunication().length).toBe(0)
+    expect(reportImplicitParentChildCommunication()).toStrictEqual([])
   })
 
   it('should report files where there is a prop mutation', () => {
-    const script = { 
+    const script = {
       content: `
         <script setup>
         defineProps({
@@ -46,16 +48,19 @@ describe('checkImplicitParentChildCommunication', () => {
         <template>
           <input v-model="todo.text" />
         </template>
-      `
-    } as SFCScriptBlock;
+      `,
+    } as SFCScriptBlock
     const filename = 'props-mutation.vue'
     const lineNumber = 7
+    const offenses = [{
+      file: filename,
+      rule: `${TEXT_INFO}vue-caution ~ implicit parent-child communication${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Avoid implicit parent-child communication to maintain clear and predictable component behavior.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-use-with-caution.html#implicit-parent-child-communication`,
+      message: `line #${lineNumber} ${BG_WARN}(todo)${BG_RESET} 🚨`,
+    }]
     checkImplicitParentChildCommunication(script, filename)
-    expect(reportImplicitParentChildCommunication()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(
-      `- ${filename}#${lineNumber} ${BG_WARN}(todo)${BG_RESET} 🚨`
-    )
+    expect(reportImplicitParentChildCommunication().length).toBe(1)
+    expect(reportImplicitParentChildCommunication()).toStrictEqual(offenses)
   })
 
   it('should report files where $parent/getCurrentInstance is present', () => {
@@ -89,16 +94,21 @@ describe('checkImplicitParentChildCommunication', () => {
             <button @click="removeTodo">×</button>
           </span>
         </template>
-      `
-    } as SFCScriptBlock;
-    const filename = "parent-instance.vue"
+      `,
+    } as SFCScriptBlock
+    const filename = 'parent-instance.vue'
     const lineNumber = 2
+    const offenses = [{
+      file: filename,
+      rule: `${TEXT_INFO}vue-caution ~ implicit parent-child communication${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Avoid implicit parent-child communication to maintain clear and predictable component behavior.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-use-with-caution.html#implicit-parent-child-communication`,
+      message: `line #${lineNumber} ${BG_WARN}(getCurrentInstance)${BG_RESET} 🚨`,
+    }]
     checkImplicitParentChildCommunication(script, filename)
-    expect(reportImplicitParentChildCommunication()).toBe(2)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(
-      `- ${filename}#${lineNumber} ${BG_WARN}(getCurrentInstance)${BG_RESET} 🚨`
-    )
+    const r = reportImplicitParentChildCommunication()
+    console.info(r)
+    expect(reportImplicitParentChildCommunication().length).toBe(1)
+    expect(reportImplicitParentChildCommunication()).toStrictEqual(offenses)
   })
 
   it('should report files where props mutation & $parent/getCurrentInstance are present', () => {
@@ -132,15 +142,25 @@ describe('checkImplicitParentChildCommunication', () => {
             <input v-model="todo.text" />
           </span>
         </template>
-      `
-    } as SFCScriptBlock;
-    const filename = "complex-sample.vue"
-    const lineNumber = 2
+      `,
+    } as SFCScriptBlock
+    const filename = 'complex-sample.vue'
+    const offenses = [
+      {
+        file: filename,
+        rule: `${TEXT_INFO}vue-caution ~ implicit parent-child communication${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Avoid implicit parent-child communication to maintain clear and predictable component behavior.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-use-with-caution.html#implicit-parent-child-communication`,
+        message: `line #9 ${BG_WARN}(todo)${BG_RESET} 🚨`,
+      },
+      {
+        file: filename,
+        rule: `${TEXT_INFO}vue-caution ~ implicit parent-child communication${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Avoid implicit parent-child communication to maintain clear and predictable component behavior.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-use-with-caution.html#implicit-parent-child-communication`,
+        message: `line #2 ${BG_WARN}(getCurrentInstance)${BG_RESET} 🚨`,
+      },
+    ]
     checkImplicitParentChildCommunication(script, filename)
-    expect(reportImplicitParentChildCommunication()).toBe(4)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(
-      `- ${filename}#${lineNumber} ${BG_WARN}(getCurrentInstance)${BG_RESET} 🚨`
-    )
+    expect(reportImplicitParentChildCommunication().length).toBe(2)
+    expect(reportImplicitParentChildCommunication()).toStrictEqual(offenses)
   })
-})
\ No newline at end of file
+})
diff --git a/src/rules/vue-essential/globalStyle.test.ts b/src/rules/vue-essential/globalStyle.test.ts
index e5cacc3..0488b42 100644
--- a/src/rules/vue-essential/globalStyle.test.ts
+++ b/src/rules/vue-essential/globalStyle.test.ts
@@ -1,25 +1,31 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCStyleBlock } from '@vue/compiler-sfc'
-import { checkGlobalStyle, reportGlobalStyle } from './globalStyle'
-import { BG_ERR, BG_RESET } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCStyleBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkGlobalStyle, reportGlobalStyle, resetGlobalStyle } from './globalStyle'
 
 describe('checkGlobalStyle', () => {
+  beforeEach(() => {
+    resetGlobalStyle()
+  })
+
   it('should not report scoped styles', () => {
     const styles = [{ content: '<style scoped>h3{background:pink}</style>', scoped: true }] as SFCStyleBlock[]
     const filePath = 'scoped-style.vue'
     checkGlobalStyle(styles, filePath)
-    expect(reportGlobalStyle()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportGlobalStyle().length).toBe(0)
+    expect(reportGlobalStyle()).toStrictEqual([])
   })
 
   it('should report global styles', () => {
     const styles = [{ content: '<style>h3{background:pink}</style>' }] as SFCStyleBlock[]
     const filePath = 'global-style.vue'
     checkGlobalStyle(styles, filePath)
-    expect(reportGlobalStyle()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${filePath} 🚨`)
+    expect(reportGlobalStyle().length).toBe(1)
+    expect(reportGlobalStyle()).toStrictEqual([{
+      file: filePath,
+      rule: `${TEXT_INFO}vue-essential ~ global style${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Use <style scoped>.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-component-scoped-styling`,
+      message: `🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-essential/simpleProp.test.ts b/src/rules/vue-essential/simpleProp.test.ts
index f221f97..b368e15 100644
--- a/src/rules/vue-essential/simpleProp.test.ts
+++ b/src/rules/vue-essential/simpleProp.test.ts
@@ -1,10 +1,14 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkSimpleProp, reportSimpleProp } from './simpleProp'
+import { beforeEach, describe, expect, it } from 'vitest'
 
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkSimpleProp, reportSimpleProp, resetSimpleProp } from './simpleProp'
 
 describe('checkSimpleProp', () => {
+  beforeEach(() => {
+    resetSimpleProp()
+  })
+
   it('should not report files with detailed props definition', () => {
     const script = {
       content: `<script setup>
@@ -15,8 +19,8 @@ describe('checkSimpleProp', () => {
     } as SFCScriptBlock
     const fileName = 'proper-prop.vue'
     checkSimpleProp(script, fileName)
-    expect(reportSimpleProp()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportSimpleProp().length).toBe(0)
+    expect(reportSimpleProp()).toStrictEqual([])
   })
 
   it('should report files with simple props defeiniton', () => {
@@ -27,8 +31,12 @@ describe('checkSimpleProp', () => {
     } as SFCScriptBlock
     const fileName = 'simple-prop.vue'
     checkSimpleProp(script, fileName)
-    expect(reportSimpleProp()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} 🚨`)
+    expect(reportSimpleProp().length).toBe(1)
+    expect(reportSimpleProp()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-essential ~ simple prop${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Add at least type definition.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-detailed-prop-definitions`,
+      message: `🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-essential/singeNameComponent.test.ts b/src/rules/vue-essential/singeNameComponent.test.ts
deleted file mode 100644
index 8f422da..0000000
--- a/src/rules/vue-essential/singeNameComponent.test.ts
+++ /dev/null
@@ -1,30 +0,0 @@
-import { describe, it, expect, vi } from 'vitest'
-import { checkSingleNameComponent, reportSingleNameComponent } from './singleNameComponent'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
-
-describe('checkSingleNameComponent', () => {
-  it('ignores pages directory', () => {
-    checkSingleNameComponent('pages/SomeComponent.vue')
-    expect(reportSingleNameComponent()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
-  })
-
-  it('ignores App.vue', () => {
-    checkSingleNameComponent('components/App.vue')
-    expect(reportSingleNameComponent()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
-  })
-
-  it('ignores multi-word component', () => {
-    checkSingleNameComponent('components/AppHeader.vue')
-    expect(reportSingleNameComponent()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
-  })
-
-  it('detects single name component', () => {
-    checkSingleNameComponent('components/Header.vue')
-    expect(reportSingleNameComponent()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-  })
-})
diff --git a/src/rules/vue-essential/singleNameComponent.test.ts b/src/rules/vue-essential/singleNameComponent.test.ts
new file mode 100644
index 0000000..6904a9d
--- /dev/null
+++ b/src/rules/vue-essential/singleNameComponent.test.ts
@@ -0,0 +1,39 @@
+import { beforeEach, describe, expect, it } from 'vitest'
+
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkSingleNameComponent, reportSingleNameComponent, resetSingleNameComponent } from './singleNameComponent'
+
+describe('checkSingleNameComponent', () => {
+  beforeEach(() => {
+    resetSingleNameComponent()
+  })
+
+  it('ignores pages directory', () => {
+    checkSingleNameComponent('pages/SomeComponent.vue')
+    expect(reportSingleNameComponent().length).toBe(0)
+    expect(reportSingleNameComponent()).toStrictEqual([])
+  })
+
+  it('ignores App.vue', () => {
+    checkSingleNameComponent('components/App.vue')
+    expect(reportSingleNameComponent().length).toBe(0)
+    expect(reportSingleNameComponent()).toStrictEqual([])
+  })
+
+  it('ignores multi-word component', () => {
+    checkSingleNameComponent('components/AppHeader.vue')
+    expect(reportSingleNameComponent().length).toBe(0)
+    expect(reportSingleNameComponent()).toStrictEqual([])
+  })
+
+  it('detects single name component', () => {
+    checkSingleNameComponent('components/Header.vue')
+    expect(reportSingleNameComponent().length).toBe(1)
+    expect(reportSingleNameComponent()).toStrictEqual([{
+      file: 'components/Header.vue',
+      rule: `${TEXT_INFO}vue-essential ~ single name component${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Rename the component to use multi-word name.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-multi-word-component-names`,
+      message: `🚨`,
+    }])
+  })
+})
diff --git a/src/rules/vue-essential/vforNoKey.test.ts b/src/rules/vue-essential/vforNoKey.test.ts
index 07322d8..1649697 100644
--- a/src/rules/vue-essential/vforNoKey.test.ts
+++ b/src/rules/vue-essential/vforNoKey.test.ts
@@ -1,11 +1,13 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { BG_ERR, BG_RESET } from '../asceeCodes'
-import { checkVforNoKey, reportVforNoKey } from './vforNoKey'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCTemplateBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkVforNoKey, reportVforNoKey, resetReportVForNoKey } from './vforNoKey'
 
 describe('checkVforNoKey', () => {
+  beforeEach(() => {
+    resetReportVForNoKey()
+  })
+
   it('should not report files where v-for has key property', () => {
     const script = {
       content: `<template>
@@ -21,8 +23,8 @@ describe('checkVforNoKey', () => {
     } as SFCTemplateBlock
     const fileName = 'vfor-with-key.vue'
     checkVforNoKey(script, fileName)
-    expect(reportVforNoKey()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportVforNoKey().length).toBe(0)
+    expect(reportVforNoKey()).toStrictEqual([])
   })
 
   it('should report files where v-for has no key property', () => {
@@ -37,8 +39,12 @@ describe('checkVforNoKey', () => {
     } as SFCTemplateBlock
     const fileName = 'vfor-no-key.vue'
     checkVforNoKey(script, fileName)
-    expect(reportVforNoKey()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} 🚨`)
+    expect(reportVforNoKey().length).toBe(1)
+    expect(reportVforNoKey()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-essential ~ v-for has no key${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Add a \`:key\` property to all v-for.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#use-keyed-v-for`,
+      message: `🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-essential/vifWithVfor.test.ts b/src/rules/vue-essential/vifWithVfor.test.ts
index 0c040a4..1410839 100644
--- a/src/rules/vue-essential/vifWithVfor.test.ts
+++ b/src/rules/vue-essential/vifWithVfor.test.ts
@@ -1,11 +1,13 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { checkVifWithVfor, reportVifWithVfor } from './vifWithVfor'
-import { BG_ERR, BG_RESET } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCTemplateBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkVifWithVfor, reportVifWithVfor, resetVIfWithVFor } from './vifWithVfor'
 
 describe('checkVifWithVfor', () => {
+  beforeEach(() => {
+    resetVIfWithVFor()
+  })
+
   it('should not report files where v-for and v-if is not used together', () => {
     const script = {
       content: `<template>
@@ -20,8 +22,8 @@ describe('checkVifWithVfor', () => {
     } as SFCTemplateBlock
     const fileName = 'separate-vif-vfor.vue'
     checkVifWithVfor(script, fileName)
-    expect(reportVifWithVfor()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportVifWithVfor().length).toBe(0)
+    expect(reportVifWithVfor()).toStrictEqual([])
   })
 
   it('should report files where v-for is used with v-if', () => {
@@ -40,8 +42,12 @@ describe('checkVifWithVfor', () => {
     } as SFCTemplateBlock
     const fileName = 'vif-with-vfor.vue'
     checkVifWithVfor(script, fileName)
-    expect(reportVifWithVfor()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} 🚨`)
+    expect(reportVifWithVfor().length).toBe(1)
+    expect(reportVifWithVfor()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-essential ~ v-if used with v-for${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Move out the v-if to a computed property.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-essential.html#avoid-v-if-with-v-for`,
+      message: `🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-recommended/elementAttributeOrder.test.ts b/src/rules/vue-recommended/elementAttributeOrder.test.ts
index a05d9d0..2afac5f 100644
--- a/src/rules/vue-recommended/elementAttributeOrder.test.ts
+++ b/src/rules/vue-recommended/elementAttributeOrder.test.ts
@@ -1,11 +1,14 @@
+import { beforeEach, describe, expect, it } from 'vitest'
 import type { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { describe, expect, it, vi } from 'vitest'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-import { checkElementAttributeOrder, reportElementAttributeOrder } from './elementAttributeOrder'
 
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkElementAttributeOrder, reportElementAttributeOrder, resetElementAttributeOrder } from './elementAttributeOrder'
 
 describe('checkElementAttributeOrder', () => {
+  beforeEach(() => {
+    resetElementAttributeOrder()
+  })
+
   it('should not report files where elements attribute order is correct', () => {
     const template = {
       content: `
@@ -16,8 +19,8 @@ describe('checkElementAttributeOrder', () => {
     } as SFCTemplateBlock
     const filename = 'element-attribute-order.vue'
     checkElementAttributeOrder(template, filename)
-    expect(reportElementAttributeOrder()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportElementAttributeOrder().length).toBe(0)
+    expect(reportElementAttributeOrder()).toStrictEqual([])
   })
 
   it('should report files where elements attribute order are incorrect', () => {
@@ -30,11 +33,13 @@ describe('checkElementAttributeOrder', () => {
     } as SFCTemplateBlock
     const filename = 'element-attribute-order-incorrect.vue'
     checkElementAttributeOrder(template, filename)
-    expect(reportElementAttributeOrder()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(
-            `- ${filename} tag has attributes out of order ${BG_WARN}(input)${BG_RESET} 🚨`,
-    )
+    expect(reportElementAttributeOrder().length).toBe(1)
+    expect(reportElementAttributeOrder()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}vue-recommended ~ element attribute order${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}The attributes of elements (including components) should be ordered consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#element-attribute-order`,
+      message: `tag has attributes out of order ${BG_WARN}(input)${BG_RESET} 🚨`,
+    }])
   })
 
   it('should not report files where elements attribute order is incorrect 2', () => {
@@ -47,10 +52,12 @@ describe('checkElementAttributeOrder', () => {
     } as SFCTemplateBlock
     const filename = 'element-attribute-order-incorrect-2.vue'
     checkElementAttributeOrder(template, filename)
-    expect(reportElementAttributeOrder()).toBe(2)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(
-            `- ${filename} tag has attributes out of order ${BG_WARN}(div)${BG_RESET} 🚨`,
-    )
+    expect(reportElementAttributeOrder().length).toBe(1)
+    expect(reportElementAttributeOrder()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}vue-recommended ~ element attribute order${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}The attributes of elements (including components) should be ordered consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#element-attribute-order`,
+      message: `tag has attributes out of order ${BG_WARN}(div)${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-recommended/topLevelElementOrder.test.ts b/src/rules/vue-recommended/topLevelElementOrder.test.ts
index 3c39921..5205fc8 100644
--- a/src/rules/vue-recommended/topLevelElementOrder.test.ts
+++ b/src/rules/vue-recommended/topLevelElementOrder.test.ts
@@ -1,10 +1,14 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCBlock } from '@vue/compiler-sfc'
-import { checkTopLevelElementOrder, reportTopLevelElementOrder } from './topLevelElementOrder'
+import { beforeEach, describe, expect, it } from 'vitest'
 
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import type { SFCBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkTopLevelElementOrder, reportTopLevelElementOrder, resetTopLevelElementOrder } from './topLevelElementOrder'
 
 describe('checkTopLevelElementOrder', () => {
+  beforeEach(() => {
+    resetTopLevelElementOrder()
+  })
+
   it('should not report files where top-level element order is correct', () => {
     const script = {
       content: `
@@ -20,12 +24,12 @@ describe('checkTopLevelElementOrder', () => {
           color: white;
         }
         </style>
-      `
-    } as SFCBlock;
+      `,
+    } as SFCBlock
     const filename = 'sfc-correct-order.vue'
     checkTopLevelElementOrder(script.content, filename)
-    expect(reportTopLevelElementOrder()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportTopLevelElementOrder().length).toBe(0)
+    expect(reportTopLevelElementOrder()).toStrictEqual([])
   })
 
   it('should report files where top-level element order is incorrect', () => {
@@ -43,13 +47,17 @@ describe('checkTopLevelElementOrder', () => {
           color: white;
         }
         </style>
-      `
-    } as SFCBlock;
+      `,
+    } as SFCBlock
     const filename = 'sfc-incorrect-order.vue'
     checkTopLevelElementOrder(script.content, filename)
-    expect(reportTopLevelElementOrder()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(` - ${filename} 🚨`)
+    expect(reportTopLevelElementOrder().length).toBe(1)
+    expect(reportTopLevelElementOrder()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}vue-recommended ~ top level element order${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Single-File Components should always order <script>, <template>, and <style> tags consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#single-file-component-top-level-element-order`,
+      message: '🚨',
+    }])
   })
 
   it('should report files where one top-level element order is missing', () => {
@@ -63,12 +71,16 @@ describe('checkTopLevelElementOrder', () => {
         <template>
           <h1 class="age-input">22</h1>
         </template>
-      `
-    } as SFCBlock;
+      `,
+    } as SFCBlock
     const filename = 'sfc-missing-element.vue'
     checkTopLevelElementOrder(script.content, filename)
-    expect(reportTopLevelElementOrder()).toBe(2)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(` - ${filename} 🚨`)
+    expect(reportTopLevelElementOrder().length).toBe(1)
+    expect(reportTopLevelElementOrder()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}vue-recommended ~ top level element order${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Single-File Components should always order <script>, <template>, and <style> tags consistently.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-recommended.html#single-file-component-top-level-element-order`,
+      message: '🚨',
+    }])
   })
-})
\ No newline at end of file
+})
diff --git a/src/rules/vue-strong/componentFilenameCasing.test.ts b/src/rules/vue-strong/componentFilenameCasing.test.ts
index 33e80ba..8c12618 100644
--- a/src/rules/vue-strong/componentFilenameCasing.test.ts
+++ b/src/rules/vue-strong/componentFilenameCasing.test.ts
@@ -1,56 +1,64 @@
-import { describe, it, expect, vi, beforeEach } from 'vitest'
+import { beforeEach, describe, expect, it } from 'vitest'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 import {
   checkComponentFilenameCasing,
   reportComponentFilenameCasing,
   resetComponentFilenameCasing,
 } from './componentFilenameCasing'
 
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
-
 describe('checkComponentFilenameCasing', () => {
   beforeEach(() => {
     resetComponentFilenameCasing()
-    mockConsoleLog.mockClear()
   })
 
   it('ignores PascalCase component file names', () => {
     checkComponentFilenameCasing('components/AppHeader.vue')
-    expect(reportComponentFilenameCasing()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportComponentFilenameCasing().length).toBe(0)
+    expect(reportComponentFilenameCasing()).toStrictEqual([])
   })
 
   it('ignores components in pages folder - Linux', () => {
     checkComponentFilenameCasing('pages/gauranga.vue')
-    expect(reportComponentFilenameCasing()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportComponentFilenameCasing().length).toBe(0)
+    expect(reportComponentFilenameCasing()).toStrictEqual([])
   })
   it('ignores components in pages folder - Windows', () => {
     checkComponentFilenameCasing(`pages\gauranga.vue`)
-    expect(reportComponentFilenameCasing()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportComponentFilenameCasing().length).toBe(0)
+    expect(reportComponentFilenameCasing()).toStrictEqual([])
   })
 
   it('ignores components in layouts folder', () => {
     checkComponentFilenameCasing('layouts/gauranga.vue')
-    expect(reportComponentFilenameCasing()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportComponentFilenameCasing().length).toBe(0)
+    expect(reportComponentFilenameCasing()).toStrictEqual([])
   })
 
   it('ignores kebab-case component file names', () => {
     checkComponentFilenameCasing('components/app-header.vue')
-    expect(reportComponentFilenameCasing()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportComponentFilenameCasing().length).toBe(0)
+    expect(reportComponentFilenameCasing()).toStrictEqual([])
   })
 
   it('detects single lowercase component file names', () => {
     checkComponentFilenameCasing('components/myheader.vue')
-    expect(reportComponentFilenameCasing()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
+    expect(reportComponentFilenameCasing().length).toBe(1)
+    expect(reportComponentFilenameCasing()).toStrictEqual([{
+      file: 'components/myheader.vue',
+      rule: `${TEXT_INFO}vue-strong ~ component name is not PascalCase and not kebab-case${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Rename the component to use PascalCase or kebab-case file name.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#single-file-component-filename-casing`,
+      message: `🚨`,
+    }])
   })
 
   it('detects single mixed case component file names', () => {
     checkComponentFilenameCasing('components/myHeader.vue')
-    expect(reportComponentFilenameCasing()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
+    expect(reportComponentFilenameCasing().length).toBe(1)
+    expect(reportComponentFilenameCasing()).toStrictEqual([{
+      file: 'components/myHeader.vue',
+      rule: `${TEXT_INFO}vue-strong ~ component name is not PascalCase and not kebab-case${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Rename the component to use PascalCase or kebab-case file name.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#single-file-component-filename-casing`,
+      message: `🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-strong/componentFiles.test.ts b/src/rules/vue-strong/componentFiles.test.ts
index 6bbf7a5..0f6da0f 100644
--- a/src/rules/vue-strong/componentFiles.test.ts
+++ b/src/rules/vue-strong/componentFiles.test.ts
@@ -1,23 +1,25 @@
-import { describe, expect, it, vi } from 'vitest';
-import { SFCScriptBlock } from '@vue/compiler-sfc';
-import { BG_ERR, BG_RESET, BG_WARN } from '../asceeCodes';
-import { checkComponentFiles, reportComponentFiles } from './componentFiles';
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {});
+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 { checkComponentFiles, reportComponentFiles, resetComponentFiles } from './componentFiles'
 
 describe('checkComponentFiles', () => {
+  beforeEach(() => {
+    resetComponentFiles()
+  })
+
   it('should not report files where each component is its own file', () => {
     const script = {
       content: `
         <script setup>
         console.log('This is just fine');
         </script>
-      `
+      `,
     } as SFCScriptBlock
     const filename = 'component.vue'
     checkComponentFiles(script, filename)
-    expect(reportComponentFiles()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportComponentFiles().length).toBe(0)
+    expect(reportComponentFiles()).toStrictEqual([])
   })
 
   it('should report files each component is not its own file', () => {
@@ -32,15 +34,21 @@ describe('checkComponentFiles', () => {
           // ...
         })
         </script>
-      `
+      `,
     } as SFCScriptBlock
     const filename = 'component.vue'
-    const lineNumber = 6;
     checkComponentFiles(script, filename)
-    expect(reportComponentFiles()).toBe(2)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(
-      `- ${filename}#${lineNumber} ${BG_WARN}(TodoItem)${BG_RESET} 🚨`
-    )
+    expect(reportComponentFiles().length).toBe(2)
+    expect(reportComponentFiles()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}vue-strong ~ component files${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Whenever a build system is available to concatenate files, each component should be in its own file.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files`,
+      message: `line #2 ${BG_WARN}(TodoList)${BG_RESET} 🚨`,
+    }, {
+      file: filename,
+      rule: `${TEXT_INFO}vue-strong ~ component files${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Whenever a build system is available to concatenate files, each component should be in its own file.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#component-files`,
+      message: `line #6 ${BG_WARN}(TodoItem)${BG_RESET} 🚨`,
+    }])
   })
-})
\ No newline at end of file
+})
diff --git a/src/rules/vue-strong/directiveShorthands.test.ts b/src/rules/vue-strong/directiveShorthands.test.ts
index 5fc313f..275dcca 100644
--- a/src/rules/vue-strong/directiveShorthands.test.ts
+++ b/src/rules/vue-strong/directiveShorthands.test.ts
@@ -1,11 +1,13 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCDescriptor, SFCTemplateBlock } from '@vue/compiler-sfc'
-import { checkDirectiveShorthands, reportDirectiveShorthands } from './directiveShorthands'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCDescriptor } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkDirectiveShorthands, reportDirectiveShorthands, resetDirectiveShorthands } from './directiveShorthands'
 
 describe('checkDirectiveShorthands', () => {
+  beforeEach(() => {
+    resetDirectiveShorthands()
+  })
+
   it('should not report files where directive shorthands are used', () => {
     const template = `<template #header>
       <input
@@ -21,8 +23,8 @@ describe('checkDirectiveShorthands', () => {
     } as SFCDescriptor
     const fileName = 'directive-shorthands.vue'
     checkDirectiveShorthands(descriptor, fileName)
-    expect(reportDirectiveShorthands()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportDirectiveShorthands().length).toBe(0)
+    expect(reportDirectiveShorthands()).toStrictEqual([])
   })
 
   it('should report files where directive shorthands are not used', () => {
@@ -40,8 +42,26 @@ describe('checkDirectiveShorthands', () => {
     } as SFCDescriptor
     const fileName = 'no-directive-shorthands.vue'
     checkDirectiveShorthands(descriptor, fileName)
-    expect(reportDirectiveShorthands()).toBe(3)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName}:4 ${BG_WARN}v-on${BG_RESET} 🚨`)
+    expect(reportDirectiveShorthands().length).toBe(3)
+    expect(reportDirectiveShorthands()).toStrictEqual([
+      {
+        file: fileName,
+        rule: `${TEXT_INFO}vue-strong ~ directive shorthands not used${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Use ":" for v-bind:, "@" for v-on: and "#" for v-slot.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands`,
+        message: `line #1 ${BG_WARN}v-slot${BG_RESET} 🚨`,
+      },
+      {
+        file: fileName,
+        rule: `${TEXT_INFO}vue-strong ~ directive shorthands not used${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Use ":" for v-bind:, "@" for v-on: and "#" for v-slot.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands`,
+        message: `line #3 ${BG_WARN}v-bind${BG_RESET} 🚨`,
+      },
+      {
+        file: fileName,
+        rule: `${TEXT_INFO}vue-strong ~ directive shorthands not used${TEXT_RESET}`,
+        description: `👉 ${TEXT_WARN}Use ":" for v-bind:, "@" for v-on: and "#" for v-slot.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#directive-shorthands`,
+        message: `line #4 ${BG_WARN}v-on${BG_RESET} 🚨`,
+      },
+    ])
   })
 })
diff --git a/src/rules/vue-strong/fullWordComponentName.test.ts b/src/rules/vue-strong/fullWordComponentName.test.ts
index acddeb6..33de2d0 100644
--- a/src/rules/vue-strong/fullWordComponentName.test.ts
+++ b/src/rules/vue-strong/fullWordComponentName.test.ts
@@ -1,32 +1,42 @@
-import { describe, expect, it, vi } from 'vitest'
-import { checkFullWordComponentName, reportFullWordComponentName } from './fullWordComponentName'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkFullWordComponentName, reportFullWordComponentName, resetFullWordComponentName } from './fullWordComponentName'
 
 describe('checkFullWordComponentName', () => {
+  beforeEach(() => {
+    resetFullWordComponentName()
+  })
+
   it('should not report files where filename has at least three consonants', () => {
-    const filename = 'path/to/file/fullWordComponentNames.vue';
-    checkFullWordComponentName(filename);
-    expect(reportFullWordComponentName()).toBe(0);
-    expect(mockConsoleLog).not.toHaveBeenCalled();
+    const filename = 'path/to/file/fullWordComponentNames.vue'
+    checkFullWordComponentName(filename)
+    expect(reportFullWordComponentName().length).toBe(0)
+    expect(reportFullWordComponentName()).toStrictEqual([])
   })
 
   it('should report files where filename has less than three consonants', () => {
-    const filename = 'menu.vue';
-    const componentName = 'menu';
-    checkFullWordComponentName(filename);
-    expect(reportFullWordComponentName()).toBe(1);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${filename} 🚨 ${BG_WARN}(${componentName})${BG_RESET}`);
+    const filename = 'menu.vue'
+    const componentName = 'menu'
+    checkFullWordComponentName(filename)
+    expect(reportFullWordComponentName().length).toBe(1)
+    expect(reportFullWordComponentName()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}vue-strong ~ full-word component names${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Component names should prefer full words over abbreviations.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#full-word-component-names`,
+      message: `${BG_WARN}(${componentName})${BG_RESET} 🚨`,
+    }])
   })
 
-  it('should report files where filename has less than three consonants', () => {
-    const filename = 'nav.vue';
-    const componentName = 'nav';
-    checkFullWordComponentName(filename);
-    expect(reportFullWordComponentName()).toBe(2);
-    expect(mockConsoleLog).toHaveBeenCalled();
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${filename} 🚨 ${BG_WARN}(${componentName})${BG_RESET}`);
+  it('should report files where filename has less than three consonants ', () => {
+    const filename = 'nav.vue'
+    const componentName = 'nav'
+    checkFullWordComponentName(filename)
+    expect(reportFullWordComponentName().length).toBe(1)
+    expect(reportFullWordComponentName()).toStrictEqual([{
+      file: filename,
+      rule: `${TEXT_INFO}vue-strong ~ full-word component names${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Component names should prefer full words over abbreviations.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#full-word-component-names`,
+      message: `${BG_WARN}(${componentName})${BG_RESET} 🚨`,
+    }])
   })
-})
\ No newline at end of file
+})
diff --git a/src/rules/vue-strong/propNameCasing.test.ts b/src/rules/vue-strong/propNameCasing.test.ts
index 2213617..ff075f8 100644
--- a/src/rules/vue-strong/propNameCasing.test.ts
+++ b/src/rules/vue-strong/propNameCasing.test.ts
@@ -1,10 +1,13 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkPropNameCasing, reportPropNameCasing } from './propNameCasing'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCScriptBlock } from '@vue/compiler-sfc'
+import { TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkPropNameCasing, reportPropNameCasing, resetPropNameCasing } from './propNameCasing'
 
 describe('checkPropNameCasing', () => {
+  beforeEach(() => {
+    resetPropNameCasing()
+  })
+
   it('should not report files with camelCase props name', () => {
     const script = {
       content: `<script setup>
@@ -14,8 +17,8 @@ describe('checkPropNameCasing', () => {
     } as SFCScriptBlock
     const fileName = 'proper-prop-name.vue'
     checkPropNameCasing(script, fileName)
-    expect(reportPropNameCasing()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportPropNameCasing().length).toBe(0)
+    expect(reportPropNameCasing()).toStrictEqual([])
   })
 
   it('should report files with none camelCase props name', () => {
@@ -28,8 +31,12 @@ describe('checkPropNameCasing', () => {
     } as SFCScriptBlock
     const fileName = 'mixed-case-prop-name.vue'
     checkPropNameCasing(script, fileName)
-    expect(reportPropNameCasing()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName} 🚨`)
+    expect(reportPropNameCasing().length).toBe(1)
+    expect(reportPropNameCasing()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-strong ~ prop names are not camelCased${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Rename the props to camelCase.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#prop-name-casing`,
+      message: `🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-strong/quotedAttributeValues.test.ts b/src/rules/vue-strong/quotedAttributeValues.test.ts
index 7f99b83..052ed4f 100644
--- a/src/rules/vue-strong/quotedAttributeValues.test.ts
+++ b/src/rules/vue-strong/quotedAttributeValues.test.ts
@@ -1,11 +1,13 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCDescriptor } from '@vue/compiler-sfc'
-import { checkQuotedAttributeValues, reportQuotedAttributeValues } from './quotedAttributeValues'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCDescriptor } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
+import { checkQuotedAttributeValues, reportQuotedAttributeValues, resetQuotedAttributeValues } from './quotedAttributeValues'
 
 describe('checkQuotedAttributeValues', () => {
+  beforeEach(() => {
+    resetQuotedAttributeValues()
+  })
+
   it('should not report files where template attribute values are quoted', () => {
     const template = `<template>
       <AppSidebar :style="{ width: sidebarWidth + 'px' }">
@@ -18,8 +20,8 @@ describe('checkQuotedAttributeValues', () => {
     } as SFCDescriptor
     const fileName = 'quoted-attribue-value.vue'
     checkQuotedAttributeValues(descriptor, fileName)
-    expect(reportQuotedAttributeValues()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportQuotedAttributeValues().length).toBe(0)
+    expect(reportQuotedAttributeValues()).toStrictEqual([])
   })
 
   it('should report files where template attribute values are not quoted', () => {
@@ -34,8 +36,12 @@ describe('checkQuotedAttributeValues', () => {
     } as SFCDescriptor
     const fileName = 'not-quoted-attribue-value.vue'
     checkQuotedAttributeValues(descriptor, fileName)
-    expect(reportQuotedAttributeValues()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName}#2 ${BG_WARN}:style={${BG_RESET} 🚨`)
+    expect(reportQuotedAttributeValues().length).toBe(1)
+    expect(reportQuotedAttributeValues()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-strong ~ attribute value is not quoted${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Use quotes for attribute values.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#quoted-attribute-values`,
+      message: `line #2 ${BG_WARN}:style={${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-strong/selfClosingComponents.test.ts b/src/rules/vue-strong/selfClosingComponents.test.ts
index b7c4e55..852e6a3 100644
--- a/src/rules/vue-strong/selfClosingComponents.test.ts
+++ b/src/rules/vue-strong/selfClosingComponents.test.ts
@@ -1,14 +1,11 @@
-import { beforeEach, describe, expect, it, vi } from 'vitest'
-import { SFCDescriptor } from '@vue/compiler-sfc'
+import { beforeEach, describe, expect, it } from 'vitest'
+import type { SFCDescriptor } from '@vue/compiler-sfc'
+import { BG_RESET, BG_WARN, TEXT_INFO, TEXT_RESET, TEXT_WARN } from '../asceeCodes'
 import { checkSelfClosingComponents, reportSelfClosingComponents, resetSelfClosingComponents } from './selfClosingComponents'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
 
 describe('checkSelfClosingComponents', () => {
   beforeEach(() => {
     resetSelfClosingComponents()
-    mockConsoleLog.mockClear()
   })
 
   it('should not report simple html tags', () => {
@@ -23,8 +20,8 @@ describe('checkSelfClosingComponents', () => {
     } as SFCDescriptor
     const fileName = 'self-close-component.vue'
     checkSelfClosingComponents(descriptor, fileName)
-    expect(reportSelfClosingComponents()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportSelfClosingComponents().length).toBe(0)
+    expect(reportSelfClosingComponents()).toStrictEqual([])
   })
 
   it('should not report files where the components self close', () => {
@@ -39,8 +36,8 @@ describe('checkSelfClosingComponents', () => {
     } as SFCDescriptor
     const fileName = 'self-close-component.vue'
     checkSelfClosingComponents(descriptor, fileName)
-    expect(reportSelfClosingComponents()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportSelfClosingComponents().length).toBe(0)
+    expect(reportSelfClosingComponents()).toStrictEqual([])
   })
 
   it('should report files where the components does not self close - single line', () => {
@@ -55,9 +52,13 @@ describe('checkSelfClosingComponents', () => {
     } as SFCDescriptor
     const fileName = 'not-self-close-component.vue'
     checkSelfClosingComponents(descriptor, fileName)
-    expect(reportSelfClosingComponents()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName}#2 ${BG_WARN}<MyComponent></MyComponent>${BG_RESET} 🚨`)
+    expect(reportSelfClosingComponents().length).toBe(1)
+    expect(reportSelfClosingComponents()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-strong ~ component is not self closing${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Components with no content should be self-closing.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#self-closing-components`,
+      message: `line #2 ${BG_WARN}<MyComponent></MyComponent>${BG_RESET} 🚨`,
+    }])
   })
 
   it('should report files where the components does not self close - multi line', () => {
@@ -74,8 +75,12 @@ describe('checkSelfClosingComponents', () => {
     } as SFCDescriptor
     const fileName = 'not-self-close-component.vue'
     checkSelfClosingComponents(descriptor, fileName)
-    expect(reportSelfClosingComponents()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName}#4 ${BG_WARN}></MyComponent>${BG_RESET} 🚨`)
+    expect(reportSelfClosingComponents().length).toBe(1)
+    expect(reportSelfClosingComponents()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-strong ~ component is not self closing${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Components with no content should be self-closing.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#self-closing-components`,
+      message: `line #4 ${BG_WARN}></MyComponent>${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-strong/simpleComputed.test.ts b/src/rules/vue-strong/simpleComputed.test.ts
index 4478a37..209373b 100644
--- a/src/rules/vue-strong/simpleComputed.test.ts
+++ b/src/rules/vue-strong/simpleComputed.test.ts
@@ -1,28 +1,30 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCScriptBlock } from '@vue/compiler-sfc'
-import { checkSimpleComputed, reportSimpleComputed } from './simpleComputed'
-import { BG_RESET, BG_WARN } from '../asceeCodes'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+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 { checkSimpleComputed, reportSimpleComputed, resetSimpleComputed } from './simpleComputed'
 
 describe('checkSimpleComputed', () => {
+  beforeEach(() => {
+    resetSimpleComputed()
+  })
+
   it('should not report files with simple computed properties', () => {
     let script = {
       content: `const isWeekend = computed(() => today.getDay() === 0 || today.getDay() === 6)
       const one = 1
       const isWeekend2 = computed(() => today.getDay() === 0 || today.getDay() === 6)`,
     } as SFCScriptBlock
-    let fileName = 'simple-computed.vue'
+    const fileName = 'simple-computed.vue'
     checkSimpleComputed(script, fileName)
-    expect(reportSimpleComputed()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportSimpleComputed().length).toBe(0)
+    expect(reportSimpleComputed()).toStrictEqual([])
 
     script = {
       content: `const isWeekend = computed(()=>today.getDay() === 0 || today.getDay() === 6)`,
     } as SFCScriptBlock
     checkSimpleComputed(script, fileName)
-    expect(reportSimpleComputed()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportSimpleComputed().length).toBe(0)
+    expect(reportSimpleComputed()).toStrictEqual([])
   })
 
   it('should report files with complicated computed properties', () => {
@@ -49,8 +51,12 @@ describe('checkSimpleComputed', () => {
     } as SFCScriptBlock
     const fileName = 'complicated-computed.vue'
     checkSimpleComputed(script, fileName)
-    expect(reportSimpleComputed()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(`- ${fileName}:2 ${BG_WARN}computed${BG_RESET} 🚨`)
+    expect(reportSimpleComputed().length).toBe(1)
+    expect(reportSimpleComputed()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-strong ~ complicated computed property${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Refactor the computed properties to smaller ones.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#simple-computed-properties`,
+      message: `line #2 ${BG_WARN}computed${BG_RESET} 🚨`,
+    }])
   })
 })
diff --git a/src/rules/vue-strong/templateSimpleExpression.test.ts b/src/rules/vue-strong/templateSimpleExpression.test.ts
index b013cfa..7a5648d 100644
--- a/src/rules/vue-strong/templateSimpleExpression.test.ts
+++ b/src/rules/vue-strong/templateSimpleExpression.test.ts
@@ -1,11 +1,13 @@
-import { describe, expect, it, vi } from 'vitest'
-import { SFCTemplateBlock } from '@vue/compiler-sfc'
-import { BG_ERR, BG_RESET, BG_WARN } from '../asceeCodes'
-import { checkTemplateSimpleExpression, reportTemplateSimpleExpression } from './templateSimpleExpression'
-
-const mockConsoleLog = vi.spyOn(console, 'log').mockImplementation(() => {})
+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 { checkTemplateSimpleExpression, reportTemplateSimpleExpression, resetTemplateSimpleExpression } from './templateSimpleExpression'
 
 describe('checkTemplateSimpleExpression', () => {
+  beforeEach(() => {
+    resetTemplateSimpleExpression()
+  })
+
   it('should not report files where template expression is simple', () => {
     const script = {
       content: `<template>
@@ -15,8 +17,8 @@ describe('checkTemplateSimpleExpression', () => {
     } as SFCTemplateBlock
     const fileName = 'simple-expression.vue'
     checkTemplateSimpleExpression(script, fileName)
-    expect(reportTemplateSimpleExpression()).toBe(0)
-    expect(mockConsoleLog).not.toHaveBeenCalled()
+    expect(reportTemplateSimpleExpression().length).toBe(0)
+    expect(reportTemplateSimpleExpression()).toStrictEqual([])
   })
 
   it('should report files where template expression is not simple', () => {
@@ -31,10 +33,12 @@ describe('checkTemplateSimpleExpression', () => {
     } as SFCTemplateBlock
     const fileName = 'not-simple-expression.vue'
     checkTemplateSimpleExpression(script, fileName)
-    expect(reportTemplateSimpleExpression()).toBe(1)
-    expect(mockConsoleLog).toHaveBeenCalled()
-    expect(mockConsoleLog).toHaveBeenLastCalledWith(
-      `- ${fileName}#5 ${BG_WARN}fullName.split(' ').map((word) => {${BG_RESET} 🚨`
-    )
+    expect(reportTemplateSimpleExpression().length).toBe(1)
+    expect(reportTemplateSimpleExpression()).toStrictEqual([{
+      file: fileName,
+      rule: `${TEXT_INFO}vue-strong ~ lengthy template expression${TEXT_RESET}`,
+      description: `👉 ${TEXT_WARN}Refactor the expression into a computed property.${TEXT_RESET} See: https://vuejs.org/style-guide/rules-strongly-recommended.html#simple-expressions-in-templates`,
+      message: `line #5 ${BG_WARN}fullName.split(' ').map((word) => {${BG_RESET} 🚨`,
+    }])
   })
 })