eslint-plugin-jsdoc 48.0.0 → 48.0.1
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 +1 -1
- package/src/WarnSettings.js +34 -0
- package/src/alignTransform.js +356 -0
- package/src/defaultTagOrder.js +168 -0
- package/src/exportParser.js +957 -0
- package/src/getDefaultTagStructureForMode.js +969 -0
- package/src/index.js +266 -0
- package/src/iterateJsdoc.js +2555 -0
- package/src/jsdocUtils.js +1693 -0
- package/src/rules/checkAccess.js +45 -0
- package/src/rules/checkAlignment.js +63 -0
- package/src/rules/checkExamples.js +594 -0
- package/src/rules/checkIndentation.js +75 -0
- package/src/rules/checkLineAlignment.js +364 -0
- package/src/rules/checkParamNames.js +404 -0
- package/src/rules/checkPropertyNames.js +152 -0
- package/src/rules/checkSyntax.js +30 -0
- package/src/rules/checkTagNames.js +314 -0
- package/src/rules/checkTypes.js +535 -0
- package/src/rules/checkValues.js +220 -0
- package/src/rules/emptyTags.js +88 -0
- package/src/rules/implementsOnClasses.js +64 -0
- package/src/rules/importsAsDependencies.js +131 -0
- package/src/rules/informativeDocs.js +182 -0
- package/src/rules/matchDescription.js +286 -0
- package/src/rules/matchName.js +147 -0
- package/src/rules/multilineBlocks.js +333 -0
- package/src/rules/noBadBlocks.js +109 -0
- package/src/rules/noBlankBlockDescriptions.js +69 -0
- package/src/rules/noBlankBlocks.js +53 -0
- package/src/rules/noDefaults.js +85 -0
- package/src/rules/noMissingSyntax.js +195 -0
- package/src/rules/noMultiAsterisks.js +134 -0
- package/src/rules/noRestrictedSyntax.js +91 -0
- package/src/rules/noTypes.js +73 -0
- package/src/rules/noUndefinedTypes.js +328 -0
- package/src/rules/requireAsteriskPrefix.js +189 -0
- package/src/rules/requireDescription.js +161 -0
- package/src/rules/requireDescriptionCompleteSentence.js +333 -0
- package/src/rules/requireExample.js +118 -0
- package/src/rules/requireFileOverview.js +154 -0
- package/src/rules/requireHyphenBeforeParamDescription.js +178 -0
- package/src/rules/requireJsdoc.js +629 -0
- package/src/rules/requireParam.js +592 -0
- package/src/rules/requireParamDescription.js +89 -0
- package/src/rules/requireParamName.js +55 -0
- package/src/rules/requireParamType.js +89 -0
- package/src/rules/requireProperty.js +48 -0
- package/src/rules/requirePropertyDescription.js +25 -0
- package/src/rules/requirePropertyName.js +25 -0
- package/src/rules/requirePropertyType.js +25 -0
- package/src/rules/requireReturns.js +238 -0
- package/src/rules/requireReturnsCheck.js +141 -0
- package/src/rules/requireReturnsDescription.js +59 -0
- package/src/rules/requireReturnsType.js +51 -0
- package/src/rules/requireThrows.js +111 -0
- package/src/rules/requireYields.js +216 -0
- package/src/rules/requireYieldsCheck.js +208 -0
- package/src/rules/sortTags.js +557 -0
- package/src/rules/tagLines.js +359 -0
- package/src/rules/textEscaping.js +146 -0
- package/src/rules/validTypes.js +368 -0
- package/src/tagNames.js +234 -0
- package/src/utils/hasReturnValue.js +549 -0
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
import iterateJsdoc from '../iterateJsdoc.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} targetTagName
|
|
5
|
+
* @param {boolean} allowExtraTrailingParamDocs
|
|
6
|
+
* @param {boolean} checkDestructured
|
|
7
|
+
* @param {boolean} checkRestProperty
|
|
8
|
+
* @param {RegExp} checkTypesRegex
|
|
9
|
+
* @param {boolean} disableExtraPropertyReporting
|
|
10
|
+
* @param {boolean} enableFixer
|
|
11
|
+
* @param {import('../jsdocUtils.js').ParamNameInfo[]} functionParameterNames
|
|
12
|
+
* @param {import('comment-parser').Block} jsdoc
|
|
13
|
+
* @param {import('../iterateJsdoc.js').Utils} utils
|
|
14
|
+
* @param {import('../iterateJsdoc.js').Report} report
|
|
15
|
+
* @returns {boolean}
|
|
16
|
+
*/
|
|
17
|
+
const validateParameterNames = (
|
|
18
|
+
targetTagName,
|
|
19
|
+
allowExtraTrailingParamDocs,
|
|
20
|
+
checkDestructured,
|
|
21
|
+
checkRestProperty,
|
|
22
|
+
checkTypesRegex,
|
|
23
|
+
disableExtraPropertyReporting,
|
|
24
|
+
enableFixer,
|
|
25
|
+
functionParameterNames, jsdoc, utils, report,
|
|
26
|
+
) => {
|
|
27
|
+
const paramTags = Object.entries(jsdoc.tags).filter(([
|
|
28
|
+
, tag,
|
|
29
|
+
]) => {
|
|
30
|
+
return tag.tag === targetTagName;
|
|
31
|
+
});
|
|
32
|
+
const paramTagsNonNested = paramTags.filter(([
|
|
33
|
+
, tag,
|
|
34
|
+
]) => {
|
|
35
|
+
return !tag.name.includes('.');
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
let dotted = 0;
|
|
39
|
+
let thisOffset = 0;
|
|
40
|
+
|
|
41
|
+
// eslint-disable-next-line complexity
|
|
42
|
+
return paramTags.some(([
|
|
43
|
+
, tag,
|
|
44
|
+
], index) => {
|
|
45
|
+
/** @type {import('../iterateJsdoc.js').Integer} */
|
|
46
|
+
let tagsIndex;
|
|
47
|
+
const dupeTagInfo = paramTags.find(([
|
|
48
|
+
tgsIndex,
|
|
49
|
+
tg,
|
|
50
|
+
], idx) => {
|
|
51
|
+
tagsIndex = Number(tgsIndex);
|
|
52
|
+
|
|
53
|
+
return tg.name === tag.name && idx !== index;
|
|
54
|
+
});
|
|
55
|
+
if (dupeTagInfo) {
|
|
56
|
+
utils.reportJSDoc(`Duplicate @${targetTagName} "${tag.name}"`, dupeTagInfo[1], enableFixer ? () => {
|
|
57
|
+
utils.removeTag(tagsIndex);
|
|
58
|
+
} : null);
|
|
59
|
+
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (tag.name.includes('.')) {
|
|
64
|
+
dotted++;
|
|
65
|
+
|
|
66
|
+
return false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let functionParameterName = functionParameterNames[index - dotted + thisOffset];
|
|
70
|
+
if (functionParameterName === 'this' && tag.name.trim() !== 'this') {
|
|
71
|
+
++thisOffset;
|
|
72
|
+
functionParameterName = functionParameterNames[index - dotted + thisOffset];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!functionParameterName) {
|
|
76
|
+
if (allowExtraTrailingParamDocs) {
|
|
77
|
+
return false;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
report(
|
|
81
|
+
`@${targetTagName} "${tag.name}" does not match an existing function parameter.`,
|
|
82
|
+
null,
|
|
83
|
+
tag,
|
|
84
|
+
);
|
|
85
|
+
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (Array.isArray(functionParameterName)) {
|
|
90
|
+
if (!checkDestructured) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
if (tag.type && tag.type.search(checkTypesRegex) === -1) {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const [
|
|
99
|
+
parameterName,
|
|
100
|
+
{
|
|
101
|
+
names: properties,
|
|
102
|
+
hasPropertyRest,
|
|
103
|
+
rests,
|
|
104
|
+
annotationParamName,
|
|
105
|
+
},
|
|
106
|
+
] =
|
|
107
|
+
/**
|
|
108
|
+
* @type {[string | undefined, import('../jsdocUtils.js').FlattendRootInfo & {
|
|
109
|
+
* annotationParamName?: string | undefined;
|
|
110
|
+
}]} */ (functionParameterName);
|
|
111
|
+
if (annotationParamName !== undefined) {
|
|
112
|
+
const name = tag.name.trim();
|
|
113
|
+
if (name !== annotationParamName) {
|
|
114
|
+
report(`@${targetTagName} "${name}" does not match parameter name "${annotationParamName}"`, null, tag);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const tagName = parameterName === undefined ? tag.name.trim() : parameterName;
|
|
119
|
+
const expectedNames = properties.map((name) => {
|
|
120
|
+
return `${tagName}.${name}`;
|
|
121
|
+
});
|
|
122
|
+
const actualNames = paramTags.map(([
|
|
123
|
+
, paramTag,
|
|
124
|
+
]) => {
|
|
125
|
+
return paramTag.name.trim();
|
|
126
|
+
});
|
|
127
|
+
const actualTypes = paramTags.map(([
|
|
128
|
+
, paramTag,
|
|
129
|
+
]) => {
|
|
130
|
+
return paramTag.type;
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const missingProperties = [];
|
|
134
|
+
|
|
135
|
+
/** @type {string[]} */
|
|
136
|
+
const notCheckingNames = [];
|
|
137
|
+
|
|
138
|
+
for (const [
|
|
139
|
+
idx,
|
|
140
|
+
name,
|
|
141
|
+
] of expectedNames.entries()) {
|
|
142
|
+
if (notCheckingNames.some((notCheckingName) => {
|
|
143
|
+
return name.startsWith(notCheckingName);
|
|
144
|
+
})) {
|
|
145
|
+
continue;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const actualNameIdx = actualNames.findIndex((actualName) => {
|
|
149
|
+
return utils.comparePaths(name)(actualName);
|
|
150
|
+
});
|
|
151
|
+
if (actualNameIdx === -1) {
|
|
152
|
+
if (!checkRestProperty && rests[idx]) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const missingIndex = actualNames.findIndex((actualName) => {
|
|
157
|
+
return utils.pathDoesNotBeginWith(name, actualName);
|
|
158
|
+
});
|
|
159
|
+
const line = tag.source[0].number - 1 + (missingIndex > -1 ? missingIndex : actualNames.length);
|
|
160
|
+
missingProperties.push({
|
|
161
|
+
name,
|
|
162
|
+
tagPlacement: {
|
|
163
|
+
line: line === 0 ? 1 : line,
|
|
164
|
+
},
|
|
165
|
+
});
|
|
166
|
+
} else if (actualTypes[actualNameIdx].search(checkTypesRegex) === -1 && actualTypes[actualNameIdx] !== '') {
|
|
167
|
+
notCheckingNames.push(name);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const hasMissing = missingProperties.length;
|
|
172
|
+
if (hasMissing) {
|
|
173
|
+
for (const {
|
|
174
|
+
tagPlacement,
|
|
175
|
+
name: missingProperty,
|
|
176
|
+
} of missingProperties) {
|
|
177
|
+
report(`Missing @${targetTagName} "${missingProperty}"`, null, tagPlacement);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
if (!hasPropertyRest || checkRestProperty) {
|
|
182
|
+
/** @type {[string, import('comment-parser').Spec][]} */
|
|
183
|
+
const extraProperties = [];
|
|
184
|
+
for (const [
|
|
185
|
+
idx,
|
|
186
|
+
name,
|
|
187
|
+
] of actualNames.entries()) {
|
|
188
|
+
const match = name.startsWith(tag.name.trim() + '.');
|
|
189
|
+
if (
|
|
190
|
+
match && !expectedNames.some(
|
|
191
|
+
utils.comparePaths(name),
|
|
192
|
+
) && !utils.comparePaths(name)(tag.name) &&
|
|
193
|
+
(!disableExtraPropertyReporting || properties.some((prop) => {
|
|
194
|
+
return prop.split('.').length >= name.split('.').length - 1;
|
|
195
|
+
}))
|
|
196
|
+
) {
|
|
197
|
+
extraProperties.push([
|
|
198
|
+
name, paramTags[idx][1],
|
|
199
|
+
]);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
if (extraProperties.length) {
|
|
204
|
+
for (const [
|
|
205
|
+
extraProperty,
|
|
206
|
+
tg,
|
|
207
|
+
] of extraProperties) {
|
|
208
|
+
report(`@${targetTagName} "${extraProperty}" does not exist on ${tag.name}`, null, tg);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
return hasMissing;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
let funcParamName;
|
|
219
|
+
if (typeof functionParameterName === 'object') {
|
|
220
|
+
const {
|
|
221
|
+
name,
|
|
222
|
+
} = functionParameterName;
|
|
223
|
+
funcParamName = name;
|
|
224
|
+
} else {
|
|
225
|
+
funcParamName = functionParameterName;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
if (funcParamName !== tag.name.trim()) {
|
|
229
|
+
// Todo: Improve for array or object child items
|
|
230
|
+
const actualNames = paramTagsNonNested.map(([
|
|
231
|
+
, {
|
|
232
|
+
name,
|
|
233
|
+
},
|
|
234
|
+
]) => {
|
|
235
|
+
return name.trim();
|
|
236
|
+
});
|
|
237
|
+
const expectedNames = functionParameterNames.map((item, idx) => {
|
|
238
|
+
if (/**
|
|
239
|
+
* @type {[string|undefined, (import('../jsdocUtils.js').FlattendRootInfo & {
|
|
240
|
+
* annotationParamName?: string,
|
|
241
|
+
})]} */ (item)?.[1]?.names) {
|
|
242
|
+
return actualNames[idx];
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return item;
|
|
246
|
+
}).filter((item) => {
|
|
247
|
+
return item !== 'this';
|
|
248
|
+
}).join(', ');
|
|
249
|
+
|
|
250
|
+
report(
|
|
251
|
+
`Expected @${targetTagName} names to be "${expectedNames}". Got "${actualNames.join(', ')}".`,
|
|
252
|
+
null,
|
|
253
|
+
tag,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
return true;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
return false;
|
|
260
|
+
});
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* @param {string} targetTagName
|
|
265
|
+
* @param {boolean} _allowExtraTrailingParamDocs
|
|
266
|
+
* @param {{
|
|
267
|
+
* name: string,
|
|
268
|
+
* idx: import('../iterateJsdoc.js').Integer
|
|
269
|
+
* }[]} jsdocParameterNames
|
|
270
|
+
* @param {import('comment-parser').Block} jsdoc
|
|
271
|
+
* @param {Function} report
|
|
272
|
+
* @returns {boolean}
|
|
273
|
+
*/
|
|
274
|
+
const validateParameterNamesDeep = (
|
|
275
|
+
targetTagName, _allowExtraTrailingParamDocs,
|
|
276
|
+
jsdocParameterNames, jsdoc, report,
|
|
277
|
+
) => {
|
|
278
|
+
/** @type {string} */
|
|
279
|
+
let lastRealParameter;
|
|
280
|
+
|
|
281
|
+
return jsdocParameterNames.some(({
|
|
282
|
+
name: jsdocParameterName,
|
|
283
|
+
idx,
|
|
284
|
+
}) => {
|
|
285
|
+
const isPropertyPath = jsdocParameterName.includes('.');
|
|
286
|
+
|
|
287
|
+
if (isPropertyPath) {
|
|
288
|
+
if (!lastRealParameter) {
|
|
289
|
+
report(`@${targetTagName} path declaration ("${jsdocParameterName}") appears before any real parameter.`, null, jsdoc.tags[idx]);
|
|
290
|
+
|
|
291
|
+
return true;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
let pathRootNodeName = jsdocParameterName.slice(0, jsdocParameterName.indexOf('.'));
|
|
295
|
+
|
|
296
|
+
if (pathRootNodeName.endsWith('[]')) {
|
|
297
|
+
pathRootNodeName = pathRootNodeName.slice(0, -2);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
if (pathRootNodeName !== lastRealParameter) {
|
|
301
|
+
report(
|
|
302
|
+
`@${targetTagName} path declaration ("${jsdocParameterName}") root node name ("${pathRootNodeName}") ` +
|
|
303
|
+
`does not match previous real parameter name ("${lastRealParameter}").`,
|
|
304
|
+
null,
|
|
305
|
+
jsdoc.tags[idx],
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
} else {
|
|
311
|
+
lastRealParameter = jsdocParameterName;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return false;
|
|
315
|
+
});
|
|
316
|
+
};
|
|
317
|
+
|
|
318
|
+
export default iterateJsdoc(({
|
|
319
|
+
context,
|
|
320
|
+
jsdoc,
|
|
321
|
+
report,
|
|
322
|
+
utils,
|
|
323
|
+
}) => {
|
|
324
|
+
const {
|
|
325
|
+
allowExtraTrailingParamDocs,
|
|
326
|
+
checkDestructured = true,
|
|
327
|
+
checkRestProperty = false,
|
|
328
|
+
checkTypesPattern = '/^(?:[oO]bject|[aA]rray|PlainObject|Generic(?:Object|Array))$/',
|
|
329
|
+
enableFixer = false,
|
|
330
|
+
useDefaultObjectProperties = false,
|
|
331
|
+
disableExtraPropertyReporting = false,
|
|
332
|
+
} = context.options[0] || {};
|
|
333
|
+
|
|
334
|
+
const checkTypesRegex = utils.getRegexFromString(checkTypesPattern);
|
|
335
|
+
|
|
336
|
+
const jsdocParameterNamesDeep = utils.getJsdocTagsDeep('param');
|
|
337
|
+
if (!jsdocParameterNamesDeep || !jsdocParameterNamesDeep.length) {
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
const functionParameterNames = utils.getFunctionParameterNames(useDefaultObjectProperties);
|
|
342
|
+
const targetTagName = /** @type {string} */ (utils.getPreferredTagName({
|
|
343
|
+
tagName: 'param',
|
|
344
|
+
}));
|
|
345
|
+
const isError = validateParameterNames(
|
|
346
|
+
targetTagName,
|
|
347
|
+
allowExtraTrailingParamDocs,
|
|
348
|
+
checkDestructured,
|
|
349
|
+
checkRestProperty,
|
|
350
|
+
checkTypesRegex,
|
|
351
|
+
disableExtraPropertyReporting,
|
|
352
|
+
enableFixer,
|
|
353
|
+
functionParameterNames,
|
|
354
|
+
jsdoc,
|
|
355
|
+
utils,
|
|
356
|
+
report,
|
|
357
|
+
);
|
|
358
|
+
|
|
359
|
+
if (isError || !checkDestructured) {
|
|
360
|
+
return;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
validateParameterNamesDeep(
|
|
364
|
+
targetTagName, allowExtraTrailingParamDocs, jsdocParameterNamesDeep, jsdoc, report,
|
|
365
|
+
);
|
|
366
|
+
}, {
|
|
367
|
+
meta: {
|
|
368
|
+
docs: {
|
|
369
|
+
description: 'Ensures that parameter names in JSDoc match those in the function declaration.',
|
|
370
|
+
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-param-names.md#repos-sticky-header',
|
|
371
|
+
},
|
|
372
|
+
fixable: 'code',
|
|
373
|
+
schema: [
|
|
374
|
+
{
|
|
375
|
+
additionalProperties: false,
|
|
376
|
+
properties: {
|
|
377
|
+
allowExtraTrailingParamDocs: {
|
|
378
|
+
type: 'boolean',
|
|
379
|
+
},
|
|
380
|
+
checkDestructured: {
|
|
381
|
+
type: 'boolean',
|
|
382
|
+
},
|
|
383
|
+
checkRestProperty: {
|
|
384
|
+
type: 'boolean',
|
|
385
|
+
},
|
|
386
|
+
checkTypesPattern: {
|
|
387
|
+
type: 'string',
|
|
388
|
+
},
|
|
389
|
+
disableExtraPropertyReporting: {
|
|
390
|
+
type: 'boolean',
|
|
391
|
+
},
|
|
392
|
+
enableFixer: {
|
|
393
|
+
type: 'boolean',
|
|
394
|
+
},
|
|
395
|
+
useDefaultObjectProperties: {
|
|
396
|
+
type: 'boolean',
|
|
397
|
+
},
|
|
398
|
+
},
|
|
399
|
+
type: 'object',
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
type: 'suggestion',
|
|
403
|
+
},
|
|
404
|
+
});
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import iterateJsdoc from '../iterateJsdoc.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {string} targetTagName
|
|
5
|
+
* @param {boolean} enableFixer
|
|
6
|
+
* @param {import('comment-parser').Block} jsdoc
|
|
7
|
+
* @param {import('../iterateJsdoc.js').Utils} utils
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
const validatePropertyNames = (
|
|
11
|
+
targetTagName,
|
|
12
|
+
enableFixer,
|
|
13
|
+
jsdoc, utils,
|
|
14
|
+
) => {
|
|
15
|
+
const propertyTags = Object.entries(jsdoc.tags).filter(([
|
|
16
|
+
, tag,
|
|
17
|
+
]) => {
|
|
18
|
+
return tag.tag === targetTagName;
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
return propertyTags.some(([
|
|
22
|
+
, tag,
|
|
23
|
+
], index) => {
|
|
24
|
+
/** @type {import('../iterateJsdoc.js').Integer} */
|
|
25
|
+
let tagsIndex;
|
|
26
|
+
const dupeTagInfo = propertyTags.find(([
|
|
27
|
+
tgsIndex,
|
|
28
|
+
tg,
|
|
29
|
+
], idx) => {
|
|
30
|
+
tagsIndex = Number(tgsIndex);
|
|
31
|
+
|
|
32
|
+
return tg.name === tag.name && idx !== index;
|
|
33
|
+
});
|
|
34
|
+
if (dupeTagInfo) {
|
|
35
|
+
utils.reportJSDoc(`Duplicate @${targetTagName} "${tag.name}"`, dupeTagInfo[1], enableFixer ? () => {
|
|
36
|
+
utils.removeTag(tagsIndex);
|
|
37
|
+
} : null);
|
|
38
|
+
|
|
39
|
+
return true;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return false;
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {string} targetTagName
|
|
48
|
+
* @param {{
|
|
49
|
+
* idx: number;
|
|
50
|
+
* name: string;
|
|
51
|
+
* type: string;
|
|
52
|
+
* }[]} jsdocPropertyNames
|
|
53
|
+
* @param {import('comment-parser').Block} jsdoc
|
|
54
|
+
* @param {Function} report
|
|
55
|
+
*/
|
|
56
|
+
const validatePropertyNamesDeep = (
|
|
57
|
+
targetTagName,
|
|
58
|
+
jsdocPropertyNames, jsdoc, report,
|
|
59
|
+
) => {
|
|
60
|
+
/** @type {string} */
|
|
61
|
+
let lastRealProperty;
|
|
62
|
+
|
|
63
|
+
return jsdocPropertyNames.some(({
|
|
64
|
+
name: jsdocPropertyName,
|
|
65
|
+
idx,
|
|
66
|
+
}) => {
|
|
67
|
+
const isPropertyPath = jsdocPropertyName.includes('.');
|
|
68
|
+
|
|
69
|
+
if (isPropertyPath) {
|
|
70
|
+
if (!lastRealProperty) {
|
|
71
|
+
report(`@${targetTagName} path declaration ("${jsdocPropertyName}") appears before any real property.`, null, jsdoc.tags[idx]);
|
|
72
|
+
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
let pathRootNodeName = jsdocPropertyName.slice(0, jsdocPropertyName.indexOf('.'));
|
|
77
|
+
|
|
78
|
+
if (pathRootNodeName.endsWith('[]')) {
|
|
79
|
+
pathRootNodeName = pathRootNodeName.slice(0, -2);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (pathRootNodeName !== lastRealProperty) {
|
|
83
|
+
report(
|
|
84
|
+
`@${targetTagName} path declaration ("${jsdocPropertyName}") root node name ("${pathRootNodeName}") ` +
|
|
85
|
+
`does not match previous real property name ("${lastRealProperty}").`,
|
|
86
|
+
null,
|
|
87
|
+
jsdoc.tags[idx],
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
lastRealProperty = jsdocPropertyName;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return false;
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
|
|
100
|
+
export default iterateJsdoc(({
|
|
101
|
+
context,
|
|
102
|
+
jsdoc,
|
|
103
|
+
report,
|
|
104
|
+
utils,
|
|
105
|
+
}) => {
|
|
106
|
+
const {
|
|
107
|
+
enableFixer = false,
|
|
108
|
+
} = context.options[0] || {};
|
|
109
|
+
const jsdocPropertyNamesDeep = utils.getJsdocTagsDeep('property');
|
|
110
|
+
if (!jsdocPropertyNamesDeep || !jsdocPropertyNamesDeep.length) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const targetTagName = /** @type {string} */ (utils.getPreferredTagName({
|
|
115
|
+
tagName: 'property',
|
|
116
|
+
}));
|
|
117
|
+
const isError = validatePropertyNames(
|
|
118
|
+
targetTagName,
|
|
119
|
+
enableFixer,
|
|
120
|
+
jsdoc,
|
|
121
|
+
utils,
|
|
122
|
+
);
|
|
123
|
+
|
|
124
|
+
if (isError) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
validatePropertyNamesDeep(
|
|
129
|
+
targetTagName, jsdocPropertyNamesDeep, jsdoc, report,
|
|
130
|
+
);
|
|
131
|
+
}, {
|
|
132
|
+
iterateAllJsdocs: true,
|
|
133
|
+
meta: {
|
|
134
|
+
docs: {
|
|
135
|
+
description: 'Ensures that property names in JSDoc are not duplicated on the same block and that nested properties have defined roots.',
|
|
136
|
+
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-property-names.md#repos-sticky-header',
|
|
137
|
+
},
|
|
138
|
+
fixable: 'code',
|
|
139
|
+
schema: [
|
|
140
|
+
{
|
|
141
|
+
additionalProperties: false,
|
|
142
|
+
properties: {
|
|
143
|
+
enableFixer: {
|
|
144
|
+
type: 'boolean',
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
type: 'object',
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
type: 'suggestion',
|
|
151
|
+
},
|
|
152
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import iterateJsdoc from '../iterateJsdoc.js';
|
|
2
|
+
|
|
3
|
+
export default iterateJsdoc(({
|
|
4
|
+
jsdoc,
|
|
5
|
+
report,
|
|
6
|
+
settings,
|
|
7
|
+
}) => {
|
|
8
|
+
const {
|
|
9
|
+
mode,
|
|
10
|
+
} = settings;
|
|
11
|
+
|
|
12
|
+
// Don't check for "permissive" and "closure"
|
|
13
|
+
if (mode === 'jsdoc' || mode === 'typescript') {
|
|
14
|
+
for (const tag of jsdoc.tags) {
|
|
15
|
+
if (tag.type.slice(-1) === '=') {
|
|
16
|
+
report('Syntax should not be Google Closure Compiler style.', null, tag);
|
|
17
|
+
break;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}, {
|
|
22
|
+
iterateAllJsdocs: true,
|
|
23
|
+
meta: {
|
|
24
|
+
docs: {
|
|
25
|
+
description: 'Reports against syntax not valid for the mode (e.g., Google Closure Compiler in non-Closure mode).',
|
|
26
|
+
url: 'https://github.com/gajus/eslint-plugin-jsdoc/blob/main/docs/rules/check-syntax.md#repos-sticky-header',
|
|
27
|
+
},
|
|
28
|
+
type: 'suggestion',
|
|
29
|
+
},
|
|
30
|
+
});
|