22 * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
33 * SPDX-License-Identifier: AGPL-3.0-or-later
44 */
5+
56import type { Rule } from 'eslint'
7+ import type { AST } from 'vue-eslint-parser'
68
7- import * as vueUtils from 'eslint-plugin-vue/lib/utils/index.js'
89import { createLibVersionValidator } from '../utils/lib-version-parser.ts'
10+ import { defineTemplateBodyVisitor } from '../utils/vue-template-visitor.ts'
911
1012export default {
1113 meta : {
@@ -66,8 +68,8 @@ export default {
6668 'tertiary-no-background' ,
6769 ]
6870
69- return vueUtils . defineTemplateBodyVisitor ( context , {
70- 'VElement VAttribute:has(VIdentifier[name="type"])' : function ( node ) {
71+ return defineTemplateBodyVisitor ( context , {
72+ 'VElement VAttribute:has(VIdentifier[name="type"])' : function ( node : AST . VAttribute | AST . VDirective ) {
7173 if ( ! [
7274 'ncactions' ,
7375 'ncappnavigationnew' ,
@@ -85,13 +87,13 @@ export default {
8587
8688 const hasNativeType = node . parent . attributes . find ( ( attr ) => (
8789 attr . key . name === 'native-type'
88- || ( attr . key . type === 'VDirectiveKey' && attr . key . argument && attr . key . argument . name === 'native-type' ) ) )
90+ || ( attr . key . type === 'VDirectiveKey' && attr . key . argument && attr . key . argument . type === 'VIdentifier' && attr . key . argument . name === 'native-type' ) ) )
8991
9092 const isLiteral = node . value . type === 'VLiteral' && legacyTypes . includes ( node . value . value )
9193 const isExpression = node . value . type === 'VExpressionContainer'
9294 && node . value . expression . type === 'ConditionalExpression'
93- && ( legacyTypes . includes ( node . value . expression . consequent . value )
94- || legacyTypes . includes ( node . value . expression . alternate . value )
95+ && ( ( 'value' in node . value . expression . consequent && legacyTypes . includes ( node . value . expression . consequent . value . toString ( ) ) )
96+ || ( 'value' in node . value . expression . alternate && legacyTypes . includes ( node . value . expression . alternate . value . toString ( ) ) )
9597 )
9698
9799 /**
@@ -114,7 +116,7 @@ export default {
114116 }
115117 } ,
116118
117- 'VElement VAttribute:has(VIdentifier[name="native-type"])' : function ( node ) {
119+ 'VElement VAttribute:has(VIdentifier[name="native-type"])' : function ( node : AST . VAttribute | AST . VDirective ) {
118120 if ( ! [
119121 'ncbutton' ,
120122 'ncdialogbutton' ,
@@ -140,7 +142,7 @@ export default {
140142 } )
141143 } ,
142144
143- 'VElement VAttribute:has(VIdentifier[name="aria-hidden"])' : function ( node ) {
145+ 'VElement VAttribute:has(VIdentifier[name="aria-hidden"])' : function ( node : AST . VAttribute | AST . VDirective ) {
144146 if ( node . parent . parent . name . startsWith ( 'ncaction' )
145147 || node . parent . parent . name === 'ncbutton' ) {
146148 if ( ! isAriaHiddenValid ) {
@@ -155,7 +157,7 @@ export default {
155157 }
156158 } ,
157159
158- 'VElement[name="ncappcontent"] VAttribute:has(VIdentifier[name="allow-swipe-navigation"])' : function ( node ) {
160+ 'VElement[name="ncappcontent"] VAttribute:has(VIdentifier[name="allow-swipe-navigation"])' : function ( node : AST . VAttribute | AST . VDirective ) {
159161 if ( ! isDisableSwipeValid ) {
160162 context . report ( { node, messageId : 'outdatedVueLibrary' } )
161163 return
@@ -167,7 +169,7 @@ export default {
167169 } )
168170 } ,
169171
170- 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="show-user-status"])' : function ( node ) {
172+ 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="show-user-status"])' : function ( node : AST . VAttribute | AST . VDirective ) {
171173 if ( ! isDefaultBooleanFalseValid ) {
172174 context . report ( { node, messageId : 'outdatedVueLibrary' } )
173175 return
@@ -179,7 +181,7 @@ export default {
179181 } )
180182 } ,
181183
182- 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="show-user-status-compact"])' : function ( node ) {
184+ 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="show-user-status-compact"])' : function ( node : AST . VAttribute | AST . VDirective ) {
183185 if ( ! isDefaultBooleanFalseValid ) {
184186 context . report ( { node, messageId : 'outdatedVueLibrary' } )
185187 return
@@ -191,7 +193,7 @@ export default {
191193 } )
192194 } ,
193195
194- 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="allow-placeholder"])' : function ( node ) {
196+ 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="allow-placeholder"])' : function ( node : AST . VAttribute | AST . VDirective ) {
195197 if ( ! isDefaultBooleanFalseValid ) {
196198 context . report ( { node, messageId : 'outdatedVueLibrary' } )
197199 return
@@ -203,7 +205,7 @@ export default {
203205 } )
204206 } ,
205207
206- 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="formatter"])' : function ( node ) {
208+ 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="formatter"])' : function ( node : AST . VAttribute | AST . VDirective ) {
207209 if ( ! isDateTimePickerFormatValid ) {
208210 context . report ( { node, messageId : 'outdatedVueLibrary' } )
209211 return
@@ -215,7 +217,7 @@ export default {
215217 } )
216218 } ,
217219
218- 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="lang"])' : function ( node ) {
220+ 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="lang"])' : function ( node : AST . VAttribute | AST . VDirective ) {
219221 if ( ! isVue3Valid ) {
220222 // Do not throw for v8.X.X
221223 return
@@ -227,7 +229,7 @@ export default {
227229 } )
228230 } ,
229231
230- 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="range"])' : function ( node ) {
232+ 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="range"])' : function ( node : AST . VAttribute | AST . VDirective ) {
231233 if ( ! isDateTimePickerFormatValid ) {
232234 context . report ( { node, messageId : 'outdatedVueLibrary' } )
233235 return
@@ -239,7 +241,7 @@ export default {
239241 } )
240242 } ,
241243
242- 'VElement VAttribute:has(VIdentifier[name="can-close"])' : function ( node ) {
244+ 'VElement VAttribute:has(VIdentifier[name="can-close"])' : function ( node : AST . VAttribute | AST . VDirective ) {
243245 if ( ! [
244246 'ncdialog' ,
245247 'ncmodal' ,
@@ -258,7 +260,7 @@ export default {
258260 } )
259261 } ,
260262
261- 'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="close-on-click-outside"])' : function ( node ) {
263+ 'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="close-on-click-outside"])' : function ( node : AST . VAttribute | AST . VDirective ) {
262264 if ( ! isVue3Valid ) {
263265 // Do not throw for v8.X.X
264266 return
@@ -270,7 +272,7 @@ export default {
270272 } )
271273 } ,
272274
273- 'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="enable-swipe"])' : function ( node ) {
275+ 'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="enable-swipe"])' : function ( node : AST . VAttribute | AST . VDirective ) {
274276 if ( ! isDisableSwipeValid ) {
275277 context . report ( { node, messageId : 'outdatedVueLibrary' } )
276278 return
@@ -282,7 +284,7 @@ export default {
282284 } )
283285 } ,
284286
285- 'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="close-button-contained"])' : function ( node ) {
287+ 'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="close-button-contained"])' : function ( node : AST . VAttribute | AST . VDirective ) {
286288 if ( ! isCloseButtonOutsideValid ) {
287289 context . report ( { node, messageId : 'outdatedVueLibrary' } )
288290 return
@@ -294,7 +296,7 @@ export default {
294296 } )
295297 } ,
296298
297- 'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="focus-trap"])' : function ( node ) {
299+ 'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="focus-trap"])' : function ( node : AST . VAttribute | AST . VDirective ) {
298300 if ( ! isNcPopoverNoFocusTrapValid ) {
299301 context . report ( { node, messageId : 'outdatedVueLibrary' } )
300302 return
@@ -306,7 +308,7 @@ export default {
306308 } )
307309 } ,
308310
309- 'VElement[name="ncselect"] VAttribute:has(VIdentifier[name="close-on-select"])' : function ( node ) {
311+ 'VElement[name="ncselect"] VAttribute:has(VIdentifier[name="close-on-select"])' : function ( node : AST . VAttribute | AST . VDirective ) {
310312 if ( ! isNcSelectKeepOpenValid ) {
311313 context . report ( { node, messageId : 'outdatedVueLibrary' } )
312314 return
@@ -318,7 +320,7 @@ export default {
318320 } )
319321 } ,
320322
321- 'VElement[name="ncselect"] VAttribute:has(VIdentifier[name="user-select"])' : function ( node ) {
323+ 'VElement[name="ncselect"] VAttribute:has(VIdentifier[name="user-select"])' : function ( node : AST . VAttribute | AST . VDirective ) {
322324 if ( ! isNcSelectUsersValid ) {
323325 context . report ( { node, messageId : 'outdatedVueLibrary' } )
324326 return
@@ -330,7 +332,7 @@ export default {
330332 } )
331333 } ,
332334
333- 'VElement VAttribute:has(VIdentifier[name="trailing-button-icon"])' : function ( node ) {
335+ 'VElement VAttribute:has(VIdentifier[name="trailing-button-icon"])' : function ( node : AST . VAttribute | AST . VDirective ) {
334336 if ( node . parent . parent . name !== 'nctextfield' ) {
335337 return
336338 }
@@ -342,8 +344,12 @@ export default {
342344
343345 const isLiteral = node . value . type === 'VLiteral' && node . value . value === 'arrowRight'
344346
345- const isExpression = node . value . type === 'VExpressionContainer' && node . value . expression ?. type === 'ConditionalExpression'
346- && ( node . value . expression . consequent . value === 'arrowRight' || node . value . expression . alternate . value === 'arrowRight' )
347+ const isExpression = node . value . type === 'VExpressionContainer'
348+ && node . value . expression ?. type === 'ConditionalExpression'
349+ && (
350+ ( 'value' in node . value . expression . consequent && node . value . expression . consequent . value === 'arrowRight' )
351+ || ( 'value' in node . value . expression . alternate && node . value . expression . alternate . value === 'arrowRight' )
352+ )
347353
348354 /**
349355 * if it is a literal with a deprecated value -> we migrate
@@ -357,24 +363,25 @@ export default {
357363 if ( node . key . type === 'VIdentifier' ) {
358364 return fixer . replaceTextRange ( node . value . range , '"arrowEnd"' )
359365 } else if ( node . key . type === 'VDirectiveKey' ) {
360- return ( node . value . expression . consequent . value === 'arrowRight' )
361- ? fixer . replaceTextRange ( node . value . expression . consequent . range , '\'arrowEnd\'' )
362- : fixer . replaceTextRange ( node . value . expression . alternate . range , '\'arrowEnd\'' )
366+ const expression = ( node . value as AST . VExpressionContainer ) . expression as AST . ESLintConditionalExpression
367+ return ( expression . consequent as AST . ESLintLiteral ) . value === 'arrowRight'
368+ ? fixer . replaceTextRange ( expression . consequent . range , '\'arrowEnd\'' )
369+ : fixer . replaceTextRange ( expression . alternate . range , '\'arrowEnd\'' )
363370 }
364371 } ,
365372 } )
366373 }
367374 } ,
368375
369- 'VElement[name="ncsettingssection"] VAttribute:has(VIdentifier[name="limit-width"])' : function ( node ) {
376+ 'VElement[name="ncsettingssection"] VAttribute:has(VIdentifier[name="limit-width"])' : function ( node : AST . VAttribute | AST . VDirective ) {
370377 // This was deprecated in 8.13.0 (Nextcloud 30+), before first supported version by plugin
371378 context . report ( {
372379 node,
373380 messageId : 'removeLimitWidth' ,
374381 } )
375382 } ,
376383
377- 'VElement VAttribute:has(VIdentifier[name="exact"])' : function ( node ) {
384+ 'VElement VAttribute:has(VIdentifier[name="exact"])' : function ( node : AST . VAttribute | AST . VDirective ) {
378385 if ( ! [
379386 'ncactionrouter' ,
380387 'ncappnavigationitem' ,
@@ -396,7 +403,7 @@ export default {
396403 } )
397404 } ,
398405
399- 'VElement VAttribute:has(VIdentifier[name="checked"])' : function ( node ) {
406+ 'VElement VAttribute:has(VIdentifier[name="checked"])' : function ( node : AST . VAttribute | AST . VDirective ) {
400407 if ( ! [
401408 'ncactioncheckbox' ,
402409 'ncactionradio' ,
@@ -429,7 +436,7 @@ export default {
429436 } )
430437 } ,
431438
432- 'VElement VAttribute:has(VIdentifier[name="value"])' : function ( node ) {
439+ 'VElement VAttribute:has(VIdentifier[name="value"])' : function ( node : AST . VAttribute | AST . VDirective ) {
433440 if ( ! [
434441 'ncactioninput' ,
435442 'ncactiontexteditable' ,
0 commit comments