@o3r/eslint-plugin 9.4.0-alpha.2 → 9.4.0-alpha.20
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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@o3r/eslint-plugin",
|
|
3
|
-
"version": "9.4.0-alpha.
|
|
3
|
+
"version": "9.4.0-alpha.20",
|
|
4
4
|
"description": "The module provides in-house eslint plugins to use in your own eslint configuration.",
|
|
5
5
|
"main": "./src/public_api.js",
|
|
6
6
|
"keywords": [
|
|
@@ -47,10 +47,10 @@
|
|
|
47
47
|
"@babel/core": "~7.23.0",
|
|
48
48
|
"@babel/preset-typescript": "~7.23.0",
|
|
49
49
|
"@compodoc/compodoc": "^1.1.19",
|
|
50
|
-
"@nx/eslint-plugin": "~16.
|
|
51
|
-
"@nx/jest": "~16.
|
|
52
|
-
"@o3r/build-helpers": "^9.4.0-alpha.
|
|
53
|
-
"@o3r/eslint-plugin": "^9.4.0-alpha.
|
|
50
|
+
"@nx/eslint-plugin": "~16.10.0",
|
|
51
|
+
"@nx/jest": "~16.10.0",
|
|
52
|
+
"@o3r/build-helpers": "^9.4.0-alpha.20",
|
|
53
|
+
"@o3r/eslint-plugin": "^9.4.0-alpha.20",
|
|
54
54
|
"@types/jest": "~29.5.2",
|
|
55
55
|
"@types/node": "^18.0.0",
|
|
56
56
|
"@typescript-eslint/eslint-plugin": "^5.60.1",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
"jest": "~29.7.0",
|
|
65
65
|
"jest-junit": "~16.0.0",
|
|
66
66
|
"jsonc-eslint-parser": "~2.3.0",
|
|
67
|
-
"nx": "~16.
|
|
67
|
+
"nx": "~16.10.0",
|
|
68
68
|
"rimraf": "^5.0.1",
|
|
69
69
|
"ts-jest": "~29.1.1",
|
|
70
70
|
"typescript": "~5.1.6"
|
package/src/index.js
CHANGED
|
@@ -3,10 +3,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
4
4
|
const no_folder_import_for_module_1 = require("./rules/no-folder-import-for-module/no-folder-import-for-module");
|
|
5
5
|
const template_async_number_limitation_1 = require("./rules/template-async-number-limitation/template-async-number-limitation");
|
|
6
|
+
const o3r_widget_tags_1 = require("./rules/o3r-widget-tags/o3r-widget-tags");
|
|
6
7
|
module.exports = {
|
|
7
8
|
rules: {
|
|
8
9
|
'no-folder-import-for-module': no_folder_import_for_module_1.default,
|
|
9
|
-
'template-async-number-limitation': template_async_number_limitation_1.default
|
|
10
|
+
'template-async-number-limitation': template_async_number_limitation_1.default,
|
|
11
|
+
'o3r-widget-tags': o3r_widget_tags_1.default
|
|
10
12
|
},
|
|
11
13
|
configs: {
|
|
12
14
|
'@o3r/no-folder-import-for-module': 'error',
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const experimental_utils_1 = require("@typescript-eslint/experimental-utils");
|
|
4
|
+
const utils_1 = require("../utils");
|
|
5
|
+
const o3rWidgetParameterPattern = '^[a-zA-Z0-9-_:.]+$';
|
|
6
|
+
const o3rWidgetParamTypes = ['string', 'number', 'boolean', 'string[]', 'number[]', 'boolean[]'];
|
|
7
|
+
const createCommentString = (comment) => `/*${comment}*/`;
|
|
8
|
+
exports.default = (0, utils_1.createRule)({
|
|
9
|
+
name: 'o3r-widget-tags',
|
|
10
|
+
meta: {
|
|
11
|
+
hasSuggestions: true,
|
|
12
|
+
fixable: 'code',
|
|
13
|
+
type: 'problem',
|
|
14
|
+
docs: {
|
|
15
|
+
description: 'Ensures that @o3rWidget and @o3rWidgetParam are used with correct value',
|
|
16
|
+
recommended: 'error'
|
|
17
|
+
},
|
|
18
|
+
schema: [
|
|
19
|
+
{
|
|
20
|
+
type: 'object',
|
|
21
|
+
required: ['widgets'],
|
|
22
|
+
properties: {
|
|
23
|
+
supportedInterfaceNames: {
|
|
24
|
+
type: 'array',
|
|
25
|
+
items: {
|
|
26
|
+
type: 'string'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
widgets: {
|
|
30
|
+
additionalProperties: {
|
|
31
|
+
type: 'object',
|
|
32
|
+
additionalProperties: false,
|
|
33
|
+
patternProperties: {
|
|
34
|
+
[o3rWidgetParameterPattern]: {
|
|
35
|
+
type: 'object',
|
|
36
|
+
required: ['type'],
|
|
37
|
+
properties: {
|
|
38
|
+
required: {
|
|
39
|
+
type: 'boolean'
|
|
40
|
+
},
|
|
41
|
+
type: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
enum: o3rWidgetParamTypes
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
],
|
|
53
|
+
messages: {
|
|
54
|
+
notSupportedType: '{{ o3rWidgetType }} is not supported. Supported o3rWidget types: {{ supportedO3rWidgets }}.',
|
|
55
|
+
notSupportedParamForType: '{{ o3rWidgetParam }} is not supported for {{ o3rWidgetType }}. Supported o3rWidgetParam for {{ o3rWidgetType }}: {{ supportedO3rWidgetParam }}.',
|
|
56
|
+
invalidParamValueType: '{{ o3rWidgetParam }} supports only {{ o3rWidgetParamType }}.',
|
|
57
|
+
noParamWithoutWidget: '@o3rWidgetParam cannot be used without @o3rWidget',
|
|
58
|
+
duplicatedParam: '@o3rWidgetParam {{ o3rWidgetParam }} must be defined only once.',
|
|
59
|
+
onlyOneWidgetAllowed: '@o3rWidget must be defined only once.',
|
|
60
|
+
notInConfigurationInterface: '@o3rWidget can only be used in `Configuration` interface.',
|
|
61
|
+
requiredParamMissing: '@o3rWidgetParam {{ o3rWidgetParam }} is mandatory when using @o3rWidget {{ o3rWidgetType }}.',
|
|
62
|
+
suggestParamMissing: 'Add @o3rWidgetParam {{ o3rWidgetParam }}.',
|
|
63
|
+
suggestRemoveDuplicatedO3rWidget: 'Remove the 2nd @o3rWidget.',
|
|
64
|
+
suggestRemoveDuplicatedO3rWidgetParam: 'Remove the 2nd @o3rWidgetParam.',
|
|
65
|
+
suggestAddO3rWidgetTag: 'Add @o3rWidget tag.',
|
|
66
|
+
suggestReplaceO3rWidgetType: 'Replace {{ currentType }} by {{ suggestedType }}.'
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
defaultOptions: [],
|
|
70
|
+
create: (context) => {
|
|
71
|
+
const options = context.options
|
|
72
|
+
.reduce((acc, option) => {
|
|
73
|
+
acc.supportedInterfaceNames = (acc.supportedInterfaceNames || []).concat(option.supportedInterfaceNames || []);
|
|
74
|
+
acc.widgets = {
|
|
75
|
+
...acc.widgets,
|
|
76
|
+
...option.widgets
|
|
77
|
+
};
|
|
78
|
+
return acc;
|
|
79
|
+
}, { widgets: {}, supportedInterfaceNames: ['Configuration', 'NestedConfiguration'] });
|
|
80
|
+
const supportedO3rWidgets = new Set(Object.keys(options.widgets));
|
|
81
|
+
return {
|
|
82
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
83
|
+
TSPropertySignature: (node) => {
|
|
84
|
+
const sourceCode = context.getSourceCode();
|
|
85
|
+
const [comment] = sourceCode.getCommentsBefore(node);
|
|
86
|
+
if (!comment || !comment.value.length) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
const { loc, value: docText } = comment;
|
|
90
|
+
const widgetTypes = Array.from(docText.matchAll(/@o3rWidget (.*)/g));
|
|
91
|
+
if (widgetTypes.length > 1) {
|
|
92
|
+
const fix = (fixer) => {
|
|
93
|
+
return fixer.replaceTextRange(comment.range, createCommentString(comment.value.replace(/(.*(@o3rWidget ).*(\n.*)*)(\n.*)\2.*/, '$1')));
|
|
94
|
+
};
|
|
95
|
+
return context.report({
|
|
96
|
+
messageId: 'onlyOneWidgetAllowed',
|
|
97
|
+
node,
|
|
98
|
+
loc,
|
|
99
|
+
fix,
|
|
100
|
+
suggest: [{
|
|
101
|
+
messageId: 'suggestRemoveDuplicatedO3rWidget',
|
|
102
|
+
fix
|
|
103
|
+
}]
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
const widgetType = widgetTypes[0]?.[1].trim();
|
|
107
|
+
const widgetParameterTexts = Array.from(docText.matchAll(/@o3rWidgetParam (.*)/g))
|
|
108
|
+
.map((match) => match[1].trim());
|
|
109
|
+
if (!widgetType) {
|
|
110
|
+
if (widgetParameterTexts.length) {
|
|
111
|
+
const fix = (fixer) => {
|
|
112
|
+
return fixer.replaceTextRange(comment.range, createCommentString(comment.value.replace(/((.*)@o3rWidgetParam .*)/, '$2@o3rWidget widgetType\n$1')));
|
|
113
|
+
};
|
|
114
|
+
return context.report({
|
|
115
|
+
messageId: 'noParamWithoutWidget',
|
|
116
|
+
node,
|
|
117
|
+
loc,
|
|
118
|
+
fix,
|
|
119
|
+
suggest: [{
|
|
120
|
+
messageId: 'suggestAddO3rWidgetTag',
|
|
121
|
+
fix
|
|
122
|
+
}]
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const interfaceDeclNode = node.parent?.parent;
|
|
128
|
+
if (interfaceDeclNode?.type !== experimental_utils_1.TSESTree.AST_NODE_TYPES.TSInterfaceDeclaration
|
|
129
|
+
|| !interfaceDeclNode.extends?.some((ext) => ext.type === experimental_utils_1.TSESTree.AST_NODE_TYPES.TSInterfaceHeritage
|
|
130
|
+
&& ext.expression.type === experimental_utils_1.TSESTree.AST_NODE_TYPES.Identifier
|
|
131
|
+
&& options.supportedInterfaceNames.includes(ext.expression.name))) {
|
|
132
|
+
return context.report({
|
|
133
|
+
messageId: 'notInConfigurationInterface',
|
|
134
|
+
node,
|
|
135
|
+
loc
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
if (!supportedO3rWidgets.has(widgetType)) {
|
|
139
|
+
return context.report({
|
|
140
|
+
messageId: 'notSupportedType',
|
|
141
|
+
node,
|
|
142
|
+
loc,
|
|
143
|
+
data: {
|
|
144
|
+
o3rWidgetType: widgetType,
|
|
145
|
+
supportedO3rWidgets: Array.from(supportedO3rWidgets).join(', ')
|
|
146
|
+
},
|
|
147
|
+
suggest: Array.from(supportedO3rWidgets).map((suggestedWidget) => ({
|
|
148
|
+
messageId: 'suggestReplaceO3rWidgetType',
|
|
149
|
+
data: {
|
|
150
|
+
currentType: widgetType,
|
|
151
|
+
suggestedType: suggestedWidget
|
|
152
|
+
},
|
|
153
|
+
fix: (fixer) => {
|
|
154
|
+
return fixer.replaceTextRange(comment.range, createCommentString(comment.value.replace(`@o3rWidget ${widgetType}`, `@o3rWidget ${suggestedWidget}`)));
|
|
155
|
+
}
|
|
156
|
+
}))
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
const widgetParameters = widgetParameterTexts.map((text) => {
|
|
160
|
+
const [name, ...values] = text.split(' ');
|
|
161
|
+
return {
|
|
162
|
+
name,
|
|
163
|
+
textValue: values.join(' ')
|
|
164
|
+
};
|
|
165
|
+
});
|
|
166
|
+
const widgetParameterNames = widgetParameters.map(({ name }) => name);
|
|
167
|
+
const supportedO3rWidgetParam = new Set(Object.keys(options.widgets[widgetType]));
|
|
168
|
+
const checkedParam = new Set();
|
|
169
|
+
for (const widgetParameterName of widgetParameterNames) {
|
|
170
|
+
if (checkedParam.has(widgetParameterName)) {
|
|
171
|
+
const fix = (fixer) => {
|
|
172
|
+
return fixer.replaceTextRange(comment.range, createCommentString(comment.value.replace(/(.*(@o3rWidgetParam ).*(\n.*)*)(\n.*)\2.*/m, '$1')));
|
|
173
|
+
};
|
|
174
|
+
return context.report({
|
|
175
|
+
messageId: 'duplicatedParam',
|
|
176
|
+
node,
|
|
177
|
+
loc,
|
|
178
|
+
data: {
|
|
179
|
+
o3rWidgetParam: widgetParameterName
|
|
180
|
+
},
|
|
181
|
+
fix,
|
|
182
|
+
suggest: [{
|
|
183
|
+
messageId: 'suggestRemoveDuplicatedO3rWidget',
|
|
184
|
+
fix
|
|
185
|
+
}]
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (!supportedO3rWidgetParam.has(widgetParameterName)) {
|
|
189
|
+
return context.report({
|
|
190
|
+
messageId: 'notSupportedParamForType',
|
|
191
|
+
node,
|
|
192
|
+
loc,
|
|
193
|
+
data: {
|
|
194
|
+
o3rWidgetParam: widgetParameterName,
|
|
195
|
+
o3rWidgetType: widgetType,
|
|
196
|
+
supportedO3rWidgetParam: Array.from(supportedO3rWidgetParam).join(', ')
|
|
197
|
+
},
|
|
198
|
+
suggest: Array.from(supportedO3rWidgetParam).map((suggestedParam) => ({
|
|
199
|
+
messageId: 'suggestReplaceO3rWidgetType',
|
|
200
|
+
data: {
|
|
201
|
+
currentType: widgetType,
|
|
202
|
+
suggestedType: suggestedParam
|
|
203
|
+
},
|
|
204
|
+
fix: (fixer) => {
|
|
205
|
+
return fixer.replaceTextRange(comment.range, createCommentString(comment.value.replace(`@o3rWidgetParam ${widgetType}`, `@o3rWidgetParam ${suggestedParam}`)));
|
|
206
|
+
}
|
|
207
|
+
}))
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
checkedParam.add(widgetParameterName);
|
|
211
|
+
}
|
|
212
|
+
const firstRequiredParam = Object.entries(options.widgets[widgetType]).find(([param, { required }]) => required && !checkedParam.has(param));
|
|
213
|
+
if (firstRequiredParam) {
|
|
214
|
+
const [firstRequiredParamName] = firstRequiredParam;
|
|
215
|
+
const fix = (fixer) => {
|
|
216
|
+
return fixer.replaceTextRange(comment.range, createCommentString(comment.value.replace(/((.*)@o3rWidget (.*))/, `$1\n$2@o3rWidgetParam ${firstRequiredParamName} value`)));
|
|
217
|
+
};
|
|
218
|
+
return context.report({
|
|
219
|
+
messageId: 'requiredParamMissing',
|
|
220
|
+
node,
|
|
221
|
+
loc,
|
|
222
|
+
data: {
|
|
223
|
+
o3rWidgetParam: firstRequiredParamName,
|
|
224
|
+
o3rWidgetType: widgetType
|
|
225
|
+
},
|
|
226
|
+
fix,
|
|
227
|
+
suggest: [{
|
|
228
|
+
messageId: 'suggestParamMissing',
|
|
229
|
+
data: {
|
|
230
|
+
o3rWidgetParam: firstRequiredParamName
|
|
231
|
+
},
|
|
232
|
+
fix
|
|
233
|
+
}]
|
|
234
|
+
});
|
|
235
|
+
}
|
|
236
|
+
for (const widgetParameter of widgetParameters) {
|
|
237
|
+
const { name, textValue } = widgetParameter;
|
|
238
|
+
const supportedTypeForParam = options.widgets[widgetType][name];
|
|
239
|
+
try {
|
|
240
|
+
const value = JSON.parse(textValue);
|
|
241
|
+
if (supportedTypeForParam.type.endsWith('[]')) {
|
|
242
|
+
if (Array.isArray(value) && value.every((element) => typeof element === supportedTypeForParam.type.substring(0, -2))) {
|
|
243
|
+
continue;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
else if (typeof value === supportedTypeForParam.type) {
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch { }
|
|
251
|
+
return context.report({
|
|
252
|
+
messageId: 'invalidParamValueType',
|
|
253
|
+
node,
|
|
254
|
+
loc,
|
|
255
|
+
data: {
|
|
256
|
+
o3rWidgetParam: name,
|
|
257
|
+
o3rWidgetParamType: supportedTypeForParam.type
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
};
|
|
263
|
+
}
|
|
264
|
+
});
|