@nextcloud/eslint-config 9.0.0-rc.0 → 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 (67) hide show
  1. package/CHANGELOG.md +133 -44
  2. package/README.md +139 -33
  3. package/dist/configs/codeStyle.d.ts +13 -0
  4. package/dist/configs/codeStyle.js +176 -0
  5. package/dist/configs/documentation.d.ts +12 -0
  6. package/dist/configs/documentation.js +117 -0
  7. package/dist/configs/filesystem.d.ts +9 -0
  8. package/dist/configs/filesystem.js +25 -0
  9. package/dist/configs/imports.d.ts +12 -0
  10. package/dist/configs/imports.js +111 -0
  11. package/dist/configs/javascript.d.ts +14 -0
  12. package/dist/configs/javascript.js +118 -0
  13. package/dist/configs/json.d.ts +9 -0
  14. package/dist/configs/json.js +45 -0
  15. package/dist/configs/node.d.ts +10 -0
  16. package/dist/configs/node.js +24 -0
  17. package/dist/configs/typescript.d.ts +12 -0
  18. package/dist/configs/typescript.js +60 -0
  19. package/dist/configs/vue.d.ts +12 -0
  20. package/dist/configs/vue.js +158 -0
  21. package/dist/configs/vue2.d.ts +12 -0
  22. package/dist/configs/vue2.js +30 -0
  23. package/dist/configs/vue3.d.ts +12 -0
  24. package/dist/configs/vue3.js +48 -0
  25. package/dist/globs.d.ts +20 -0
  26. package/dist/globs.js +41 -0
  27. package/dist/index.d.ts +31 -31
  28. package/dist/index.js +81 -0
  29. package/dist/plugins/import-extensions/index.d.ts +15 -0
  30. package/dist/plugins/import-extensions/index.js +13 -0
  31. package/dist/plugins/import-extensions/rules/ban-inline-type-imports.d.ts +7 -0
  32. package/dist/plugins/import-extensions/rules/ban-inline-type-imports.js +176 -0
  33. package/dist/plugins/import-extensions/rules/extensions.d.ts +11 -0
  34. package/dist/plugins/import-extensions/rules/extensions.js +143 -0
  35. package/dist/plugins/import-extensions/rules/index.d.ts +8 -0
  36. package/dist/plugins/import-extensions/rules/index.js +10 -0
  37. package/dist/plugins/nextcloud/index.d.ts +7 -0
  38. package/dist/plugins/nextcloud/index.js +9 -0
  39. package/dist/plugins/nextcloud/rules/index.d.ts +6 -0
  40. package/dist/plugins/nextcloud/rules/index.js +18 -0
  41. package/dist/plugins/nextcloud/rules/l10n-enforce-ellipsis.d.ts +7 -0
  42. package/dist/plugins/nextcloud/rules/l10n-enforce-ellipsis.js +44 -0
  43. package/dist/plugins/nextcloud/rules/l10n-non-breaking-space.d.ts +7 -0
  44. package/dist/plugins/nextcloud/rules/l10n-non-breaking-space.js +39 -0
  45. package/dist/plugins/nextcloud/rules/no-deprecated-globals.d.ts +7 -0
  46. package/dist/plugins/nextcloud/rules/no-deprecated-globals.js +209 -0
  47. package/dist/plugins/nextcloud/rules/no-deprecated-library-exports.d.ts +7 -0
  48. package/dist/plugins/nextcloud/rules/no-deprecated-library-exports.js +89 -0
  49. package/dist/plugins/nextcloud/rules/no-deprecated-library-props.d.ts +41 -0
  50. package/dist/plugins/nextcloud/rules/no-deprecated-library-props.js +429 -0
  51. package/dist/plugins/nextcloud/rules/no-removed-globals.d.ts +7 -0
  52. package/dist/plugins/nextcloud/rules/no-removed-globals.js +203 -0
  53. package/dist/plugins/nextcloud/utils/app-version-parser.d.ts +41 -0
  54. package/dist/plugins/nextcloud/utils/app-version-parser.js +106 -0
  55. package/dist/plugins/nextcloud/utils/lib-version-parser.d.ts +18 -0
  56. package/dist/plugins/nextcloud/utils/lib-version-parser.js +49 -0
  57. package/dist/plugins/nextcloud/utils/vue-template-visitor.d.ts +26 -0
  58. package/dist/plugins/nextcloud/utils/vue-template-visitor.js +38 -0
  59. package/dist/plugins/packageJson.d.ts +10 -0
  60. package/dist/plugins/packageJson.js +66 -0
  61. package/dist/utils.d.ts +12 -0
  62. package/dist/utils.js +19 -0
  63. package/dist/version.d.ts +1 -0
  64. package/dist/version.js +6 -0
  65. package/package.json +35 -27
  66. package/dist/index.mjs +0 -1464
  67. package/dist/index.mjs.map +0 -1
