@nextcloud/eslint-config 9.0.0-rc.1 → 9.0.0-rc.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/CHANGELOG.md +114 -42
  2. package/README.md +85 -31
  3. package/dist/configs/codeStyle.d.ts +1 -1
  4. package/dist/configs/codeStyle.js +27 -29
  5. package/dist/configs/documentation.js +71 -38
  6. package/dist/configs/filesystem.js +10 -3
  7. package/dist/configs/imports.d.ts +1 -1
  8. package/dist/configs/imports.js +15 -18
  9. package/dist/configs/javascript.js +2 -2
  10. package/dist/configs/typescript.js +8 -1
  11. package/dist/configs/vue.js +36 -12
  12. package/dist/configs/vue2.js +2 -2
  13. package/dist/configs/vue3.js +24 -0
  14. package/dist/globs.d.ts +2 -0
  15. package/dist/globs.js +6 -2
  16. package/dist/index.d.ts +11 -7
  17. package/dist/index.js +4 -1
  18. package/dist/plugins/import-extensions/index.d.ts +15 -0
  19. package/dist/plugins/{nextcloud-vue → import-extensions}/index.js +4 -0
  20. package/dist/plugins/import-extensions/rules/ban-inline-type-imports.d.ts +7 -0
  21. package/dist/plugins/import-extensions/rules/ban-inline-type-imports.js +176 -0
  22. package/dist/plugins/import-extensions/rules/extensions.d.ts +11 -0
  23. package/dist/plugins/import-extensions/rules/extensions.js +143 -0
  24. package/dist/plugins/import-extensions/rules/index.d.ts +8 -0
  25. package/dist/plugins/import-extensions/rules/index.js +10 -0
  26. package/dist/plugins/nextcloud/rules/index.js +16 -4
  27. package/dist/plugins/nextcloud/rules/l10n-enforce-ellipsis.js +44 -0
  28. package/dist/plugins/{l10n/rules/non-breaking-space.js → nextcloud/rules/l10n-non-breaking-space.js} +11 -2
  29. package/dist/plugins/nextcloud/rules/{no-deprecations.js → no-deprecated-globals.js} +16 -3
  30. package/dist/plugins/nextcloud/rules/no-deprecated-library-exports.js +89 -0
  31. package/dist/plugins/nextcloud/rules/no-deprecated-library-props.d.ts +41 -0
  32. package/dist/plugins/nextcloud/rules/no-deprecated-library-props.js +429 -0
  33. package/dist/plugins/nextcloud/rules/{no-removed-apis.js → no-removed-globals.js} +53 -2
  34. package/dist/plugins/nextcloud/utils/lib-version-parser.d.ts +18 -0
  35. package/dist/plugins/nextcloud/utils/lib-version-parser.js +49 -0
  36. package/dist/plugins/nextcloud/utils/vue-template-visitor.d.ts +26 -0
  37. package/dist/plugins/nextcloud/utils/vue-template-visitor.js +38 -0
  38. package/dist/utils.d.ts +1 -12
  39. package/package.json +31 -25
  40. package/dist/plugins/l10n/index.d.ts +0 -10
  41. package/dist/plugins/l10n/index.js +0 -17
  42. package/dist/plugins/l10n/rules/enforce-ellipsis.js +0 -32
  43. package/dist/plugins/nextcloud-vue/index.d.ts +0 -8
  44. package/dist/plugins/nextcloud-vue/rules/index.d.ts +0 -6
  45. package/dist/plugins/nextcloud-vue/rules/index.js +0 -4
  46. package/dist/plugins/nextcloud-vue/rules/no-deprecated-exports.js +0 -44
  47. package/dist/plugins/nextcloud-vue/utils/lib-version-parser.d.ts +0 -33
  48. package/dist/plugins/nextcloud-vue/utils/lib-version-parser.js +0 -94
  49. /package/dist/plugins/{l10n/rules/enforce-ellipsis.d.ts → nextcloud/rules/l10n-enforce-ellipsis.d.ts} +0 -0
  50. /package/dist/plugins/{l10n/rules/non-breaking-space.d.ts → nextcloud/rules/l10n-non-breaking-space.d.ts} +0 -0
  51. /package/dist/plugins/{nextcloud-vue/rules/no-deprecated-exports.d.ts → nextcloud/rules/no-deprecated-globals.d.ts} +0 -0
  52. /package/dist/plugins/nextcloud/rules/{no-deprecations.d.ts → no-deprecated-library-exports.d.ts} +0 -0
  53. /package/dist/plugins/nextcloud/rules/{no-removed-apis.d.ts → no-removed-globals.d.ts} +0 -0
  54. /package/dist/plugins/nextcloud/utils/{version-parser.d.ts → app-version-parser.d.ts} +0 -0
  55. /package/dist/plugins/nextcloud/utils/{version-parser.js → app-version-parser.js} +0 -0
