eslint-plugin-jsdoc 48.8.3 → 48.9.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.
@@ -0,0 +1,602 @@
1
+ // Todo: Support TS by fenced block type
2
+
3
+ import {readFileSync} from 'fs';
4
+ import * as espree from 'espree';
5
+ import {
6
+ getRegexFromString,
7
+ forEachPreferredTag,
8
+ getTagDescription,
9
+ getPreferredTagName,
10
+ hasTag,
11
+ } from './jsdocUtils.js';
12
+ import {
13
+ parseComment,
14
+ } from '@es-joy/jsdoccomment';
15
+
16
+ const {version} = JSON.parse(
17
+ // @ts-expect-error `Buffer` is ok for `JSON.parse`
18
+ readFileSync('./package.json')
19
+ );
20
+
21
+ // const zeroBasedLineIndexAdjust = -1;
22
+ const likelyNestedJSDocIndentSpace = 1;
23
+ const preTagSpaceLength = 1;
24
+
25
+ // If a space is present, we should ignore it
26
+ const firstLinePrefixLength = preTagSpaceLength;
27
+
28
+ const hasCaptionRegex = /^\s*<caption>([\s\S]*?)<\/caption>/u;
29
+
30
+ /**
31
+ * @param {string} str
32
+ * @returns {string}
33
+ */
34
+ const escapeStringRegexp = (str) => {
35
+ return str.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&');
36
+ };
37
+
38
+ /**
39
+ * @param {string} str
40
+ * @param {string} ch
41
+ * @returns {import('./iterateJsdoc.js').Integer}
42
+ */
43
+ const countChars = (str, ch) => {
44
+ return (str.match(new RegExp(escapeStringRegexp(ch), 'gu')) || []).length;
45
+ };
46
+
47
+ /**
48
+ * @param {string} text
49
+ * @returns {[
50
+ * import('./iterateJsdoc.js').Integer,
51
+ * import('./iterateJsdoc.js').Integer
52
+ * ]}
53
+ */
54
+ const getLinesCols = (text) => {
55
+ const matchLines = countChars(text, '\n');
56
+
57
+ const colDelta = matchLines ?
58
+ text.slice(text.lastIndexOf('\n') + 1).length :
59
+ text.length;
60
+
61
+ return [
62
+ matchLines, colDelta,
63
+ ];
64
+ };
65
+
66
+ /**
67
+ * @typedef {number} Integer
68
+ */
69
+
70
+ /**
71
+ * @typedef {object} JsdocProcessorOptions
72
+ * @property {boolean} [captionRequired]
73
+ * @property {Integer} [paddedIndent]
74
+ * @property {boolean} [checkDefaults]
75
+ * @property {boolean} [checkParams]
76
+ * @property {boolean} [checkExamples]
77
+ * @property {boolean} [checkProperties]
78
+ * @property {string} [matchingFileName]
79
+ * @property {string} [matchingFileNameDefaults]
80
+ * @property {string} [matchingFileNameParams]
81
+ * @property {string} [matchingFileNameProperties]
82
+ * @property {string} [exampleCodeRegex]
83
+ * @property {string} [rejectExampleCodeRegex]
84
+ * @property {"script"|"module"} [sourceType]
85
+ * @property {import('eslint').Linter.FlatConfigParserModule} [parser]
86
+ */
87
+
88
+ /**
89
+ * We use a function for the ability of the user to pass in a config, but
90
+ * without requiring all users of the plugin to do so.
91
+ * @param {JsdocProcessorOptions} [options]
92
+ */
93
+ export const getJsdocProcessorPlugin = (options = {}) => {
94
+ const {
95
+ exampleCodeRegex = null,
96
+ rejectExampleCodeRegex = null,
97
+ checkExamples = true,
98
+ checkDefaults = false,
99
+ checkParams = false,
100
+ checkProperties = false,
101
+ matchingFileName = null,
102
+ matchingFileNameDefaults = null,
103
+ matchingFileNameParams = null,
104
+ matchingFileNameProperties = null,
105
+ paddedIndent = 0,
106
+ captionRequired = false,
107
+ sourceType = 'module',
108
+ parser = undefined
109
+ } = options;
110
+
111
+ /** @type {RegExp} */
112
+ let exampleCodeRegExp;
113
+ /** @type {RegExp} */
114
+ let rejectExampleCodeRegExp;
115
+
116
+ if (exampleCodeRegex) {
117
+ exampleCodeRegExp = getRegexFromString(exampleCodeRegex);
118
+ }
119
+
120
+ if (rejectExampleCodeRegex) {
121
+ rejectExampleCodeRegExp = getRegexFromString(rejectExampleCodeRegex);
122
+ }
123
+
124
+ /**
125
+ * @type {{
126
+ * targetTagName: string,
127
+ * ext: string,
128
+ * codeStartLine: number,
129
+ * codeStartCol: number,
130
+ * nonJSPrefacingCols: number,
131
+ * commentLineCols: [number, number]
132
+ * }[]}
133
+ */
134
+ const otherInfo = [];
135
+
136
+ /** @type {import('eslint').Linter.LintMessage[]} */
137
+ let extraMessages = [];
138
+
139
+ /**
140
+ * @param {import('./iterateJsdoc.js').JsdocBlockWithInline} jsdoc
141
+ * @param {string} jsFileName
142
+ * @param {[number, number]} commentLineCols
143
+ */
144
+ const getTextsAndFileNames = (jsdoc, jsFileName, commentLineCols) => {
145
+ /**
146
+ * @type {{
147
+ * text: string,
148
+ * filename: string|null|undefined
149
+ * }[]}
150
+ */
151
+ const textsAndFileNames = [];
152
+
153
+ /**
154
+ * @param {{
155
+ * filename: string|null,
156
+ * defaultFileName: string|undefined,
157
+ * source: string,
158
+ * targetTagName: string,
159
+ * rules?: import('eslint').Linter.RulesRecord|undefined,
160
+ * lines?: import('./iterateJsdoc.js').Integer,
161
+ * cols?: import('./iterateJsdoc.js').Integer,
162
+ * skipInit?: boolean,
163
+ * ext: string,
164
+ * sources?: {
165
+ * nonJSPrefacingCols: import('./iterateJsdoc.js').Integer,
166
+ * nonJSPrefacingLines: import('./iterateJsdoc.js').Integer,
167
+ * string: string,
168
+ * }[],
169
+ * tag?: import('comment-parser').Spec & {
170
+ * line?: import('./iterateJsdoc.js').Integer,
171
+ * }|{
172
+ * line: import('./iterateJsdoc.js').Integer,
173
+ * }
174
+ * }} cfg
175
+ */
176
+ const checkSource = ({
177
+ filename,
178
+ ext,
179
+ defaultFileName,
180
+ lines = 0,
181
+ cols = 0,
182
+ skipInit,
183
+ source,
184
+ targetTagName,
185
+ sources = [],
186
+ tag = {
187
+ line: 0,
188
+ },
189
+ }) => {
190
+ if (!skipInit) {
191
+ sources.push({
192
+ nonJSPrefacingCols: cols,
193
+ nonJSPrefacingLines: lines,
194
+ string: source,
195
+ });
196
+ }
197
+
198
+ /**
199
+ * @param {{
200
+ * nonJSPrefacingCols: import('./iterateJsdoc.js').Integer,
201
+ * nonJSPrefacingLines: import('./iterateJsdoc.js').Integer,
202
+ * string: string
203
+ * }} cfg
204
+ */
205
+ const addSourceInfo = function ({
206
+ nonJSPrefacingCols,
207
+ nonJSPrefacingLines,
208
+ string,
209
+ }) {
210
+ const src = paddedIndent ?
211
+ string.replaceAll(new RegExp(`(^|\n) {${paddedIndent}}(?!$)`, 'gu'), '\n') :
212
+ string;
213
+
214
+ // Programmatic ESLint API: https://eslint.org/docs/developer-guide/nodejs-api
215
+ const file = filename || defaultFileName;
216
+
217
+ if (!('line' in tag)) {
218
+ tag.line = tag.source[0].number;
219
+ }
220
+
221
+ // NOTE: `tag.line` can be 0 if of form `/** @tag ... */`
222
+ const codeStartLine = /**
223
+ * @type {import('comment-parser').Spec & {
224
+ * line: import('./iterateJsdoc.js').Integer,
225
+ * }}
226
+ */ (tag).line + nonJSPrefacingLines;
227
+ const codeStartCol = likelyNestedJSDocIndentSpace;
228
+
229
+ textsAndFileNames.push({
230
+ text: src,
231
+ filename: file,
232
+ });
233
+ otherInfo.push({
234
+ targetTagName,
235
+ ext,
236
+ codeStartLine,
237
+ codeStartCol,
238
+ nonJSPrefacingCols,
239
+ commentLineCols
240
+ });
241
+ };
242
+
243
+ for (const targetSource of sources) {
244
+ addSourceInfo(targetSource);
245
+ }
246
+ };
247
+
248
+ /**
249
+ *
250
+ * @param {string|null} filename
251
+ * @param {string} [ext] Since `eslint-plugin-markdown` v2, and
252
+ * ESLint 7, this is the default which other JS-fenced rules will used.
253
+ * Formerly "md" was the default.
254
+ * @returns {{
255
+ * defaultFileName: string|undefined,
256
+ * filename: string|null,
257
+ * ext: string
258
+ * }}
259
+ */
260
+ const getFilenameInfo = (filename, ext = 'md/*.js') => {
261
+ let defaultFileName;
262
+ if (!filename) {
263
+ if (typeof jsFileName === 'string' && jsFileName.includes('.')) {
264
+ defaultFileName = jsFileName.replace(/\.[^.]*$/u, `.${ext}`);
265
+ } else {
266
+ defaultFileName = `dummy.${ext}`;
267
+ }
268
+ }
269
+
270
+ return {
271
+ ext,
272
+ defaultFileName,
273
+ filename,
274
+ };
275
+ };
276
+
277
+ if (checkDefaults) {
278
+ const filenameInfo = getFilenameInfo(matchingFileNameDefaults, 'jsdoc-defaults');
279
+ forEachPreferredTag(jsdoc, 'default', (tag, targetTagName) => {
280
+ if (!tag.description.trim()) {
281
+ return;
282
+ }
283
+
284
+ checkSource({
285
+ source: `(${getTagDescription(tag)})`,
286
+ targetTagName,
287
+ ...filenameInfo,
288
+ });
289
+ });
290
+ }
291
+
292
+ if (checkParams) {
293
+ const filenameInfo = getFilenameInfo(matchingFileNameParams, 'jsdoc-params');
294
+ forEachPreferredTag(jsdoc, 'param', (tag, targetTagName) => {
295
+ if (!tag.default || !tag.default.trim()) {
296
+ return;
297
+ }
298
+
299
+ checkSource({
300
+ source: `(${tag.default})`,
301
+ targetTagName,
302
+ ...filenameInfo,
303
+ });
304
+ });
305
+ }
306
+
307
+ if (checkProperties) {
308
+ const filenameInfo = getFilenameInfo(matchingFileNameProperties, 'jsdoc-properties');
309
+ forEachPreferredTag(jsdoc, 'property', (tag, targetTagName) => {
310
+ if (!tag.default || !tag.default.trim()) {
311
+ return;
312
+ }
313
+
314
+ checkSource({
315
+ source: `(${tag.default})`,
316
+ targetTagName,
317
+ ...filenameInfo,
318
+ });
319
+ });
320
+ }
321
+
322
+ if (!checkExamples) {
323
+ return textsAndFileNames;
324
+ }
325
+
326
+ const tagName = /** @type {string} */ (getPreferredTagName(jsdoc, {
327
+ tagName: 'example',
328
+ }));
329
+ if (!hasTag(jsdoc, tagName)) {
330
+ return textsAndFileNames;
331
+ }
332
+
333
+ const matchingFilenameInfo = getFilenameInfo(matchingFileName);
334
+
335
+ forEachPreferredTag(jsdoc, 'example', (tag, targetTagName) => {
336
+ let source = /** @type {string} */ (getTagDescription(tag));
337
+ const match = source.match(hasCaptionRegex);
338
+
339
+ if (captionRequired && (!match || !match[1].trim())) {
340
+ extraMessages.push({
341
+ line: 1 + commentLineCols[0] + (tag.line ?? tag.source[0].number),
342
+ column: commentLineCols[1] + 1,
343
+ severity: 2,
344
+ message: `@${targetTagName} error - Caption is expected for examples.`,
345
+ ruleId: 'jsdoc/example-missing-caption'
346
+ });
347
+ return;
348
+ }
349
+
350
+ source = source.replace(hasCaptionRegex, '');
351
+ const [
352
+ lines,
353
+ cols,
354
+ ] = match ? getLinesCols(match[0]) : [
355
+ 0, 0,
356
+ ];
357
+
358
+ if (exampleCodeRegex && !exampleCodeRegExp.test(source) ||
359
+ rejectExampleCodeRegex && rejectExampleCodeRegExp.test(source)
360
+ ) {
361
+ return;
362
+ }
363
+
364
+ const sources = [];
365
+ let skipInit = false;
366
+ if (exampleCodeRegex) {
367
+ let nonJSPrefacingCols = 0;
368
+ let nonJSPrefacingLines = 0;
369
+
370
+ let startingIndex = 0;
371
+ let lastStringCount = 0;
372
+
373
+ let exampleCode;
374
+ exampleCodeRegExp.lastIndex = 0;
375
+ while ((exampleCode = exampleCodeRegExp.exec(source)) !== null) {
376
+ const {
377
+ index,
378
+ '0': n0,
379
+ '1': n1,
380
+ } = exampleCode;
381
+
382
+ // Count anything preceding user regex match (can affect line numbering)
383
+ const preMatch = source.slice(startingIndex, index);
384
+
385
+ const [
386
+ preMatchLines,
387
+ colDelta,
388
+ ] = getLinesCols(preMatch);
389
+
390
+ let nonJSPreface;
391
+ let nonJSPrefaceLineCount;
392
+ if (n1) {
393
+ const idx = n0.indexOf(n1);
394
+ nonJSPreface = n0.slice(0, idx);
395
+ nonJSPrefaceLineCount = countChars(nonJSPreface, '\n');
396
+ } else {
397
+ nonJSPreface = '';
398
+ nonJSPrefaceLineCount = 0;
399
+ }
400
+
401
+ nonJSPrefacingLines += lastStringCount + preMatchLines + nonJSPrefaceLineCount;
402
+
403
+ // Ignore `preMatch` delta if newlines here
404
+ if (nonJSPrefaceLineCount) {
405
+ const charsInLastLine = nonJSPreface.slice(nonJSPreface.lastIndexOf('\n') + 1).length;
406
+
407
+ nonJSPrefacingCols += charsInLastLine;
408
+ } else {
409
+ nonJSPrefacingCols += colDelta + nonJSPreface.length;
410
+ }
411
+
412
+ const string = n1 || n0;
413
+ sources.push({
414
+ nonJSPrefacingCols,
415
+ nonJSPrefacingLines,
416
+ string,
417
+ });
418
+ startingIndex = exampleCodeRegExp.lastIndex;
419
+ lastStringCount = countChars(string, '\n');
420
+ if (!exampleCodeRegExp.global) {
421
+ break;
422
+ }
423
+ }
424
+
425
+ skipInit = true;
426
+ }
427
+
428
+ checkSource({
429
+ cols,
430
+ lines,
431
+ skipInit,
432
+ source,
433
+ sources,
434
+ tag,
435
+ targetTagName,
436
+ ...matchingFilenameInfo,
437
+ });
438
+ });
439
+
440
+ return textsAndFileNames;
441
+ };
442
+
443
+ // See https://eslint.org/docs/latest/extend/plugins#processors-in-plugins
444
+ // See https://eslint.org/docs/latest/extend/custom-processors
445
+ // From https://github.com/eslint/eslint/issues/14745#issuecomment-869457265
446
+ /*
447
+ {
448
+ "files": ["*.js", "*.ts"],
449
+ "processor": "jsdoc/example" // a pretended value here
450
+ },
451
+ {
452
+ "files": [
453
+ "*.js/*_jsdoc-example.js",
454
+ "*.ts/*_jsdoc-example.js",
455
+ "*.js/*_jsdoc-example.ts"
456
+ ],
457
+ "rules": {
458
+ // specific rules for examples in jsdoc only here
459
+ // And other rules for `.js` and `.ts` will also be enabled for them
460
+ }
461
+ }
462
+ */
463
+ return {
464
+ meta: {
465
+ name: 'eslint-plugin-jsdoc/processor',
466
+ version,
467
+ },
468
+ processors: {
469
+ examples: {
470
+ meta: {
471
+ name: 'eslint-plugin-jsdoc/preprocessor',
472
+ version,
473
+ },
474
+ /**
475
+ * @param {string} text
476
+ * @param {string} filename
477
+ */
478
+ preprocess (text, filename) {
479
+ try {
480
+ let ast;
481
+
482
+ // May be running a second time so catch and ignore
483
+ try {
484
+ ast = parser
485
+ // @ts-expect-error Ok
486
+ ? parser.parseForESLint(text, {
487
+ ecmaVersion: 'latest',
488
+ sourceType,
489
+ comment: true
490
+ }).ast
491
+ : espree.parse(text, {
492
+ ecmaVersion: 'latest',
493
+ sourceType,
494
+ comment: true
495
+ });
496
+ } catch (err) {
497
+ return [text];
498
+ }
499
+
500
+ /** @type {[number, number][]} */
501
+ const commentLineCols = [];
502
+ const jsdocComments = /** @type {import('estree').Comment[]} */ (
503
+ /**
504
+ * @type {import('estree').Program & {
505
+ * comments?: import('estree').Comment[]
506
+ * }}
507
+ */
508
+ (ast).comments
509
+ ).filter((comment) => {
510
+ return (/^\*\s/u).test(comment.value);
511
+ }).map((comment) => {
512
+ /* c8 ignore next -- Unsupporting processors only? */
513
+ const [start] = comment.range ?? [];
514
+ const textToStart = text.slice(0, start);
515
+
516
+ const [lines, cols] = getLinesCols(textToStart);
517
+
518
+ // const lines = [...textToStart.matchAll(/\n/gu)].length
519
+ // const lastLinePos = textToStart.lastIndexOf('\n');
520
+ // const cols = lastLinePos === -1
521
+ // ? 0
522
+ // : textToStart.slice(lastLinePos).length;
523
+ commentLineCols.push([lines, cols]);
524
+ return parseComment(comment);
525
+ });
526
+
527
+ return [
528
+ text,
529
+ ...jsdocComments.flatMap((jsdoc, idx) => {
530
+ return getTextsAndFileNames(
531
+ jsdoc,
532
+ filename,
533
+ commentLineCols[idx]
534
+ );
535
+ }).filter(Boolean)
536
+ ];
537
+ /* c8 ignore next 3 */
538
+ } catch (err) {
539
+ console.log('err', filename, err);
540
+ }
541
+ },
542
+
543
+ /**
544
+ * @param {import('eslint').Linter.LintMessage[][]} messages
545
+ * @param {string} filename
546
+ */
547
+ postprocess ([jsMessages, ...messages], filename) {
548
+ messages.forEach((message, idx) => {
549
+ const {
550
+ targetTagName,
551
+ codeStartLine,
552
+ codeStartCol,
553
+ nonJSPrefacingCols,
554
+ commentLineCols
555
+ } = otherInfo[idx];
556
+
557
+ message.forEach((msg) => {
558
+ const {
559
+ message,
560
+ ruleId,
561
+ severity,
562
+ fatal,
563
+ line,
564
+ column,
565
+ endColumn,
566
+ endLine,
567
+
568
+ // Todo: Make fixable
569
+ // fix
570
+ // fix: {range: [number, number], text: string}
571
+ // suggestions: {desc: , messageId:, fix: }[],
572
+ } = msg;
573
+
574
+ const [codeCtxLine, codeCtxColumn] = commentLineCols;
575
+ const startLine = codeCtxLine + codeStartLine + line;
576
+ const startCol = 1 + // Seems to need one more now
577
+ codeCtxColumn + codeStartCol + (
578
+ // This might not work for line 0, but line 0 is unlikely for examples
579
+ line <= 1 ? nonJSPrefacingCols + firstLinePrefixLength : preTagSpaceLength
580
+ ) + column;
581
+
582
+ msg.message = '@' + targetTagName + ' ' + (severity === 2 ? 'error' : 'warning') +
583
+ (ruleId ? ' (' + ruleId + ')' : '') + ': ' +
584
+ (fatal ? 'Fatal: ' : '') +
585
+ message;
586
+ msg.line = startLine;
587
+ msg.column = startCol;
588
+ msg.endLine = endLine ? startLine + endLine : startLine;
589
+ // added `- column` to offset what `endColumn` already seemed to include
590
+ msg.endColumn = endColumn ? startCol - column + endColumn : startCol;
591
+ });
592
+ });
593
+
594
+ const ret = [...jsMessages].concat(...messages, ...extraMessages);
595
+ extraMessages = [];
596
+ return ret;
597
+ },
598
+ supportsAutofix: true
599
+ },
600
+ },
601
+ };
602
+ };
package/src/index.js CHANGED
@@ -55,6 +55,8 @@ import tagLines from './rules/tagLines.js';
55
55
  import textEscaping from './rules/textEscaping.js';
