diff --git a/src/analyzer.ts b/src/analyzer.ts
index 1a81d4d1..3a50fa29 100644
--- a/src/analyzer.ts
+++ b/src/analyzer.ts
@@ -22,6 +22,7 @@ import { checkFunctionSize, reportFunctionSize } from './rules/rrd/functionSize'
import { checkParameterCount, reportParameterCount } from './rules/rrd/parameterCount'
import { checkShortVariableName, reportShortVariableName } from './rules/rrd/shortVariableName'
import { checkSimpleComputed, reportSimpleComputed } from './rules/vue-strong/simpleComputed'
+import { checkComponentFiles, reportComponentFiles } from './rules/vue-strong/componentFiles'
import { checkImplicitParentChildCommunication, reportImplicitParentChildCommunication } from './rules/vue-caution/implicitParentChildCommunication'
let filesCount = 0
@@ -82,6 +83,7 @@ export const analyze = (dir: string) => {
checkSimpleProp(script, filePath)
checkPropNameCasing(script, filePath)
+ checkComponentFiles(script, filePath)
checkScriptLength(script, filePath)
checkCyclomaticComplexity(script, filePath)
@@ -126,6 +128,7 @@ export const analyze = (dir: string) => {
errors += reportQuotedAttributeValues()
errors += reportDirectiveShorthands()
errors += reportSimpleComputed()
+ errors += reportComponentFiles()
// vue-reccomended rules
diff --git a/src/rules/vue-strong/componentFiles.test.ts b/src/rules/vue-strong/componentFiles.test.ts
new file mode 100644
index 00000000..6bbf7a56
--- /dev/null
+++ b/src/rules/vue-strong/componentFiles.test.ts
@@ -0,0 +1,46 @@
+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(() => {});
+
+describe('checkComponentFiles', () => {
+ it('should not report files where each component is its own file', () => {
+ const script = {
+ content: `
+
+ `
+ } as SFCScriptBlock
+ const filename = 'component.vue'
+ checkComponentFiles(script, filename)
+ expect(reportComponentFiles()).toBe(0)
+ expect(mockConsoleLog).not.toHaveBeenCalled()
+ })
+
+ it('should report files each component is not its own file', () => {
+ const script = {
+ content: `
+
+ `
+ } 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} 🚨`
+ )
+ })
+})
\ No newline at end of file
diff --git a/src/rules/vue-strong/componentFiles.ts b/src/rules/vue-strong/componentFiles.ts
new file mode 100644
index 00000000..64a0fe46
--- /dev/null
+++ b/src/rules/vue-strong/componentFiles.ts
@@ -0,0 +1,39 @@
+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 getLineNumber from "../getLineNumber";
+
+type ComponentFiles = { filename: string, message: string };
+
+const componentFiles: ComponentFiles[] = [];
+
+const checkComponentFiles = (script: SFCScriptBlock, filePath: string) => {
+ // regular expression to match `.component('anyString', { ... })` pattern
+ const regex = /app\.component\('([^']+)',\s*\{[^}]*\}\)/g;
+
+ const matches = [...script.content.matchAll(regex)].map(match => match[1].trim());
+
+ 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}` })
+ })
+}
+
+const reportComponentFiles = () => {
+ if (componentFiles.length > 0) {
+ // count only non duplicated objects (by its `filename` property)
+ const fileCount = getUniqueFilenameCount(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} 🚨`)
+ })
+ }
+ return componentFiles.length;
+}
+
+export { checkComponentFiles, reportComponentFiles };
\ No newline at end of file