@nextcloud/eslint-config 9.0.0-rc.0 → 9.0.0-rc.2
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 +80 -40
- package/README.md +142 -29
- package/dist/configs/codeStyle.d.ts +13 -0
- package/dist/configs/codeStyle.js +184 -0
- package/dist/configs/documentation.d.ts +12 -0
- package/dist/configs/documentation.js +123 -0
- package/dist/configs/filesystem.d.ts +9 -0
- package/dist/configs/filesystem.js +20 -0
- package/dist/configs/imports.d.ts +12 -0
- package/dist/configs/imports.js +114 -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 +152 -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 +26 -0
- package/dist/globs.d.ts +20 -0
- package/dist/globs.js +39 -0
- package/dist/index.d.ts +27 -31
- package/dist/index.js +78 -0
- package/dist/plugins/l10n/index.d.ts +10 -0
- package/dist/plugins/l10n/index.js +17 -0
- package/dist/plugins/l10n/rules/enforce-ellipsis.d.ts +7 -0
- package/dist/plugins/l10n/rules/enforce-ellipsis.js +32 -0
- package/dist/plugins/l10n/rules/non-breaking-space.d.ts +7 -0
- package/dist/plugins/l10n/rules/non-breaking-space.js +30 -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 +6 -0
- package/dist/plugins/nextcloud/rules/no-deprecations.d.ts +7 -0
- package/dist/plugins/nextcloud/rules/no-deprecations.js +196 -0
- package/dist/plugins/nextcloud/rules/no-removed-apis.d.ts +7 -0
- package/dist/plugins/nextcloud/rules/no-removed-apis.js +152 -0
- package/dist/plugins/nextcloud/utils/version-parser.d.ts +41 -0
- package/dist/plugins/nextcloud/utils/version-parser.js +106 -0
- package/dist/plugins/nextcloud-vue/index.d.ts +28 -0
- package/dist/plugins/nextcloud-vue/index.js +13 -0
- package/dist/plugins/nextcloud-vue/rules/index.d.ts +17 -0
- package/dist/plugins/nextcloud-vue/rules/index.js +10 -0
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-exports.d.ts +7 -0
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-exports.js +44 -0
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-props.d.ts +20 -0
- package/dist/plugins/nextcloud-vue/rules/no-deprecated-props.js +61 -0
- package/dist/plugins/nextcloud-vue/utils/lib-version-parser.d.ts +33 -0
- package/dist/plugins/nextcloud-vue/utils/lib-version-parser.js +94 -0
- package/dist/plugins/packageJson.d.ts +10 -0
- package/dist/plugins/packageJson.js +66 -0
- package/dist/utils.d.ts +23 -0
- package/dist/utils.js +19 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +6 -0
- package/package.json +26 -22
- package/dist/index.mjs +0 -1464
- package/dist/index.mjs.map +0 -1
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
import { createVersionValidator } from "../utils/version-parser.js";
|
|
2
|
+
// ------------------------------------------------------------------------------
|
|
3
|
+
// Rule Definition
|
|
4
|
+
// ------------------------------------------------------------------------------
|
|
5
|
+
const global = {
|
|
6
|
+
$: '19.0.0',
|
|
7
|
+
Backbone: '18.0.0',
|
|
8
|
+
Clipboard: '18.0.0',
|
|
9
|
+
ClipboardJs: '18.0.0',
|
|
10
|
+
DOMPurify: '18.0.0',
|
|
11
|
+
formatDate: '16.0.0',
|
|
12
|
+
getURLParameter: '16.0.0',
|
|
13
|
+
Handlebars: '18.0.0',
|
|
14
|
+
humanFileSize: '16.0.0',
|
|
15
|
+
initCore: '17.0.0',
|
|
16
|
+
jQuery: '19.0.0',
|
|
17
|
+
jstimezonedetect: '18.0.0',
|
|
18
|
+
jstz: '18.0.0',
|
|
19
|
+
md5: '18.0.0',
|
|
20
|
+
moment: '18.0.0',
|
|
21
|
+
oc_appconfig: '17.0.0',
|
|
22
|
+
oc_appswebroots: '17.0.0',
|
|
23
|
+
oc_capabilities: '17.0.0',
|
|
24
|
+
oc_config: '17.0.0',
|
|
25
|
+
oc_current_user: '17.0.0',
|
|
26
|
+
oc_debug: '17.0.0',
|
|
27
|
+
oc_isadmin: '17.0.0',
|
|
28
|
+
oc_requesttoken: '17.0.0',
|
|
29
|
+
oc_webroot: '17.0.0',
|
|
30
|
+
OCDialogs: '17.0.0',
|
|
31
|
+
relative_modified_date: '16.0.0',
|
|
32
|
+
};
|
|
33
|
+
const oc = {
|
|
34
|
+
L10n: '26.0.0',
|
|
35
|
+
_capabilities: '17.0.0',
|
|
36
|
+
addTranslations: '17.0.0',
|
|
37
|
+
basename: '18.0.0',
|
|
38
|
+
coreApps: '17.0.0',
|
|
39
|
+
currentUser: '19.0.0',
|
|
40
|
+
dirname: '18.0.0',
|
|
41
|
+
encodePath: '18.0.0',
|
|
42
|
+
fileIsBlacklisted: '17.0.0',
|
|
43
|
+
filePath: '19.0.0',
|
|
44
|
+
generateUrl: '19.0.0',
|
|
45
|
+
get: '19.0.0',
|
|
46
|
+
getCanonicalLocale: '20.0.0',
|
|
47
|
+
getCurrentUser: '19.0.0',
|
|
48
|
+
getHost: '17.0.0',
|
|
49
|
+
getHostName: '17.0.0',
|
|
50
|
+
getPort: '17.0.0',
|
|
51
|
+
getProtocol: '17.0.0',
|
|
52
|
+
getRootPath: '19.0.0',
|
|
53
|
+
imagePath: '19.0.0',
|
|
54
|
+
isSamePath: '18.0.0',
|
|
55
|
+
joinPaths: '18.0.0',
|
|
56
|
+
linkTo: '19.0.0',
|
|
57
|
+
linkToOCS: '19.0.0',
|
|
58
|
+
linkToRemote: '19.0.0',
|
|
59
|
+
set: '19.0.0',
|
|
60
|
+
webroot: '19.0.0',
|
|
61
|
+
};
|
|
62
|
+
const oca = {
|
|
63
|
+
Search: '20.0.0',
|
|
64
|
+
};
|
|
65
|
+
const ocp = {
|
|
66
|
+
Toast: '19.0.0',
|
|
67
|
+
};
|
|
68
|
+
const ocNested = {
|
|
69
|
+
Util: {
|
|
70
|
+
formatDate: '20.0.0',
|
|
71
|
+
humanFileSize: '20.0.0',
|
|
72
|
+
relativeModifiedDate: '20.0.0',
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
const rule = {
|
|
76
|
+
meta: {
|
|
77
|
+
docs: {
|
|
78
|
+
description: 'Deprecated Nextcloud APIs',
|
|
79
|
+
category: 'Nextcloud',
|
|
80
|
+
recommended: true,
|
|
81
|
+
},
|
|
82
|
+
// fixable: null or "code" or "whitespace"
|
|
83
|
+
schema: [
|
|
84
|
+
{
|
|
85
|
+
// We accept one option which is an object
|
|
86
|
+
type: 'object',
|
|
87
|
+
properties: {
|
|
88
|
+
// if we should try to find an appinfo and only handle APIs removed before the max-version
|
|
89
|
+
parseAppInfo: {
|
|
90
|
+
type: 'boolean',
|
|
91
|
+
},
|
|
92
|
+
// Set a Nextcloud target version, only APIs removed before that versions are checked
|
|
93
|
+
targetVersion: {
|
|
94
|
+
type: 'string',
|
|
95
|
+
},
|
|
96
|
+
},
|
|
97
|
+
additionalProperties: false,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
messages: {
|
|
101
|
+
deprecatedGlobal: 'The global property or function {{name}} was deprecated in Nextcloud {{version}}',
|
|
102
|
+
},
|
|
103
|
+
},
|
|
104
|
+
create: function (context) {
|
|
105
|
+
const checkTargetVersion = createVersionValidator(context);
|
|
106
|
+
return {
|
|
107
|
+
MemberExpression: function (node) {
|
|
108
|
+
// OC.x
|
|
109
|
+
if ('name' in node.object
|
|
110
|
+
&& 'name' in node.property
|
|
111
|
+
&& node.object.name === 'OC'
|
|
112
|
+
&& Object.hasOwn(oc, node.property.name)
|
|
113
|
+
&& checkTargetVersion(oc[node.property.name])) {
|
|
114
|
+
context.report({
|
|
115
|
+
node,
|
|
116
|
+
message: `The property or function OC.${node.property.name} was deprecated in Nextcloud ${oc[node.property.name]}`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
// OCA.x
|
|
120
|
+
if ('name' in node.object
|
|
121
|
+
&& 'name' in node.property
|
|
122
|
+
&& node.object.name === 'OCA'
|
|
123
|
+
&& Object.hasOwn(oca, node.property.name)
|
|
124
|
+
&& checkTargetVersion(oca[node.property.name])) {
|
|
125
|
+
context.report({
|
|
126
|
+
node,
|
|
127
|
+
message: `The property or function OCA.${node.property.name} was deprecated in Nextcloud ${oca[node.property.name]}`,
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// OCP.x
|
|
131
|
+
if ('name' in node.object
|
|
132
|
+
&& 'name' in node.property
|
|
133
|
+
&& node.object.name === 'OCP'
|
|
134
|
+
&& Object.hasOwn(ocp, node.property.name)
|
|
135
|
+
&& checkTargetVersion(ocp[node.property.name])) {
|
|
136
|
+
context.report({
|
|
137
|
+
node,
|
|
138
|
+
message: `The property or function OCP.${node.property.name} was deprecated in Nextcloud ${ocp[node.property.name]}`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
// OC.x.y
|
|
142
|
+
if (node.object.type === 'MemberExpression'
|
|
143
|
+
&& 'name' in node.object.object
|
|
144
|
+
&& node.object.object.name === 'OC'
|
|
145
|
+
&& 'name' in node.property
|
|
146
|
+
&& 'name' in node.object.property
|
|
147
|
+
&& Object.hasOwn(ocNested, node.object.property.name)
|
|
148
|
+
&& Object.hasOwn(ocNested[node.object.property.name], node.property.name)) {
|
|
149
|
+
const version = ocNested[node.object.property.name][node.property.name];
|
|
150
|
+
if (checkTargetVersion(version)) {
|
|
151
|
+
const prop = [
|
|
152
|
+
'OC',
|
|
153
|
+
node.object.property.name,
|
|
154
|
+
node.property.name,
|
|
155
|
+
].join('.');
|
|
156
|
+
const deprecatedSince = ocNested[node.object.property.name][node.property.name];
|
|
157
|
+
context.report({
|
|
158
|
+
node,
|
|
159
|
+
message: `The property or function ${prop} was deprecated in Nextcloud ${deprecatedSince}`,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
Program(node) {
|
|
165
|
+
// Logic adapted from https://github.com/eslint/eslint/blob/master/lib/rules/no-restricted-globals.js
|
|
166
|
+
const scope = context.sourceCode.getScope(node);
|
|
167
|
+
const report = ({ identifier }) => {
|
|
168
|
+
if (checkTargetVersion(global[identifier.name])) {
|
|
169
|
+
context.report({
|
|
170
|
+
node,
|
|
171
|
+
messageId: 'deprecatedGlobal',
|
|
172
|
+
data: {
|
|
173
|
+
name: identifier.name,
|
|
174
|
+
version: global[identifier.name],
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
// Report variables declared elsewhere (ex: variables defined as "global" by eslint)
|
|
180
|
+
scope.variables.forEach((variable) => {
|
|
181
|
+
if (!variable.defs.length
|
|
182
|
+
&& Object.hasOwn(global, variable.name)) {
|
|
183
|
+
variable.references.forEach(report);
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
// Report variables not declared at all
|
|
187
|
+
scope.through.forEach((reference) => {
|
|
188
|
+
if (Object.hasOwn(global, reference.identifier.name)) {
|
|
189
|
+
report(reference);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
};
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
export default rule;
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { createVersionValidator } from "../utils/version-parser.js";
|
|
2
|
+
// ------------------------------------------------------------------------------
|
|
3
|
+
// Rule Definition
|
|
4
|
+
// ------------------------------------------------------------------------------
|
|
5
|
+
const global = {
|
|
6
|
+
autosize: '29.0.0',
|
|
7
|
+
escapeHTML: '20.0.0',
|
|
8
|
+
fileDownloadPath: '15.0.0',
|
|
9
|
+
formatDate: '19.0.0',
|
|
10
|
+
getScrollBarWidth: '15.0.0',
|
|
11
|
+
getURLParameter: '19.0.0',
|
|
12
|
+
humanFileSize: '19.0.0',
|
|
13
|
+
marked: '19.0.0',
|
|
14
|
+
relative_modified_date: '19.0.0',
|
|
15
|
+
};
|
|
16
|
+
const oc = {
|
|
17
|
+
getScrollBarWidth: '15.0.0',
|
|
18
|
+
addTranslations: '26.0.0',
|
|
19
|
+
appSettings: '28.0.0',
|
|
20
|
+
loadScript: '28.0.0',
|
|
21
|
+
loadStyle: '28.0.0',
|
|
22
|
+
};
|
|
23
|
+
const ocNested = {
|
|
24
|
+
AppConfig: {
|
|
25
|
+
hasKey: '15.0.0',
|
|
26
|
+
deleteApp: '15.0.0',
|
|
27
|
+
},
|
|
28
|
+
Util: {
|
|
29
|
+
hasSVGSupport: '15.0.0',
|
|
30
|
+
replaceSVGIcon: '15.0.0',
|
|
31
|
+
replaceSVG: '15.0.0',
|
|
32
|
+
scaleFixForIE8: '15.0.0',
|
|
33
|
+
isIE8: '15.0.0',
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
const oca = {
|
|
37
|
+
// ref: https://github.com/nextcloud/server/commit/6eced42b7a40f5b0ea0489244583219d0ee2e7af
|
|
38
|
+
Search: '20.0.0',
|
|
39
|
+
};
|
|
40
|
+
// TODO: handle OC.x.y.z like OC.Share.ShareConfigModel.areAvatarsEnabled()
|
|
41
|
+
// ref https://github.com/nextcloud/server/issues/11045
|
|
42
|
+
const rule = {
|
|
43
|
+
meta: {
|
|
44
|
+
docs: {
|
|
45
|
+
description: 'Removed Nextcloud APIs',
|
|
46
|
+
category: 'Nextcloud',
|
|
47
|
+
recommended: true,
|
|
48
|
+
},
|
|
49
|
+
// fixable: "code" or "whitespace"
|
|
50
|
+
schema: [
|
|
51
|
+
{
|
|
52
|
+
// We accept one option which is an object
|
|
53
|
+
type: 'object',
|
|
54
|
+
properties: {
|
|
55
|
+
// if we should try to find an appinfo and only handle APIs removed before the max-version
|
|
56
|
+
parseAppInfo: {
|
|
57
|
+
type: 'boolean',
|
|
58
|
+
},
|
|
59
|
+
// Set a Nextcloud target version, only APIs removed before that versions are checked
|
|
60
|
+
targetVersion: {
|
|
61
|
+
type: 'string',
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
additionalProperties: false,
|
|
65
|
+
},
|
|
66
|
+
],
|
|
67
|
+
messages: {
|
|
68
|
+
removedGlobal: 'The global property or function {{name}} was removed in Nextcloud {{version}}',
|
|
69
|
+
},
|
|
70
|
+
},
|
|
71
|
+
create(context) {
|
|
72
|
+
const checkTargetVersion = createVersionValidator(context);
|
|
73
|
+
return {
|
|
74
|
+
MemberExpression(node) {
|
|
75
|
+
// OCA.x
|
|
76
|
+
if ('name' in node.object
|
|
77
|
+
&& 'name' in node.property
|
|
78
|
+
&& node.object.name === 'OCA'
|
|
79
|
+
&& oca[node.property.name]
|
|
80
|
+
&& checkTargetVersion(oca[node.property.name])) {
|
|
81
|
+
context.report({
|
|
82
|
+
node,
|
|
83
|
+
message: `The property or function OCA.${node.property.name} was removed in Nextcloud ${oca[node.property.name]}`,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
// OC.x
|
|
87
|
+
if ('name' in node.object
|
|
88
|
+
&& 'name' in node.property
|
|
89
|
+
&& node.object.name === 'OC'
|
|
90
|
+
&& Object.hasOwn(oc, node.property.name)
|
|
91
|
+
&& checkTargetVersion(oc[node.property.name])) {
|
|
92
|
+
context.report({
|
|
93
|
+
node,
|
|
94
|
+
message: `The property or function OC.${node.property.name} was removed in Nextcloud ${oc[node.property.name]}`,
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
// OC.x.y
|
|
98
|
+
if (node.object.type === 'MemberExpression'
|
|
99
|
+
&& 'name' in node.object.object
|
|
100
|
+
&& node.object.object.name === 'OC'
|
|
101
|
+
&& 'name' in node.object.property
|
|
102
|
+
&& ocNested[node.object.property.name]
|
|
103
|
+
&& 'name' in node.property
|
|
104
|
+
&& ocNested[node.object.property.name][node.property.name]) {
|
|
105
|
+
const version = ocNested[node.object.property.name][node.property.name];
|
|
106
|
+
if (checkTargetVersion(version)) {
|
|
107
|
+
const prop = [
|
|
108
|
+
'OC',
|
|
109
|
+
node.object.property.name,
|
|
110
|
+
node.property.name,
|
|
111
|
+
].join('.');
|
|
112
|
+
context.report({
|
|
113
|
+
node,
|
|
114
|
+
message: `The property or function ${prop} was removed in Nextcloud ${version}`,
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
Program(node) {
|
|
120
|
+
// Logic adapted from https://github.com/eslint/eslint/blob/master/lib/rules/no-restricted-globals.js
|
|
121
|
+
const scope = context.sourceCode.getScope(node);
|
|
122
|
+
const report = (ref) => {
|
|
123
|
+
const { identifier } = ref;
|
|
124
|
+
if (global[identifier.name] && checkTargetVersion(global[identifier.name])) {
|
|
125
|
+
context.report({
|
|
126
|
+
node,
|
|
127
|
+
messageId: 'removedGlobal',
|
|
128
|
+
data: {
|
|
129
|
+
name: identifier.name,
|
|
130
|
+
version: global[identifier.name],
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
// Report variables declared elsewhere (ex: variables defined as "global" by eslint)
|
|
136
|
+
scope.variables.forEach((variable) => {
|
|
137
|
+
if (!variable.defs.length
|
|
138
|
+
&& Object.hasOwn(global, variable.name)) {
|
|
139
|
+
variable.references.forEach(report);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
// Report variables not declared at all
|
|
143
|
+
scope.through.forEach((reference) => {
|
|
144
|
+
if (Object.hasOwn(global, reference.identifier.name)) {
|
|
145
|
+
report(reference);
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
},
|
|
149
|
+
};
|
|
150
|
+
},
|
|
151
|
+
};
|
|
152
|
+
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);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*/
|
|
5
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
6
|
+
import { lstatSync, readFileSync } from 'node:fs';
|
|
7
|
+
import { dirname, isAbsolute, resolve, sep } from 'node:path';
|
|
8
|
+
import { lte, valid } from 'semver';
|
|
9
|
+
/**
|
|
10
|
+
* Check if a given path exists and is a directory
|
|
11
|
+
*
|
|
12
|
+
* @param filePath The path
|
|
13
|
+
*/
|
|
14
|
+
export function isDirectory(filePath) {
|
|
15
|
+
const stats = lstatSync(filePath, { throwIfNoEntry: false });
|
|
16
|
+
return stats !== undefined && stats.isDirectory();
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Check if a given path exists and is a directory
|
|
20
|
+
*
|
|
21
|
+
* @param filePath The path
|
|
22
|
+
*/
|
|
23
|
+
export function isFile(filePath) {
|
|
24
|
+
const stats = lstatSync(filePath, { throwIfNoEntry: false });
|
|
25
|
+
return stats !== undefined && stats.isFile();
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Find the path of nearest `appinfo/info.xml` relative to given path
|
|
29
|
+
*
|
|
30
|
+
* @param currentPath Path to lookup
|
|
31
|
+
* @return Either the full path including the `info.xml` part or `undefined` if no found
|
|
32
|
+
*/
|
|
33
|
+
export function findAppinfo(currentPath) {
|
|
34
|
+
while (currentPath && currentPath !== sep) {
|
|
35
|
+
const appinfoPath = `${currentPath}${sep}appinfo`;
|
|
36
|
+
if (isDirectory(appinfoPath)
|
|
37
|
+
&& isFile(`${appinfoPath}${sep}info.xml`)) {
|
|
38
|
+
return `${appinfoPath}${sep}info.xml`;
|
|
39
|
+
}
|
|
40
|
+
currentPath = resolve(currentPath, '..');
|
|
41
|
+
}
|
|
42
|
+
return undefined;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Make sure that versions like '25' can be handled by semver
|
|
46
|
+
*
|
|
47
|
+
* @param version The pure version string
|
|
48
|
+
* @return Sanitized version string
|
|
49
|
+
*/
|
|
50
|
+
export function sanitizeTargetVersion(version) {
|
|
51
|
+
let sanitizedVersion = version;
|
|
52
|
+
const sections = sanitizedVersion.split('.').length;
|
|
53
|
+
if (sections < 3) {
|
|
54
|
+
sanitizedVersion = sanitizedVersion + '.0'.repeat(3 - sections);
|
|
55
|
+
}
|
|
56
|
+
// now version should look like '25.0.0'
|
|
57
|
+
if (!valid(sanitizedVersion)) {
|
|
58
|
+
throw Error(`[@nextcloud/eslint-plugin] Invalid target version ${version} found`);
|
|
59
|
+
}
|
|
60
|
+
return sanitizedVersion;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Create a callback that takes a version number an checks if the version
|
|
64
|
+
* is valid compared to configured version / detected version.
|
|
65
|
+
*
|
|
66
|
+
* @param options Options
|
|
67
|
+
* @param options.cwd The current working directory
|
|
68
|
+
* @param options.physicalFilename The real filename where ESLint is linting currently
|
|
69
|
+
* @param options.options The plugin options
|
|
70
|
+
*/
|
|
71
|
+
export function createVersionValidator({ cwd, physicalFilename, options }) {
|
|
72
|
+
const settings = options[0];
|
|
73
|
+
if (settings?.targetVersion) {
|
|
74
|
+
// check if the rule version is lower than the current target version
|
|
75
|
+
const maxVersion = sanitizeTargetVersion(settings.targetVersion);
|
|
76
|
+
return (version) => lte(version, maxVersion);
|
|
77
|
+
}
|
|
78
|
+
// Try to find appinfo and parse the supported version
|
|
79
|
+
if (settings?.parseAppInfo !== false) {
|
|
80
|
+
// Current working directory, either the filename (can be empty) or the cwd property
|
|
81
|
+
const currentDirectory = isAbsolute(physicalFilename)
|
|
82
|
+
? resolve(dirname(physicalFilename))
|
|
83
|
+
: dirname(resolve(cwd, physicalFilename));
|
|
84
|
+
// The nearest appinfo
|
|
85
|
+
const appinfoPath = findAppinfo(currentDirectory);
|
|
86
|
+
if (appinfoPath) {
|
|
87
|
+
const parser = new XMLParser({
|
|
88
|
+
attributeNamePrefix: '@',
|
|
89
|
+
ignoreAttributes: false,
|
|
90
|
+
});
|
|
91
|
+
const xml = parser.parse(readFileSync(appinfoPath));
|
|
92
|
+
let maxVersion = xml?.info?.dependencies?.nextcloud?.['@max-version'];
|
|
93
|
+
if (typeof maxVersion !== 'string') {
|
|
94
|
+
throw new Error(`[@nextcloud/eslint-plugin] AppInfo does not contain a max-version (location: ${appinfoPath})`);
|
|
95
|
+
}
|
|
96
|
+
maxVersion = sanitizeTargetVersion(maxVersion);
|
|
97
|
+
return (version) => lte(version, maxVersion);
|
|
98
|
+
}
|
|
99
|
+
if (settings?.parseAppInfo === true) {
|
|
100
|
+
// User enforced app info parsing, so we throw an error - otherwise just fallback to default
|
|
101
|
+
throw new Error('[@nextcloud/eslint-plugin] AppInfo parsing was enabled, but no `appinfo/info.xml` was found.');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// If not configured or parsing is disabled, every rule should be handled
|
|
105
|
+
return () => true;
|
|
106
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*/
|
|
5
|
+
declare const _default: {
|
|
6
|
+
rules: {
|
|
7
|
+
'no-deprecated-exports': import("eslint").Rule.RuleModule;
|
|
8
|
+
'no-deprecated-props': {
|
|
9
|
+
meta: {
|
|
10
|
+
docs: {
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
type: "problem";
|
|
14
|
+
fixable: "code";
|
|
15
|
+
messages: {
|
|
16
|
+
useTypeInstead: string;
|
|
17
|
+
useVariantInstead: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
create(context: import("eslint").Rule.RuleContext): any;
|
|
21
|
+
};
|
|
22
|
+
};
|
|
23
|
+
meta: {
|
|
24
|
+
name: string;
|
|
25
|
+
version: string;
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
export default _default;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*/
|
|
5
|
+
import { packageVersion } from "../../version.js";
|
|
6
|
+
import { rules } from "./rules/index.js";
|
|
7
|
+
export default {
|
|
8
|
+
rules,
|
|
9
|
+
meta: {
|
|
10
|
+
name: '@nextcloud/eslint-plugin',
|
|
11
|
+
version: packageVersion,
|
|
12
|
+
},
|
|
13
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export declare const rules: {
|
|
2
|
+
'no-deprecated-exports': import("eslint").Rule.RuleModule;
|
|
3
|
+
'no-deprecated-props': {
|
|
4
|
+
meta: {
|
|
5
|
+
docs: {
|
|
6
|
+
description: string;
|
|
7
|
+
};
|
|
8
|
+
type: "problem";
|
|
9
|
+
fixable: "code";
|
|
10
|
+
messages: {
|
|
11
|
+
useTypeInstead: string;
|
|
12
|
+
useVariantInstead: string;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
create(context: import("eslint").Rule.RuleContext): any;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/*!
|
|
2
|
+
* SPDX-FileCopyrightText: 2025 Nextcloud GmbH and Nextcloud contributors
|
|
3
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
4
|
+
*/
|
|
5
|
+
import noDeprecatedExports from "./no-deprecated-exports.js";
|
|
6
|
+
import noDeprecatedProps from "./no-deprecated-props.js";
|
|
7
|
+
export const rules = {
|
|
8
|
+
'no-deprecated-exports': noDeprecatedExports,
|
|
9
|
+
'no-deprecated-props': noDeprecatedProps,
|
|
10
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { createLibVersionValidator } from "../utils/lib-version-parser.js";
|
|
2
|
+
// ------------------------------------------------------------------------------
|
|
3
|
+
// Rule Definition
|
|
4
|
+
// ------------------------------------------------------------------------------
|
|
5
|
+
/*
|
|
6
|
+
Introduced in @nextcloud/vue v8.23.0
|
|
7
|
+
https://github.com/nextcloud-libraries/nextcloud-vue/pull/6385
|
|
8
|
+
*/
|
|
9
|
+
const rule = {
|
|
10
|
+
meta: {
|
|
11
|
+
docs: {
|
|
12
|
+
description: 'Deprecated @nextcloud/vue import syntax',
|
|
13
|
+
recommended: true,
|
|
14
|
+
},
|
|
15
|
+
fixable: 'code',
|
|
16
|
+
schema: [],
|
|
17
|
+
},
|
|
18
|
+
create(context) {
|
|
19
|
+
const versionSatisfies = createLibVersionValidator(context);
|
|
20
|
+
const isVersionValid = versionSatisfies('8.23.0');
|
|
21
|
+
const oldPattern = '@nextcloud/vue/dist/([^/]+)/([^/.]+)';
|
|
22
|
+
return {
|
|
23
|
+
ImportDeclaration: function (node) {
|
|
24
|
+
if (!isVersionValid) {
|
|
25
|
+
// Can't fix, ignore the rule
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const importPath = node.source.value;
|
|
29
|
+
const match = importPath.match(new RegExp(oldPattern));
|
|
30
|
+
if (match) {
|
|
31
|
+
const newImportPath = `'@nextcloud/vue/${match[1].toLowerCase()}/${match[2]}'`;
|
|
32
|
+
context.report({
|
|
33
|
+
node,
|
|
34
|
+
message: 'Import from "@nextcloud/vue/dist" is deprecated',
|
|
35
|
+
fix(fixer) {
|
|
36
|
+
return fixer.replaceText(node.source, newImportPath);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
44
|
+
export default rule;
|
|
@@ -0,0 +1,20 @@
|
|
|
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 _default: {
|
|
7
|
+
meta: {
|
|
8
|
+
docs: {
|
|
9
|
+
description: string;
|
|
10
|
+
};
|
|
11
|
+
type: "problem";
|
|
12
|
+
fixable: "code";
|
|
13
|
+
messages: {
|
|
14
|
+
useTypeInstead: string;
|
|
15
|
+
useVariantInstead: string;
|
|
16
|
+
};
|
|
17
|
+
};
|
|
18
|
+
create(context: Rule.RuleContext): any;
|
|
19
|
+
};
|
|
20
|
+
export default _default;
|