Repository: rrd108/vue-mess-detector
Pull request #75 (2024-07-29 20:10:37)

feat: add vue-strong full-word-component-name rule w tests


Patch:
diff --git a/src/rules/rules.ts b/src/rules/rules.ts
index c6d76a3..3972837 100644
--- a/src/rules/rules.ts
+++ b/src/rules/rules.ts
@@ -11,6 +11,7 @@ export const RULES = {
     'selfClosingComponents',
     'simpleComputed',
     'templateSimpleExpression',
+    'fullWordComponentName'
   ],
   rrd: [
     'cyclomaticComplexity',
diff --git a/src/rules/vue-strong/fullWordComponentName.ts b/src/rules/vue-strong/fullWordComponentName.ts
new file mode 100644
index 0000000..6a41645
--- /dev/null
+++ b/src/rules/vue-strong/fullWordComponentName.ts
@@ -0,0 +1,51 @@
+import { BG_RESET, BG_WARN, TEXT_WARN, TEXT_RESET, TEXT_INFO, BG_ERR } from '../asceeCodes'
+import { global, charIn, charNotIn, createRegExp, exactly, oneOrMore } from "magic-regexp";
+import { getUniqueFilenameCount } from "../../helpers";
+
+type FullWordComponentNames = { filename: string, filePath: string };
+
+const fullWordComponentNames: FullWordComponentNames[] = [];
+
+const checkFullWordComponentName = (filePath: string) => {
+  // regular expression to match `filename.vue` pattern
+  const regex = createRegExp(
+    oneOrMore(charNotIn('/')).grouped(),
+    exactly('.vue').at.lineEnd()
+  );
+
+  const match = filePath.match(regex);
+
+  if (match) {
+    const filename = match[0]?.split('.vue')[0] as string;
+
+    // regular expression to match and count consonants
+    const consonantsRegex = createRegExp(
+      charIn('bcdfghjklmnpqrstvwxyzBCDFGHJKLMNPQRSTVWXYZ'),
+      [global]
+    )
+
+    const consonantsMatch = filename.match(consonantsRegex);
+
+    if (!consonantsMatch || consonantsMatch.length < 3) {
+      fullWordComponentNames.push({ filename, filePath });
+    }
+  }
+}
+
+const reportFullWordComponentName = () => {
+  if (fullWordComponentNames.length > 0) {
+    // count only non duplicated objects (by its `filename` property)
+    const fileCount = getUniqueFilenameCount<FullWordComponentNames>(fullWordComponentNames, 'filename')
+
+    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`
+    )
+    fullWordComponentNames.forEach(file => {
+      console.log(`- ${file.filePath} 🚨 ${BG_WARN}(${file.filename})${BG_RESET}`)
+    })
+  }
+  return fullWordComponentNames.length;
+}
+
+export { checkFullWordComponentName, reportFullWordComponentName }
\ No newline at end of file
diff --git a/src/rulesCheck.ts b/src/rulesCheck.ts
index 8b2f3b9..0a4cc2f 100644
--- a/src/rulesCheck.ts
+++ b/src/rulesCheck.ts
@@ -13,6 +13,7 @@ import { checkTemplateSimpleExpression } from './rules/vue-strong/templateSimple
 import { checkQuotedAttributeValues } from './rules/vue-strong/quotedAttribueValues'
 import { checkSelfClosingComponents } from './rules/vue-strong/selfClosingComponents'
 import { checkDirectiveShorthands } from './rules/vue-strong/directiveShorthands'
+import { checkFullWordComponentName } from './rules/vue-strong/fullWordComponentName'
 import { checkTooManyProps } from './rules/rrd/tooManyProps'
 import { checkFunctionSize } from './rules/rrd/functionSize'
 import { checkParameterCount } from './rules/rrd/parameterCount'
@@ -44,6 +45,7 @@ export const checkRules = (descriptor: SFCDescriptor, filePath: string, ignore:
     checkTemplateSimpleExpression(descriptor.template, filePath)
     checkQuotedAttributeValues(descriptor, filePath)
     checkDirectiveShorthands(descriptor, filePath)
+    checkFullWordComponentName(filePath)
   }
 
   if (!ignore.includes('vue-caution')) {
diff --git a/src/rulesReport.ts b/src/rulesReport.ts
index d8e4ad4..427c2f3 100644
--- a/src/rulesReport.ts
+++ b/src/rulesReport.ts
@@ -13,6 +13,7 @@ import { reportTemplateSimpleExpression } from './rules/vue-strong/templateSimpl
 import { reportQuotedAttributeValues } from './rules/vue-strong/quotedAttribueValues'
 import { reportSelfClosingComponents } from './rules/vue-strong/selfClosingComponents'
 import { reportDirectiveShorthands } from './rules/vue-strong/directiveShorthands'
+import { reportFullWordComponentName } from './rules/vue-strong/fullWordComponentName'
 import { reportTooManyProps } from './rules/rrd/tooManyProps'
 import { reportFunctionSize } from './rules/rrd/functionSize'
 import { reportParameterCount } from './rules/rrd/parameterCount'
@@ -41,6 +42,7 @@ export const reportRules = () => {
   errors += reportDirectiveShorthands()
   errors += reportSimpleComputed()
   errors += reportComponentFiles()
+  errors += reportFullWordComponentName()
 
   // vue-caution rules
   errors += reportImplicitParentChildCommunication()


Test patch:
diff --git a/src/rules/vue-strong/fullWordComponentName.test.ts b/src/rules/vue-strong/fullWordComponentName.test.ts
new file mode 100644
index 0000000..acddeb6
--- /dev/null
+++ b/src/rules/vue-strong/fullWordComponentName.test.ts
@@ -0,0 +1,32 @@
+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(() => {})
+
+describe('checkFullWordComponentName', () => {
+  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();
+  })
+
+  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}`);
+  })
+
+  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}`);
+  })
+})
\ No newline at end of file