@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.
- package/CHANGELOG.md +133 -44
- package/README.md +139 -33
- package/dist/configs/codeStyle.d.ts +13 -0
- package/dist/configs/codeStyle.js +176 -0
- package/dist/configs/documentation.d.ts +12 -0
- package/dist/configs/documentation.js +117 -0
- package/dist/configs/filesystem.d.ts +9 -0
- package/dist/configs/filesystem.js +25 -0
- package/dist/configs/imports.d.ts +12 -0
- package/dist/configs/imports.js +111 -0
- package/dist/configs/javascript.d.ts +14 -0
- package/dist/configs/javascript.js +118 -0
- package/dist/configs/json.d.ts +9 -0
- package/dist/configs/json.js +45 -0
- package/dist/configs/node.d.ts +10 -0
- package/dist/configs/node.js +24 -0
- package/dist/configs/typescript.d.ts +12 -0
- package/dist/configs/typescript.js +60 -0
- package/dist/configs/vue.d.ts +12 -0
- package/dist/configs/vue.js +158 -0
- package/dist/configs/vue2.d.ts +12 -0
- package/dist/configs/vue2.js +30 -0
- package/dist/configs/vue3.d.ts +12 -0
- package/dist/configs/vue3.js +48 -0
- package/dist/globs.d.ts +20 -0
- package/dist/globs.js +41 -0
- package/dist/index.d.ts +31 -31
- package/dist/index.js +81 -0
- package/dist/plugins/import-extensions/index.d.ts +15 -0
- package/dist/plugins/import-extensions/index.js +13 -0
- package/dist/plugins/import-extensions/rules/ban-inline-type-imports.d.ts +7 -0
- package/dist/plugins/import-extensions/rules/ban-inline-type-imports.js +176 -0
- package/dist/plugins/import-extensions/rules/extensions.d.ts +11 -0
- package/dist/plugins/import-extensions/rules/extensions.js +143 -0
- package/dist/plugins/import-extensions/rules/index.d.ts +8 -0
- package/dist/plugins/import-extensions/rules/index.js +10 -0
- package/dist/plugins/nextcloud/index.d.ts +7 -0
- package/dist/plugins/nextcloud/index.js +9 -0
- package/dist/plugins/nextcloud/rules/index.d.ts +6 -0
- package/dist/plugins/nextcloud/rules/index.js +18 -0
- package/dist/plugins/nextcloud/rules/l10n-enforce-ellipsis.d.ts +7 -0
- package/dist/plugins/nextcloud/rules/l10n-enforce-ellipsis.js +44 -0
- package/dist/plugins/nextcloud/rules/l10n-non-breaking-space.d.ts +7 -0
- package/dist/plugins/nextcloud/rules/l10n-non-breaking-space.js +39 -0
- package/dist/plugins/nextcloud/rules/no-deprecated-globals.d.ts +7 -0
- package/dist/plugins/nextcloud/rules/no-deprecated-globals.js +209 -0
- package/dist/plugins/nextcloud/rules/no-deprecated-library-exports.d.ts +7 -0
- package/dist/plugins/nextcloud/rules/no-deprecated-library-exports.js +89 -0
- package/dist/plugins/nextcloud/rules/no-deprecated-library-props.d.ts +41 -0
- package/dist/plugins/nextcloud/rules/no-deprecated-library-props.js +429 -0
- package/dist/plugins/nextcloud/rules/no-removed-globals.d.ts +7 -0
- package/dist/plugins/nextcloud/rules/no-removed-globals.js +203 -0
- package/dist/plugins/nextcloud/utils/app-version-parser.d.ts +41 -0
- package/dist/plugins/nextcloud/utils/app-version-parser.js +106 -0
- package/dist/plugins/nextcloud/utils/lib-version-parser.d.ts +18 -0
- package/dist/plugins/nextcloud/utils/lib-version-parser.js +49 -0
- package/dist/plugins/nextcloud/utils/vue-template-visitor.d.ts +26 -0
- package/dist/plugins/nextcloud/utils/vue-template-visitor.js +38 -0
- package/dist/plugins/packageJson.d.ts +10 -0
- package/dist/plugins/packageJson.js +66 -0
- package/dist/utils.d.ts +12 -0
- package/dist/utils.js +19 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +6 -0
- package/package.json +35 -27
- package/dist/index.mjs +0 -1464
- 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,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);
|