@@ -0,0 +1,429 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { createLibVersionValidator } from "../utils/lib-version-parser.js";
6
+ import { defineTemplateBodyVisitor } from "../utils/vue-template-visitor.js";
7
+ export default {
8
+ meta: {
9
+ docs: {
10
+ description: 'Deprecated `@nextcloud/vue` properties',
11
+ },
12
+ type: 'problem',
13
+ fixable: 'code',
14
+ messages: {
15
+ outdatedVueLibrary: 'Installed @nextcloud/vue library is outdated and does not support all reported errors. Install latest compatible version',
16
+ useTypeInstead: 'Using `native-type` for button variant is deprecated - use `type` instead.',
17
+ useVariantInstead: 'Using `type` for button variant is deprecated - use `variant` instead.',
18
+ useDisableSwipeForNavInstead: 'Using `allow-swipe-navigation` is deprecated - use `disable-swipe` instead',
19
+ useHideStatusInstead: 'Using `show-user-status` is deprecated - use `hide-status` instead',
20
+ useVerboseStatusInstead: 'Using `show-user-status-compact` is deprecated - use `verbose-status` instead',
21
+ useNoPlaceholderInstead: 'Using `allow-placeholder` is deprecated - use `no-placeholder` instead',
22
+ useFormatInstead: 'Using `formatter` is deprecated - use `format` instead',
23
+ useLocaleInstead: 'Using `lang` is deprecated - use `locale` instead',
24
+ useTypeDateRangeInstead: 'Using `range` is deprecated - use `type` with `date-range` or `datetime-range` instead',
25
+ useNoCloseInstead: 'Using `can-close` is deprecated - use `no-close` instead',
26
+ useNoCloseOnClickOutsideInstead: 'Using `close-on-click-outside` is deprecated - use `no-close-on-click-outside` instead',
27
+ useDisableSwipeForModalInstead: 'Using `enable-swipe` is deprecated - use `disable-swipe` instead',
28
+ useNoFocusTrapInstead: 'Using `focus-trap` is deprecated - use `no-focus-trap` instead',
29
+ useKeepOpenInstead: 'Using `close-on-select` is deprecated - use `keep-open` instead',
30
+ useNcSelectUsersInstead: 'Using `user-select` is deprecated - use `NcSelectUsers` component instead',
31
+ useArrowEndInstead: 'Using `arrow-right` is deprecated - use `arrow-end` instead',
32
+ removeAriaHidden: 'Using `aria-hidden` is deprecated - remove prop from components, otherwise root element will inherit incorrect attribute.',
33
+ removeLimitWidth: 'Using `limit-width` is deprecated - remove prop from components, otherwise root element will inherit incorrect attribute.',
34
+ removeExact: 'Using `exact` is deprecated - consult Vue Router documentation for alternatives.',
35
+ useCloseButtonOutsideInstead: 'Using `close-button-contained` is deprecated - use `close-button-outside` instead',
36
+ useModelValueInsteadChecked: 'Using `checked` is deprecated - use `model-value` or `v-model` instead',
37
+ useModelValueInsteadValue: 'Using `value` is deprecated - use `model-value` or `v-model` instead',
38
+ },
39
+ },
40
+ create(context) {
41
+ const versionSatisfies = createLibVersionValidator(context);
42
+ const isVue3Valid = versionSatisfies('9.0.0'); // #6651
43
+ const isAriaHiddenValid = versionSatisfies('8.2.0'); // #4835
44
+ const isModelValueValid = versionSatisfies('8.20.0'); // #6172
45
+ const isDisableSwipeValid = versionSatisfies('8.23.0'); // #6452
46
+ const isVariantTypeValid = versionSatisfies('8.24.0'); // #6472
47
+ const isDefaultBooleanFalseValid = versionSatisfies('8.24.0'); // #6656
48
+ const isDateTimePickerFormatValid = versionSatisfies('8.25.0'); // #6738
49
+ const isNcSelectKeepOpenValid = versionSatisfies('8.25.0'); // #6791
50
+ const isNcPopoverNoFocusTrapValid = versionSatisfies('8.26.0'); // #6808
51
+ const isNcSelectUsersValid = versionSatisfies('8.27.1'); // #7032
52
+ const isNcTextFieldArrowEndValid = versionSatisfies('8.28.0'); // #7002
53
+ const isCloseButtonOutsideValid = versionSatisfies('8.32.0'); // #7553
54
+ const legacyTypes = [
55
+ 'primary',
56
+ 'error',
57
+ 'warning',
58
+ 'success',
59
+ 'secondary',
60
+ 'tertiary',
61
+ 'tertiary-no-background',
62
+ ];
63
+ return defineTemplateBodyVisitor(context, {
64
+ 'VElement VAttribute:has(VIdentifier[name="type"])': function (node) {
65
+ if (![
66
+ 'ncactions',
67
+ 'ncappnavigationnew',
68
+ 'ncbutton',
69
+ 'ncchip',
70
+ 'ncdialogbutton',
71
+ ].includes(node.parent.parent.name)) {
72
+ return;
73
+ }
74
+ if (!isVariantTypeValid) {
75
+ context.report({ node, messageId: 'outdatedVueLibrary' });
76
+ return;
77
+ }
78
+ const hasNativeType = node.parent.attributes.find((attr) => (attr.key.name === 'native-type'
79
+ || (attr.key.type === 'VDirectiveKey' && attr.key.argument && attr.key.argument.type === 'VIdentifier' && attr.key.argument.name === 'native-type')));
80
+ const isLiteral = node.value.type === 'VLiteral' && legacyTypes.includes(node.value.value);
81
+ const isExpression = node.value.type === 'VExpressionContainer'
82
+ && node.value.expression.type === 'ConditionalExpression'
83
+ && (('value' in node.value.expression.consequent && legacyTypes.includes(node.value.expression.consequent.value.toString()))
84
+ || ('value' in node.value.expression.alternate && legacyTypes.includes(node.value.expression.alternate.value.toString())));
85
+ /**
86
+ * if it is a literal with a deprecated value -> we migrate
87
+ * if it is an expression with a defined deprecated value -> we migrate
88
+ * if it is unknown value (e.g. computed), but there is also the `native-type` we assume it is legacy and we migrate
89
+ */
90
+ if (isLiteral || isExpression || hasNativeType) {
91
+ context.report({
92
+ node,
93
+ messageId: 'useVariantInstead',
94
+ fix: (fixer) => {
95
+ if (node.key.type === 'VIdentifier') {
96
+ return fixer.replaceTextRange(node.key.range, 'variant');
97
+ }
98
+ else if (node.key.type === 'VDirectiveKey') {
99
+ return fixer.replaceTextRange(node.key.argument.range, 'variant');
100
+ }
101
+ },
102
+ });
103
+ }
104
+ },
105
+ 'VElement VAttribute:has(VIdentifier[name="native-type"])': function (node) {
106
+ if (![
107
+ 'ncbutton',
108
+ 'ncdialogbutton',
109
+ ].includes(node.parent.parent.name)) {
110
+ return;
111
+ }
112
+ if (!isVariantTypeValid) {
113
+ context.report({ node, messageId: 'outdatedVueLibrary' });
114
+ return;
115
+ }
116
+ context.report({
117
+ node,
118
+ messageId: 'useTypeInstead',
119
+ fix: (fixer) => {
120
+ if (node.key.type === 'VIdentifier') {
121
+ return fixer.replaceTextRange(node.key.range, 'type');
122
+ }
123
+ else if (node.key.type === 'VDirectiveKey') {
124
+ return fixer.replaceTextRange(node.key.argument.range, 'type');
125
+ }
126
+ },
127
+ });
128
+ },
129
+ 'VElement VAttribute:has(VIdentifier[name="aria-hidden"])': function (node) {
130
+ if (node.parent.parent.name.startsWith('ncaction')
131
+ || node.parent.parent.name === 'ncbutton') {
132
+ if (!isAriaHiddenValid) {
133
+ context.report({ node, messageId: 'outdatedVueLibrary' });
134
+ return;
135
+ }
136
+ context.report({
137
+ node,
138
+ messageId: 'removeAriaHidden',
139
+ });
140
+ }
141
+ },
142
+ 'VElement[name="ncappcontent"] VAttribute:has(VIdentifier[name="allow-swipe-navigation"])': function (node) {
143
+ if (!isDisableSwipeValid) {
144
+ context.report({ node, messageId: 'outdatedVueLibrary' });
145
+ return;
146
+ }
147
+ context.report({
148
+ node,
149
+ messageId: 'useDisableSwipeForNavInstead',
150
+ });
151
+ },
152
+ 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="show-user-status"])': function (node) {
153
+ if (!isDefaultBooleanFalseValid) {
154
+ context.report({ node, messageId: 'outdatedVueLibrary' });
155
+ return;
156
+ }
157
+ context.report({
158
+ node,
159
+ messageId: 'useHideStatusInstead',
160
+ });
161
+ },
162
+ 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="show-user-status-compact"])': function (node) {
163
+ if (!isDefaultBooleanFalseValid) {
164
+ context.report({ node, messageId: 'outdatedVueLibrary' });
165
+ return;
166
+ }
167
+ context.report({
168
+ node,
169
+ messageId: 'useVerboseStatusInstead',
170
+ });
171
+ },
172
+ 'VElement[name="ncavatar"] VAttribute:has(VIdentifier[name="allow-placeholder"])': function (node) {
173
+ if (!isDefaultBooleanFalseValid) {
174
+ context.report({ node, messageId: 'outdatedVueLibrary' });
175
+ return;
176
+ }
177
+ context.report({
178
+ node,
179
+ messageId: 'useNoPlaceholderInstead',
180
+ });
181
+ },
182
+ 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="formatter"])': function (node) {
183
+ if (!isDateTimePickerFormatValid) {
184
+ context.report({ node, messageId: 'outdatedVueLibrary' });
185
+ return;
186
+ }
187
+ context.report({
188
+ node,
189
+ messageId: 'useFormatInstead',
190
+ });
191
+ },
192
+ 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="lang"])': function (node) {
193
+ if (!isVue3Valid) {
194
+ // Do not throw for v8.X.X
195
+ return;
196
+ }
197
+ context.report({
198
+ node,
199
+ messageId: 'useLocaleInstead',
200
+ });
201
+ },
202
+ 'VElement[name="ncdatetimepicker"] VAttribute:has(VIdentifier[name="range"])': function (node) {
203
+ if (!isDateTimePickerFormatValid) {
204
+ context.report({ node, messageId: 'outdatedVueLibrary' });
205
+ return;
206
+ }
207
+ context.report({
208
+ node,
209
+ messageId: 'useTypeDateRangeInstead',
210
+ });
211
+ },
212
+ 'VElement VAttribute:has(VIdentifier[name="can-close"])': function (node) {
213
+ if (![
214
+ 'ncdialog',
215
+ 'ncmodal',
216
+ ].includes(node.parent.parent.name)) {
217
+ return;
218
+ }
219
+ if (!isDefaultBooleanFalseValid) {
220
+ context.report({ node, messageId: 'outdatedVueLibrary' });
221
+ return;
222
+ }
223
+ context.report({
224
+ node,
225
+ messageId: 'useNoCloseInstead',
226
+ });
227
+ },
228
+ 'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="close-on-click-outside"])': function (node) {
229
+ if (!isVue3Valid) {
230
+ // Do not throw for v8.X.X
231
+ return;
232
+ }
233
+ context.report({
234
+ node,
235
+ messageId: 'useNoCloseOnClickOutsideInstead',
236
+ });
237
+ },
238
+ 'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="enable-swipe"])': function (node) {
239
+ if (!isDisableSwipeValid) {
240
+ context.report({ node, messageId: 'outdatedVueLibrary' });
241
+ return;
242
+ }
243
+ context.report({
244
+ node,
245
+ messageId: 'useDisableSwipeForModalInstead',
246
+ });
247
+ },
248
+ 'VElement[name="ncmodal"] VAttribute:has(VIdentifier[name="close-button-contained"])': function (node) {
249
+ if (!isCloseButtonOutsideValid) {
250
+ context.report({ node, messageId: 'outdatedVueLibrary' });
251
+ return;
252
+ }
253
+ context.report({
254
+ node,
255
+ messageId: 'useCloseButtonOutsideInstead',
256
+ });
257
+ },
258
+ 'VElement[name="ncpopover"] VAttribute:has(VIdentifier[name="focus-trap"])': function (node) {
259
+ if (!isNcPopoverNoFocusTrapValid) {
260
+ context.report({ node, messageId: 'outdatedVueLibrary' });
261
+ return;
262
+ }
263
+ context.report({
264
+ node,
265
+ messageId: 'useNoFocusTrapInstead',
266
+ });
267
+ },
268
+ 'VElement[name="ncselect"] VAttribute:has(VIdentifier[name="close-on-select"])': function (node) {
269
+ if (!isNcSelectKeepOpenValid) {
270
+ context.report({ node, messageId: 'outdatedVueLibrary' });
271
+ return;
272
+ }
273
+ context.report({
274
+ node,
275
+ messageId: 'useKeepOpenInstead',
276
+ });
277
+ },
278
+ 'VElement[name="ncselect"] VAttribute:has(VIdentifier[name="user-select"])': function (node) {
279
+ if (!isNcSelectUsersValid) {
280
+ context.report({ node, messageId: 'outdatedVueLibrary' });
281
+ return;
282
+ }
283
+ context.report({
284
+ node,
285
+ messageId: 'useNcSelectUsersInstead',
286
+ });
287
+ },
288
+ 'VElement VAttribute:has(VIdentifier[name="trailing-button-icon"])': function (node) {
289
+ if (node.parent.parent.name !== 'nctextfield') {
290
+ return;
291
+ }
292
+ if (!isNcTextFieldArrowEndValid) {
293
+ context.report({ node, messageId: 'outdatedVueLibrary' });
294
+ return;
295
+ }
296
+ const isLiteral = node.value.type === 'VLiteral' && node.value.value === 'arrowRight';
297
+ const isExpression = node.value.type === 'VExpressionContainer'
298
+ && node.value.expression?.type === 'ConditionalExpression'
299
+ && (('value' in node.value.expression.consequent && node.value.expression.consequent.value === 'arrowRight')
300
+ || ('value' in node.value.expression.alternate && node.value.expression.alternate.value === 'arrowRight'));
301
+ /**
302
+ * if it is a literal with a deprecated value -> we migrate
303
+ * if it is an expression with a defined deprecated value -> we migrate
304
+ */
305
+ if (isLiteral || isExpression) {
306
+ context.report({
307
+ node,
308
+ messageId: 'useArrowEndInstead',
309
+ fix: (fixer) => {
310
+ if (node.key.type === 'VIdentifier') {
311
+ return fixer.replaceTextRange(node.value.range, '"arrowEnd"');
312
+ }
313
+ else if (node.key.type === 'VDirectiveKey') {
314
+ const expression = node.value.expression;
315
+ return expression.consequent.value === 'arrowRight'
316
+ ? fixer.replaceTextRange(expression.consequent.range, '\'arrowEnd\'')
317
+ : fixer.replaceTextRange(expression.alternate.range, '\'arrowEnd\'');
318
+ }
319
+ },
320
+ });
321
+ }
322
+ },
323
+ 'VElement[name="ncsettingssection"] VAttribute:has(VIdentifier[name="limit-width"])': function (node) {
324
+ // This was deprecated in 8.13.0 (Nextcloud 30+), before first supported version by plugin
325
+ context.report({
326
+ node,
327
+ messageId: 'removeLimitWidth',
328
+ });
329
+ },
330
+ 'VElement VAttribute:has(VIdentifier[name="exact"])': function (node) {
331
+ if (![
332
+ 'ncactionrouter',
333
+ 'ncappnavigationitem',
334
+ 'ncbreadcrumb',
335
+ 'ncbutton',
336
+ 'nclistitem',
337
+ ].includes(node.parent.parent.name)) {
338
+ return;
339
+ }
340
+ if (!isVue3Valid) {
341
+ // Do not throw for v8.X.X
342
+ return;
343
+ }
344
+ context.report({
345
+ node,
346
+ messageId: 'removeExact',
347
+ });
348
+ },
349
+ 'VElement VAttribute:has(VIdentifier[name="checked"])': function (node) {
350
+ if (![
351
+ 'ncactioncheckbox',
352
+ 'ncactionradio',
353
+ 'nccheckboxradioswitch',
354
+ ].includes(node.parent.parent.name)) {
355
+ return;
356
+ }
357
+ if (!isModelValueValid) {
358
+ context.report({ node, messageId: 'outdatedVueLibrary' });
359
+ return;
360
+ }
361
+ context.report({
362
+ node,
363
+ messageId: 'useModelValueInsteadChecked',
364
+ fix: (fixer) => {
365
+ if (node.key.type === 'VIdentifier') {
366
+ return fixer.replaceTextRange(node.key.range, 'model-value');
367
+ }
368
+ else if (node.key.type === 'VDirectiveKey') {
369
+ if (node.key.name.name === 'model') {
370
+ return fixer.replaceTextRange(node.key.range, 'v-model');
371
+ }
372
+ else if (node.key.modifiers.some((m) => m.name === 'sync')) {
373
+ return fixer.replaceTextRange(node.key.range, 'v-model');
374
+ }
375
+ else {
376
+ return fixer.replaceTextRange(node.key.argument.range, 'model-value');
377
+ }
378
+ }
379
+ },
380
+ });
381
+ },
382
+ 'VElement VAttribute:has(VIdentifier[name="value"])': function (node) {
383
+ if (![
384
+ 'ncactioninput',
385
+ 'ncactiontexteditable',
386
+ 'nccolorpicker',
387
+ 'ncdatetimepicker',
388
+ 'ncdatetimepickernative',
389
+ 'ncinputfield',
390
+ 'nctextfield',
391
+ 'ncpasswordfield',
392
+ 'ncrichcontenteditable',
393
+ 'ncselecttags',
394
+ 'ncselect',
395
+ 'ncsettingsinputtext',
396
+ 'ncsettingsselectgroup',
397
+ 'nctextarea',
398
+ 'nctimezonepicker',
399
+ ].includes(node.parent.parent.name)) {
400
+ return;
401
+ }
402
+ if (!isModelValueValid) {
403
+ context.report({ node, messageId: 'outdatedVueLibrary' });
404
+ return;
405
+ }
406
+ context.report({
407
+ node,
408
+ messageId: 'useModelValueInsteadValue',
409
+ fix: (fixer) => {
410
+ if (node.key.type === 'VIdentifier') {
411
+ return fixer.replaceTextRange(node.key.range, 'model-value');
412
+ }
413
+ else if (node.key.type === 'VDirectiveKey') {
414
+ if (node.key.name.name === 'model') {
415
+ return fixer.replaceTextRange(node.key.range, 'v-model');
416
+ }
417
+ else if (node.key.modifiers.some((m) => m.name === 'sync')) {
418
+ return fixer.replaceTextRange(node.key.range, 'v-model');
419
+ }
420
+ else {
421
+ return fixer.replaceTextRange(node.key.argument.range, 'model-value');
422
+ }
423
+ }
424
+ },
425
+ });
426
+ },
427
+ });
428
+ },
429
+ };
@@ -1,4 +1,8 @@
1
- import { createVersionValidator } from "../utils/version-parser.js";
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { createVersionValidator } from "../utils/app-version-parser.js";
2
6
  // ------------------------------------------------------------------------------
