44 */
55'use strict'
66
7+ const path = require ( 'path' )
78const utils = require ( '../utils' )
89const casing = require ( '../utils/casing' )
910
@@ -17,6 +18,93 @@ function camelize(str) {
1718 return str . replace ( / - ( \w ) / g, ( _ , c ) => ( c ? c . toUpperCase ( ) : '' ) )
1819}
1920
21+ class DefinedInSetupComponents {
22+ constructor ( ) {
23+ /**
24+ * Component names
25+ * @type {Set<string> }
26+ */
27+ this . names = new Set ( )
28+ }
29+
30+ /**
31+ * @param {string[] } names
32+ */
33+ addName ( ...names ) {
34+ for ( const name of names ) {
35+ this . names . add ( name )
36+ }
37+ }
38+
39+ /**
40+ * @see https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L334
41+ * @param {string } rawName
42+ */
43+ isDefinedComponent ( rawName ) {
44+ if ( this . names . has ( rawName ) ) {
45+ return true
46+ }
47+ const camelName = camelize ( rawName )
48+ if ( this . names . has ( camelName ) ) {
49+ return true
50+ }
51+ const pascalName = casing . capitalize ( camelName )
52+ if ( this . names . has ( pascalName ) ) {
53+ return true
54+ }
55+ // Check namespace
56+ // https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L305
57+ const dotIndex = rawName . indexOf ( '.' )
58+ if ( dotIndex > 0 && this . isDefinedComponent ( rawName . slice ( 0 , dotIndex ) ) ) {
59+ return true
60+ }
61+ return false
62+ }
63+ }
64+
65+ class DefinedInOptionComponents {
66+ constructor ( ) {
67+ /**
68+ * Component names
69+ * @type {Set<string> }
70+ */
71+ this . names = new Set ( )
72+ /**
73+ * Component names, transformed to kebab-case
74+ * @type {Set<string> }
75+ */
76+ this . kebabCaseNames = new Set ( )
77+ }
78+
79+ /**
80+ * @param {string[] } names
81+ */
82+ addName ( ...names ) {
83+ for ( const name of names ) {
84+ this . names . add ( name )
85+ this . kebabCaseNames . add ( casing . kebabCase ( name ) )
86+ }
87+ }
88+
89+ /**
90+ * @param {string } rawName
91+ */
92+ isDefinedComponent ( rawName ) {
93+ if ( this . names . has ( rawName ) ) {
94+ return true
95+ }
96+ const kebabCaseName = casing . kebabCase ( rawName )
97+ if (
98+ this . kebabCaseNames . has ( kebabCaseName ) &&
99+ ! casing . isPascalCase ( rawName )
100+ ) {
101+ // Component registered as `foo-bar` cannot be used as `FooBar`
102+ return true
103+ }
104+ return false
105+ }
106+ }
107+
20108module . exports = {
21109 meta : {
22110 type : 'suggestion' ,
@@ -109,13 +197,15 @@ module.exports = {
109197
110198 if ( utils . isScriptSetup ( context ) ) {
111199 // For <script setup>
200+ const definedInSetupComponents = new DefinedInSetupComponents ( )
201+ const definedInOptionComponents = new DefinedInOptionComponents ( )
202+
112203 /** @type {Set<string> } */
113- const scriptVariableNames = new Set ( )
114204 const scriptTypeOnlyNames = new Set ( )
115205 const globalScope = context . getSourceCode ( ) . scopeManager . globalScope
116206 if ( globalScope ) {
117207 for ( const variable of globalScope . variables ) {
118- scriptVariableNames . add ( variable . name )
208+ definedInSetupComponents . addName ( variable . name )
119209 }
120210 const moduleScope = globalScope . childScopes . find (
121211 ( scope ) => scope . type === 'module'
@@ -146,39 +236,37 @@ module.exports = {
146236 ) {
147237 scriptTypeOnlyNames . add ( variable . name )
148238 } else {
149- scriptVariableNames . add ( variable . name )
239+ definedInSetupComponents . addName ( variable . name )
150240 }
151241 }
152242 }
153- /**
154- * @see https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L334
155- * @param {string } name
156- */
157- const existsSetupReference = ( name ) => {
158- if ( scriptVariableNames . has ( name ) ) {
159- return true
160- }
161- const camelName = camelize ( name )
162- if ( scriptVariableNames . has ( camelName ) ) {
163- return true
164- }
165- const pascalName = casing . capitalize ( camelName )
166- if ( scriptVariableNames . has ( pascalName ) ) {
167- return true
243+
244+ // For circular references
245+ const fileName = context . getFilename ( )
246+ const selfComponentName = path . basename ( fileName , path . extname ( fileName ) )
247+ definedInSetupComponents . addName ( selfComponentName )
248+ scriptVisitor = utils . defineVueVisitor ( context , {
249+ onVueObjectEnter ( node , { type } ) {
250+ if ( type !== 'export' ) return
251+ const nameProperty = utils . findProperty ( node , 'name' )
252+
253+ if ( nameProperty && utils . isStringLiteral ( nameProperty . value ) ) {
254+ const name = utils . getStringLiteralValue ( nameProperty . value )
255+ if ( name ) {
256+ definedInOptionComponents . addName ( name )
257+ }
258+ }
168259 }
169- return false
170- }
260+ } )
261+
171262 verifyName = ( rawName , reportNode ) => {
172263 if ( ! isVerifyTargetComponent ( rawName ) ) {
173264 return
174265 }
175- if ( existsSetupReference ( rawName ) ) {
266+ if ( definedInSetupComponents . isDefinedComponent ( rawName ) ) {
176267 return
177268 }
178- // Check namespace
179- // https://github.com/vuejs/core/blob/ae4b0783d78670b6e942ae2a4e3ec6efbbffa158/packages/compiler-core/src/transforms/transformElement.ts#L305
180- const dotIndex = rawName . indexOf ( '.' )
181- if ( dotIndex > 0 && existsSetupReference ( rawName . slice ( 0 , dotIndex ) ) ) {
269+ if ( definedInOptionComponents . isDefinedComponent ( rawName ) ) {
182270 return
183271 }
184272
@@ -192,26 +280,10 @@ module.exports = {
192280 }
193281 } else {
194282 // For Options API
195-
196- /**
197- * All registered components
198- * @type {string[] }
199- */
200- const registeredComponentNames = [ ]
201- /**
202- * All registered components, transformed to kebab-case
203- * @type {string[] }
204- */
205- const registeredComponentKebabCaseNames = [ ]
206-
207- /**
208- * All registered components using kebab-case syntax
209- * @type {string[] }
210- */
211- const componentsRegisteredAsKebabCase = [ ]
283+ const definedInOptionComponents = new DefinedInOptionComponents ( )
212284
213285 scriptVisitor = utils . executeOnVue ( context , ( obj ) => {
214- registeredComponentNames . push (
286+ definedInOptionComponents . addName (
215287 ...utils . getRegisteredComponents ( obj ) . map ( ( { name } ) => name )
216288 )
217289
@@ -220,33 +292,16 @@ module.exports = {
220292 if ( nameProperty && utils . isStringLiteral ( nameProperty . value ) ) {
221293 const name = utils . getStringLiteralValue ( nameProperty . value )
222294 if ( name ) {
223- registeredComponentNames . push ( name )
295+ definedInOptionComponents . addName ( name )
224296 }
225297 }
226-
227- registeredComponentKebabCaseNames . push (
228- ...registeredComponentNames . map ( ( name ) => casing . kebabCase ( name ) )
229- )
230- componentsRegisteredAsKebabCase . push (
231- ...registeredComponentNames . filter (
232- ( name ) => name === casing . kebabCase ( name )
233- )
234- )
235298 } )
236299
237300 verifyName = ( rawName , reportNode ) => {
238301 if ( ! isVerifyTargetComponent ( rawName ) ) {
239302 return
240303 }
241- if ( registeredComponentNames . includes ( rawName ) ) {
242- return
243- }
244- const kebabCaseName = casing . kebabCase ( rawName )
245- if (
246- registeredComponentKebabCaseNames . includes ( kebabCaseName ) &&
247- ! casing . isPascalCase ( rawName )
248- ) {
249- // Component registered as `foo-bar` cannot be used as `FooBar`
304+ if ( definedInOptionComponents . isDefinedComponent ( rawName ) ) {
250305 return
251306 }
252307
0 commit comments