@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.
- package/CHANGELOG.md +114 -42
- package/README.md +85 -31
- package/dist/configs/codeStyle.d.ts +1 -1
- package/dist/configs/codeStyle.js +27 -29
- package/dist/configs/documentation.js +71 -38
- package/dist/configs/filesystem.js +10 -3
- package/dist/configs/imports.d.ts +1 -1
- package/dist/configs/imports.js +15 -18
- package/dist/configs/javascript.js +2 -2
- package/dist/configs/typescript.js +8 -1
- package/dist/configs/vue.js +36 -12
- package/dist/configs/vue2.js +2 -2
- package/dist/configs/vue3.js +24 -0
- package/dist/globs.d.ts +2 -0
- package/dist/globs.js +6 -2
- package/dist/index.d.ts +11 -7
- package/dist/index.js +4 -1
- package/dist/plugins/import-extensions/index.d.ts +15 -0
- package/dist/plugins/{nextcloud-vue → import-extensions}/index.js +4 -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/rules/index.js +16 -4
- package/dist/plugins/nextcloud/rules/l10n-enforce-ellipsis.js +44 -0
- package/dist/plugins/{l10n/rules/non-breaking-space.js → nextcloud/rules/l10n-non-breaking-space.js} +11 -2
- package/dist/plugins/nextcloud/rules/{no-deprecations.js → no-deprecated-globals.js} +16 -3
- 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-apis.js → no-removed-globals.js} +53 -2
- 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/utils.d.ts +1 -12
- package/package.json +31 -25
- package/dist/plugins/l10n/index.d.ts +0 -10
- package/dist/plugins/l10n/index.js +0 -17
- package/dist/plugins/l10n/rules/enforce-ellipsis.js +0 -32
- package/dist/plugins/nextcloud-vue/index.d.ts +0 -8
- package/dist/plugins/nextcloud-vue/rules/index.d.ts +0 -6
- package/dist/plugins/nextcloud-vue/rules/index.js +0 -4
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-exports.js +0 -44
- package/dist/plugins/nextcloud-vue/utils/lib-version-parser.d.ts +0 -33
- package/dist/plugins/nextcloud-vue/utils/lib-version-parser.js +0 -94
- /package/dist/plugins/{l10n/rules/enforce-ellipsis.d.ts → nextcloud/rules/l10n-enforce-ellipsis.d.ts} +0 -0
- /package/dist/plugins/{l10n/rules/non-breaking-space.d.ts → nextcloud/rules/l10n-non-breaking-space.d.ts} +0 -0
- /package/dist/plugins/{nextcloud-vue/rules/no-deprecated-exports.d.ts → nextcloud/rules/no-deprecated-globals.d.ts} +0 -0
- /package/dist/plugins/nextcloud/rules/{no-deprecations.d.ts → no-deprecated-library-exports.d.ts} +0 -0
- /package/dist/plugins/nextcloud/rules/{no-removed-apis.d.ts → no-removed-globals.d.ts} +0 -0
- /package/dist/plugins/nextcloud/utils/{version-parser.d.ts → app-version-parser.d.ts} +0 -0
- /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
|
-
|
|
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
|
+
}
|