56
56
  import validTypes from './rules/validTypes.js';
57
57
 
58
+ import { getJsdocProcessorPlugin } from './getJsdocProcessorPlugin.js';
59
+
58
60
  /**
59
61
  * @type {import('eslint').ESLint.Plugin & {
60
62
  * configs: Record<
@@ -274,4 +276,108 @@ index.configs['flat/recommended-typescript-error'] = createRecommendedTypeScript
274
276
  index.configs['flat/recommended-typescript-flavor'] = createRecommendedTypeScriptFlavorRuleset('warn', 'flat/recommended-typescript-flavor');
275
277
  index.configs['flat/recommended-typescript-flavor-error'] = createRecommendedTypeScriptFlavorRuleset('error', 'flat/recommended-typescript-flavor-error');
276
278
 
279
+ index.configs.examples = /** @type {import('eslint').Linter.FlatConfig[]} */ ([
280
+ {
281
+ name: 'jsdoc/examples/processor',
282
+ files: ['**/*.js'],
283
+ plugins: {
284
+ examples: getJsdocProcessorPlugin()
285
+ },
286
+ processor: 'examples/examples',
287
+ },
288
+ {
289
+ name: 'jsdoc/examples/rules',
290
+ files: ['**/*.md/*.js'],
291
+ rules: {
292
+ // "always" newline rule at end unlikely in sample code
293
+ 'eol-last': 0,
294
+
295
+ // Wouldn't generally expect example paths to resolve relative to JS file
296
+ 'import/no-unresolved': 0,
297
+
298
+ // Snippets likely too short to always include import/export info
299
+ 'import/unambiguous': 0,
300
+
301
+ 'jsdoc/require-file-overview': 0,
302
+
303
+ // The end of a multiline comment would end the comment the example is in.
304
+ 'jsdoc/require-jsdoc': 0,
305
+
306
+ // Unlikely to have inadvertent debugging within examples
307
+ 'no-console': 0,
308
+
309
+ // Often wish to start `@example` code after newline; also may use
310
+ // empty lines for spacing
311
+ 'no-multiple-empty-lines': 0,
312
+
313
+ // Many variables in examples will be `undefined`
314
+ 'no-undef': 0,
315
+
316
+ // Common to define variables for clarity without always using them
317
+ 'no-unused-vars': 0,
318
+
319
+ // See import/no-unresolved
320
+ 'node/no-missing-import': 0,
321
+ 'node/no-missing-require': 0,
322
+
323
+ // Can generally look nicer to pad a little even if code imposes more stringency
324
+ 'padded-blocks': 0,
325
+ }
326
+ }
327
+ ]);
328
+
329
+ index.configs['default-expressions'] = /** @type {import('eslint').Linter.FlatConfig[]} */ ([
330
+ {
331
+ files: ['**/*.js'],
332
+ name: 'jsdoc/default-expressions/processor',
333
+ plugins: {
334
+ examples: getJsdocProcessorPlugin({
335
+ checkDefaults: true,
336
+ checkParams: true,
337
+ checkProperties: true
338
+ })
339
+ },
340
+ processor: 'examples/examples'
341
+ },
342
+ {
343
+ name: 'jsdoc/default-expressions/rules',
344
+ files: ['**/*.jsdoc-defaults', '**/*.jsdoc-params', '**/*.jsdoc-properties'],
345
+ rules: {
346
+ ...index.configs.examples[1].rules,
347
+ 'chai-friendly/no-unused-expressions': 0,
348
+ 'no-empty-function': 0,
349
+ 'no-new': 0,
350
+ 'no-unused-expressions': 0,
351
+ quotes: [
352
+ 'error', 'double',
353
+ ],
354
+ semi: [
355
+ 'error', 'never',
356
+ ],
357
+ strict: 0
358
+ },
359
+ }
360
+ ]);
361
+
362
+ index.configs['examples-and-default-expressions'] = /** @type {import('eslint').Linter.FlatConfig[]} */ ([
363
+ {
364
+ name: 'jsdoc/examples-and-default-expressions',
365
+ plugins: {
366
+ examples: getJsdocProcessorPlugin({
367
+ checkDefaults: true,
368
+ checkParams: true,
369
+ checkProperties: true
370
+ })
371
+ },
372
+ },
373
+ ...index.configs.examples.map((config) => {
374
+ delete config.plugins;
375
+ return config;
376
+ }),
377
+ ...index.configs['default-expressions'].map((config) => {
378
+ delete config.plugins;
379
+ return config;
380
+ })
381
+ ]);
382
+
277
383
  export default index;