@@ -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
+ };
@@ -0,0 +1,7 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import type { Rule } from 'eslint';
6
+ declare const rule: Rule.RuleModule;
7
+ export default rule;
@@ -0,0 +1,203 @@
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";
6
+ // ------------------------------------------------------------------------------
7
+ // Rule Definition
8
+ // ------------------------------------------------------------------------------
9
+ const global = {
10
+ autosize: '29.0.0',
11
+ escapeHTML: '20.0.0',
12
+ fileDownloadPath: '15.0.0',
13
+ formatDate: '19.0.0',
14
+ getScrollBarWidth: '15.0.0',
15
+ getURLParameter: '19.0.0',
16
+ humanFileSize: '19.0.0',
17
+ marked: '19.0.0',
18
+ md5: '32.0.0',
19
+ relative_modified_date: '19.0.0',
20
+ };
21
+ const oc = {
22
+ AppConfig: '33.0.0',
23
+ SystemTags: '33.0.0',
24
+ getScrollBarWidth: '15.0.0',
25
+ addTranslations: '26.0.0',
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',
33
+ loadScript: '28.0.0',
34
+ loadStyle: '28.0.0',
35
+ redirect: '33.0.0',
36
+ reload: '33.0.0',
37
+ set: '33.0.0',
38
+ };
39
+ const ocNested = {
40
+ AppConfig: {
41
+ hasKey: '15.0.0',
42
+ deleteApp: '15.0.0',
43
+ },
44
+ Util: {
45
+ hasSVGSupport: '15.0.0',
46
+ replaceSVGIcon: '15.0.0',
47
+ replaceSVG: '15.0.0',
48
+ scaleFixForIE8: '15.0.0',
49
+ isIE8: '15.0.0',
50
+ },
51
+ Settings: {
52
+ UserSettings: '33.0.0',
53
+ },
54
+ };
55
+ const oca = {
56
+ // ref: https://github.com/nextcloud/server/commit/6eced42b7a40f5b0ea0489244583219d0ee2e7af
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
+ },
70
+ };
71
+ // TODO: handle OC.x.y.z like OC.Share.ShareConfigModel.areAvatarsEnabled()
72
+ // ref https://github.com/nextcloud/server/issues/11045
73
+ const rule = {
74
+ meta: {
75
+ docs: {
76
+ description: 'Removed Nextcloud APIs',
77
+ recommended: true,
78
+ },
79
+ // fixable: "code" or "whitespace"
80
+ schema: [
81
+ {
82
+ // We accept one option which is an object
83
+ type: 'object',
84
+ properties: {
85
+ // if we should try to find an appinfo and only handle APIs removed before the max-version
86
+ parseAppInfo: {
87
+ type: 'boolean',
88
+ },
89
+ // Set a Nextcloud target version, only APIs removed before that versions are checked
90
+ targetVersion: {
91
+ type: 'string',
92
+ },
93
+ },
94
+ additionalProperties: false,
95
+ },
96
+ ],
97
+ messages: {
98
+ removedGlobal: 'The global property or function {{name}} was removed in Nextcloud {{version}}',
99
+ },
100
+ },
101
+ create(context) {
102
+ const checkTargetVersion = createVersionValidator(context);
103
+ return {
104
+ MemberExpression(node) {
105
+ // OCA.x
106
+ if ('name' in node.object
107
+ && 'name' in node.property
108
+ && node.object.name === 'OCA'
109
+ && oca[node.property.name]
110
+ && checkTargetVersion(oca[node.property.name])) {
111
+ context.report({
112
+ node,
113
+ message: `The property or function OCA.${node.property.name} was removed in Nextcloud ${oca[node.property.name]}`,
114
+ });
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
+ }
137
+ // OC.x
138
+ if ('name' in node.object
139
+ && 'name' in node.property
140
+ && node.object.name === 'OC'
141
+ && Object.hasOwn(oc, node.property.name)
142
+ && checkTargetVersion(oc[node.property.name])) {
143
+ context.report({
144
+ node,
145
+ message: `The property or function OC.${node.property.name} was removed in Nextcloud ${oc[node.property.name]}`,
146
+ });
147
+ }
148
+ // OC.x.y
149
+ if (node.object.type === 'MemberExpression'
150
+ && 'name' in node.object.object
151
+ && node.object.object.name === 'OC'
152
+ && 'name' in node.object.property
153
+ && ocNested[node.object.property.name]
154
+ && 'name' in node.property
155
+ && ocNested[node.object.property.name][node.property.name]) {
156
+ const version = ocNested[node.object.property.name][node.property.name];
157
+ if (checkTargetVersion(version)) {
158
+ const prop = [
159
+ 'OC',
160
+ node.object.property.name,
161
+ node.property.name,
162
+ ].join('.');
163
+ context.report({
164
+ node,
165
+ message: `The property or function ${prop} was removed in Nextcloud ${version}`,
166
+ });
167
+ }
168
+ }
169
+ },
170
+ Program(node) {
171
+ // Logic adapted from https://github.com/eslint/eslint/blob/master/lib/rules/no-restricted-globals.js
172
+ const scope = context.sourceCode.getScope(node);
173
+ const report = (ref) => {
174
+ const { identifier } = ref;
175
+ if (global[identifier.name] && checkTargetVersion(global[identifier.name])) {
176
+ context.report({
177
+ node,
178
+ messageId: 'removedGlobal',
179
+ data: {
180
+ name: identifier.name,
181
+ version: global[identifier.name],
182
+ },
183
+ });
184
+ }
185
+ };
186
+ // Report variables declared elsewhere (ex: variables defined as "global" by eslint)
187
+ scope.variables.forEach((variable) => {
188
+ if (!variable.defs.length
189
+ && Object.hasOwn(global, variable.name)) {
190
+ variable.references.forEach(report);
191
+ }
192
+ });
193
+ // Report variables not declared at all
194
+ scope.through.forEach((reference) => {
195
+ if (Object.hasOwn(global, reference.identifier.name)) {
196
+ report(reference);
197
+ }
198
+ });
199
+ },
200
+ };
201
+ },
202
+ };
203
+ export default rule;
@@ -0,0 +1,41 @@
1
+ /*!
2
+ * SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
3
+ * SPDX-License-Identifier: AGPL-3.0-or-later
4
+ */
5
+ import type { Rule } from 'eslint';
6
+ /**
7
+ * Check if a given path exists and is a directory
8
+ *
9
+ * @param filePath The path
10
+ */
11
+ export declare function isDirectory(filePath: string): boolean;
12
+ /**
13
+ * Check if a given path exists and is a directory
14
+ *
15
+ * @param filePath The path
16
+ */
17
+ export declare function isFile(filePath: string): boolean;
18
+ /**
19
+ * Find the path of nearest `appinfo/info.xml` relative to given path
20
+ *
21
+ * @param currentPath Path to lookup
22
+ * @return Either the full path including the `info.xml` part or `undefined` if no found
23
+ */
24
+ export declare function findAppinfo(currentPath: string): string | undefined;
25
+ /**
26
+ * Make sure that versions like '25' can be handled by semver
27
+ *
28
+ * @param version The pure version string
29
+ * @return Sanitized version string
30
+ */
31
+ export declare function sanitizeTargetVersion(version: string): string;
32
+ /**
33
+ * Create a callback that takes a version number an checks if the version
34
+ * is valid compared to configured version / detected version.
35
+ *
36
+ * @param options Options
37
+ * @param options.cwd The current working directory
38
+ * @param options.physicalFilename The real filename where ESLint is linting currently
39
+ * @param options.options The plugin options
40
+ */
41
+ export declare function createVersionValidator({ cwd, physicalFilename, options }: Rule.RuleContext): ((version: string) => boolean);