3
7
  // Rule Definition
4
8
  // ------------------------------------------------------------------------------
@@ -11,14 +15,26 @@ const global = {
11
15
  getURLParameter: '19.0.0',
12
16
  humanFileSize: '19.0.0',
13
17
  marked: '19.0.0',
18
+ md5: '32.0.0',
14
19
  relative_modified_date: '19.0.0',
15
20
  };
16
21
  const oc = {
22
+ AppConfig: '33.0.0',
23
+ SystemTags: '33.0.0',
17
24
  getScrollBarWidth: '15.0.0',
18
25
  addTranslations: '26.0.0',
19
26
  appSettings: '28.0.0',
27
+ fileIsBlacklisted: '33.0.0',
28
+ get: '33.0.0',
29
+ getHost: '33.0.0',
30
+ getHostName: '33.0.0',
31
+ getPort: '33.0.0',
32
+ getProtocol: '33.0.0',
20
33
  loadScript: '28.0.0',
21
34
  loadStyle: '28.0.0',
35
+ redirect: '33.0.0',
36
+ reload: '33.0.0',
37
+ set: '33.0.0',
22
38
  };
23
39
  const ocNested = {
24
40
  AppConfig: {
@@ -32,10 +48,25 @@ const ocNested = {
32
48
  scaleFixForIE8: '15.0.0',
33
49
  isIE8: '15.0.0',
34
50
  },
51
+ Settings: {
52
+ UserSettings: '33.0.0',
53
+ },
35
54
  };
36
55
  const oca = {
37
56
  // ref: https://github.com/nextcloud/server/commit/6eced42b7a40f5b0ea0489244583219d0ee2e7af
38
57
  Search: '20.0.0',
58
+ FilesSharingDrop: '31.0.0',
59
+ };
60
+ const ocaNested = {
61
+ Core: {
62
+ ProfileSections: '33.0.0',
63
+ },
64
+ Files: {
65
+ Sidebar: '33.0.0',
66
+ },
67
+ Sharing: {
68
+ ExternalLinkActions: '33.0.0',
69
+ },
39
70
  };
40
71
  // TODO: handle OC.x.y.z like OC.Share.ShareConfigModel.areAvatarsEnabled()
41
72
  // ref https://github.com/nextcloud/server/issues/11045
@@ -43,7 +74,6 @@ const rule = {
43
74
  meta: {
44
75
  docs: {
45
76
  description: 'Removed Nextcloud APIs',
46
- category: 'Nextcloud',
47
77
  recommended: true,
48
78
  },
49
79
  // fixable: "code" or "whitespace"
@@ -83,6 +113,27 @@ const rule = {
83
113
  message: `The property or function OCA.${node.property.name} was removed in Nextcloud ${oca[node.property.name]}`,
84
114
  });
85
115
  }
116
+ // OCA.x.y
117
+ if (node.object.type === 'MemberExpression'
118
+ && 'name' in node.object.object
119
+ && node.object.object.name === 'OCA'
120
+ && 'name' in node.object.property
121
+ && ocaNested[node.object.property.name]
122
+ && 'name' in node.property
123
+ && ocaNested[node.object.property.name][node.property.name]) {
124
+ const version = ocaNested[node.object.property.name][node.property.name];
125
+ if (checkTargetVersion(version)) {
126
+ const prop = [
127
+ 'OC',
128
+ node.object.property.name,
129
+ node.property.name,
130
+ ].join('.');
131
+ context.report({
132
+ node,
133
+ message: `The property or function ${prop} was removed in Nextcloud ${version}`,
134
+ });
135
+ }
136
+ }
86
137
  // OC.x
87
138
  if ('name' in node.object
88
139
  && 'name' in node.property
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Create a callback that takes a version number and checks if the version
3
+ * is valid compared to configured version / detected version.
4
+ *
5
+ * @param options Options
6
+ * @param options.cwd The current working directory
7
+ * @param options.physicalFilename The real filename where ESLint is linting currently
8
+ * @param importResolve Optional custom import resolver function (for testing purposes)
9
+ * @return Function validator, return a boolean whether current version satisfies minimal required for the rule
10
+ */
11
+ export declare function createLibVersionValidator({ cwd, physicalFilename }: {
12
+ cwd: any;
13
+ physicalFilename: any;
14
+ }, importResolve?: (specifier: string, parent?: string | import("node:url").URL) => string): ((version: string) => boolean);
15
+ /**
16
+ * Clear the module cache
17
+ */
18
+ export declare function clearCache(): void;
@@ -0,0 +1,49 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { readFileSync } from 'node:fs';
6
+ import { isAbsolute, join, resolve } from 'node:path';
7
+ import { fileURLToPath, pathToFileURL } from 'node:url';
8
+ import { gte } from 'semver';
9
+ /**
10
+ * Cached map of paths: Reco <path_to_package.json, validator>
11
+ */
12
+ const cachedMap = new Map();
13
+ /**
14
+ * Create a callback that takes a version number and checks if the version
15
+ * is valid compared to configured version / detected version.
16
+ *
17
+ * @param options Options
18
+ * @param options.cwd The current working directory
19
+ * @param options.physicalFilename The real filename where ESLint is linting currently
20
+ * @param importResolve Optional custom import resolver function (for testing purposes)
21
+ * @return Function validator, return a boolean whether current version satisfies minimal required for the rule
22
+ */
23
+ export function createLibVersionValidator({ cwd, physicalFilename }, importResolve = import.meta.resolve) {
24
+ // Try to find package.json of the nextcloud-vue package
25
+ const sourceFile = isAbsolute(physicalFilename)
26
+ ? resolve(physicalFilename)
27
+ : resolve(cwd, physicalFilename);
28
+ let packageJsonDir;
29
+ try {
30
+ const modulePath = fileURLToPath(importResolve('@nextcloud/vue', pathToFileURL(sourceFile)));
31
+ const idx = modulePath.lastIndexOf('/dist/');
32
+ packageJsonDir = modulePath.substring(0, idx);
33
+ }
34
+ catch {
35
+ return () => false;
36
+ }
37
+ if (!cachedMap[packageJsonDir]) {
38
+ const json = JSON.parse(readFileSync(join(packageJsonDir, 'package.json'), 'utf-8'));
39
+ const libVersion = json.version;
40
+ cachedMap[packageJsonDir] = (version) => gte(libVersion, version);
41
+ }
42
+ return cachedMap[packageJsonDir];
43
+ }
44
+ /**
45
+ * Clear the module cache
46
+ */
47
+ export function clearCache() {
48
+ cachedMap.clear();
49
+ }
@@ -0,0 +1,26 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import type { Rule } from 'eslint';
6
+ type TemplateVisitor = {
7
+ [key: string]: (...args: unknown[]) => void;
8
+ };
9
+ type TemplateVisitorOptions = {
10
+ templateBodyTriggerSelector: 'Program' | 'Program:exit';
11
+ };
12
+ /**
13
+ * Register the given visitor to parser services.
14
+ * If the parser service of `vue-eslint-parser` was not found,
15
+ * this generates a warning.
16
+ *
17
+ * @param context - The rule context to use parser services.
18
+ * @param templateBodyVisitor - The visitor to traverse the template body.
19
+ * @param scriptVisitor - The visitor to traverse the script.
20
+ * @param options - The options.
21
+ *
22
+ * @return The merged visitor.
23
+ * @see https://github.com/vuejs/eslint-plugin-vue/blob/745fd4e1f3719c3a2f93bd3531da5e886c16f008/lib/utils/index.js#L2281-L2315
24
+ */
25
+ export declare function defineTemplateBodyVisitor(context: Rule.RuleContext, templateBodyVisitor: TemplateVisitor, scriptVisitor?: Rule.RuleListener, options?: TemplateVisitorOptions): Rule.RuleListener;
26
+ export {};
@@ -0,0 +1,38 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2026 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import { extname } from 'node:path';
6
+ /**
7
+ * Register the given visitor to parser services.
8
+ * If the parser service of `vue-eslint-parser` was not found,
9
+ * this generates a warning.
10
+ *
11
+ * @param context - The rule context to use parser services.
12
+ * @param templateBodyVisitor - The visitor to traverse the template body.
13
+ * @param scriptVisitor - The visitor to traverse the script.
14
+ * @param options - The options.
15
+ *
16
+ * @return The merged visitor.
17
+ * @see https://github.com/vuejs/eslint-plugin-vue/blob/745fd4e1f3719c3a2f93bd3531da5e886c16f008/lib/utils/index.js#L2281-L2315
18
+ */
19
+ export function defineTemplateBodyVisitor(context, templateBodyVisitor, scriptVisitor, options) {
20
+ const sourceCode = context.sourceCode;
21
+ if (!sourceCode.parserServices?.defineTemplateBodyVisitor) {
22
+ const filename = context.filename;
23
+ if (extname(filename) === '.vue') {
24
+ context.report({
25
+ loc: {
26
+ line: 1,
27
+ column: 0,
28
+ },
29
+ message: 'Use the latest vue-eslint-parser. See also https://eslint.vuejs.org/user-guide/#what-is-the-use-the-latest-vue-eslint-parser-error.',
30
+ });
31
+ }
32
+ else {
33
+ return scriptVisitor ?? {};
34
+ }
35
+ }
36
+ return sourceCode.parserServices
37
+ .defineTemplateBodyVisitor(templateBodyVisitor, scriptVisitor, options);
38
+ }