@dereekb/firebase 13.12.3 → 13.12.5

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.
@@ -1,12 +1,1135 @@
1
1
  'use strict';
2
2
 
3
- var eslint = require('@dereekb/util/eslint');
4
3
  var node_module = require('node:module');
5
4
  var node_fs = require('node:fs');
6
5
  var node_path = require('node:path');
7
6
  var typescriptEstree = require('@typescript-eslint/typescript-estree');
8
7
  var parser = require('@typescript-eslint/parser');
9
8
 
9
+ /**
10
+ * Returns the outermost statement node for a FunctionDeclaration — its `ExportNamedDeclaration`
11
+ * or `ExportDefaultDeclaration` parent if exported, otherwise the declaration itself. This is the
12
+ * node ESLint attaches leading comments to.
13
+ *
14
+ * @param node - The FunctionDeclaration AST node.
15
+ * @returns The statement node ESLint attaches leading comments to.
16
+ */ function getStatementAnchor(node) {
17
+ return node.parent && (node.parent.type === 'ExportNamedDeclaration' || node.parent.type === 'ExportDefaultDeclaration') ? node.parent : node;
18
+ }
19
+ /**
20
+ * Returns the JSDoc Block comment immediately preceding `anchor`, or `null` when
21
+ * the anchor has no JSDoc leader. Used by the `@dbx<Family>` companion-tag rules
22
+ * to locate the tagged declaration's documentation.
23
+ *
24
+ * @param sourceCode - The ESLint `SourceCode` object.
25
+ * @param anchor - The statement-level node ESLint attaches leading comments to.
26
+ * @returns The JSDoc block comment, or null when none is present.
27
+ */ function leadingJsdocFor(sourceCode, anchor) {
28
+ var comments = sourceCode.getCommentsBefore(anchor) || [];
29
+ var result = null;
30
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
31
+ try {
32
+ for(var _iterator = comments[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
33
+ var comment = _step.value;
34
+ if (comment.type === 'Block' && typeof comment.value === 'string' && comment.value.startsWith('*')) {
35
+ result = comment;
36
+ }
37
+ }
38
+ } catch (err) {
39
+ _didIteratorError = true;
40
+ _iteratorError = err;
41
+ } finally{
42
+ try {
43
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
44
+ _iterator.return();
45
+ }
46
+ } finally{
47
+ if (_didIteratorError) {
48
+ throw _iteratorError;
49
+ }
50
+ }
51
+ }
52
+ return result;
53
+ }
54
+
55
+ /**
56
+ * Default maximum positional parameters before the warn-level rule suggests a config object.
57
+ * Triggers a warning when a function has more than 2 positional parameters (i.e. 3+ args).
58
+ */ var DEFAULT_MAX_PARAMS_WARN = 2;
59
+ /**
60
+ * Default maximum positional parameters before the hard-error rule rejects the signature.
61
+ * Triggers an error when a function has more than 4 positional parameters (i.e. 5+ args).
62
+ */ var DEFAULT_MAX_PARAMS_HARD = 4;
63
+ /**
64
+ * Default JSDoc tag that opts a function out of this rule.
65
+ */ var DEFAULT_ALLOW_JSDOC_TAG = '@dbxAllowMultiParams';
66
+ /**
67
+ * Returns a human-readable display name for the function-like node, or `<anonymous>`.
68
+ *
69
+ * @param node - The function-like AST node.
70
+ * @returns The identifier string used in diagnostic messages.
71
+ */ function getFunctionDisplayName(node) {
72
+ var _node_id;
73
+ var name = '<anonymous>';
74
+ if (((_node_id = node.id) === null || _node_id === void 0 ? void 0 : _node_id.type) === 'Identifier') {
75
+ name = node.id.name;
76
+ } else if (node.parent) {
77
+ var _parent_id, _parent_key, _parent_key1, _parent_left;
78
+ var parent = node.parent;
79
+ if (parent.type === 'VariableDeclarator' && ((_parent_id = parent.id) === null || _parent_id === void 0 ? void 0 : _parent_id.type) === 'Identifier') {
80
+ name = parent.id.name;
81
+ } else if (parent.type === 'Property' && ((_parent_key = parent.key) === null || _parent_key === void 0 ? void 0 : _parent_key.type) === 'Identifier') {
82
+ name = parent.key.name;
83
+ } else if (parent.type === 'MethodDefinition' && ((_parent_key1 = parent.key) === null || _parent_key1 === void 0 ? void 0 : _parent_key1.type) === 'Identifier') {
84
+ name = parent.key.name;
85
+ } else if (parent.type === 'AssignmentExpression' && ((_parent_left = parent.left) === null || _parent_left === void 0 ? void 0 : _parent_left.type) === 'Identifier') {
86
+ name = parent.left.name;
87
+ }
88
+ }
89
+ return name;
90
+ }
91
+ /**
92
+ * Returns true if a parameter has any decorators (NestJS handler/Inject pattern).
93
+ *
94
+ * @param param - The parameter AST node.
95
+ * @returns True when the parameter carries at least one decorator.
96
+ */ function paramHasDecorator(param) {
97
+ var _param_decorators;
98
+ var decorators = (_param_decorators = param.decorators) !== null && _param_decorators !== void 0 ? _param_decorators : [];
99
+ return Array.isArray(decorators) && decorators.length > 0;
100
+ }
101
+ /**
102
+ * Returns true when the function is the constructor of a class.
103
+ *
104
+ * @param node - The function-like AST node.
105
+ * @returns True if `node` is the `constructor` body of a class.
106
+ */ function isConstructor(node) {
107
+ var _node_parent;
108
+ return ((_node_parent = node.parent) === null || _node_parent === void 0 ? void 0 : _node_parent.type) === 'MethodDefinition' && node.parent.kind === 'constructor';
109
+ }
110
+ /**
111
+ * Returns true if any leading JSDoc block above `anchor` contains the allow tag.
112
+ *
113
+ * @param sourceCode - The ESLint `SourceCode` instance.
114
+ * @param anchor - The AST node whose leading comments are scanned.
115
+ * @param allowTag - The JSDoc tag string that opts the function out.
116
+ * @returns True when a JSDoc with the allow tag is present.
117
+ */ function hasAllowJsdoc(sourceCode, anchor, allowTag) {
118
+ var comments = sourceCode.getCommentsBefore(anchor) || [];
119
+ var allow = false;
120
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
121
+ try {
122
+ for(var _iterator = comments[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
123
+ var comment = _step.value;
124
+ if (comment.type === 'Block' && comment.value.startsWith('*') && comment.value.includes(allowTag)) {
125
+ allow = true;
126
+ }
127
+ }
128
+ } catch (err) {
129
+ _didIteratorError = true;
130
+ _iteratorError = err;
131
+ } finally{
132
+ try {
133
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
134
+ _iterator.return();
135
+ }
136
+ } finally{
137
+ if (_didIteratorError) {
138
+ throw _iteratorError;
139
+ }
140
+ }
141
+ }
142
+ return allow;
143
+ }
144
+ /**
145
+ * Builds a prefer-config-object-style rule with a configurable default `maxParams` threshold.
146
+ * Class constructors and decorated parameters (e.g. NestJS `@Inject`) are exempted. Functions can
147
+ * opt out via a leading JSDoc block carrying the configured allow tag (default `@dbxAllowMultiParams`).
148
+ *
149
+ * @param config - Default threshold and rule description.
150
+ * @returns A complete ESLint rule definition that emits `tooManyParams` reports.
151
+ */ function createPreferConfigObjectRule(config) {
152
+ return {
153
+ meta: {
154
+ type: 'suggestion',
155
+ docs: {
156
+ description: config.description,
157
+ recommended: true
158
+ },
159
+ messages: {
160
+ tooManyParams: "Function '{{name}}' takes {{count}} positional parameters; use a single config object instead (see dbx__note__typescript-programming → Prefer Single Config Object)."
161
+ },
162
+ schema: [
163
+ {
164
+ type: 'object',
165
+ properties: {
166
+ maxParams: {
167
+ type: 'number',
168
+ minimum: 0,
169
+ description: 'Maximum number of positional parameters before the rule fires.'
170
+ },
171
+ allowJsdocTag: {
172
+ type: 'string',
173
+ description: 'JSDoc tag that opts a function out of this rule.'
174
+ }
175
+ },
176
+ additionalProperties: false
177
+ }
178
+ ]
179
+ },
180
+ create: function create(context) {
181
+ var _context_options_, _options_maxParams, _options_allowJsdocTag;
182
+ var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
183
+ var maxParams = (_options_maxParams = options.maxParams) !== null && _options_maxParams !== void 0 ? _options_maxParams : config.defaultMaxParams;
184
+ var allowTag = (_options_allowJsdocTag = options.allowJsdocTag) !== null && _options_allowJsdocTag !== void 0 ? _options_allowJsdocTag : DEFAULT_ALLOW_JSDOC_TAG;
185
+ var sourceCode = context.sourceCode;
186
+ function checkFunction(node) {
187
+ if (!isConstructor(node)) {
188
+ var _node_params;
189
+ var params = (_node_params = node.params) !== null && _node_params !== void 0 ? _node_params : [];
190
+ // Decorated parameters indicate framework-driven signatures (NestJS handlers, Angular DI inside
191
+ // constructors which we already skip — but standalone decorated functions exist too).
192
+ if (!params.some(paramHasDecorator) && params.length > maxParams) {
193
+ var _node_parent, _node_parent_parent;
194
+ // Anchor for JSDoc lookup: prefer the enclosing export statement, then a VariableDeclaration
195
+ // (for `const fn = () => ...`), otherwise the function node itself.
196
+ var anchor = node;
197
+ if (((_node_parent = node.parent) === null || _node_parent === void 0 ? void 0 : _node_parent.type) === 'VariableDeclarator' && ((_node_parent_parent = node.parent.parent) === null || _node_parent_parent === void 0 ? void 0 : _node_parent_parent.type) === 'VariableDeclaration') {
198
+ anchor = node.parent.parent;
199
+ }
200
+ if (anchor.parent && (anchor.parent.type === 'ExportNamedDeclaration' || anchor.parent.type === 'ExportDefaultDeclaration')) {
201
+ anchor = anchor.parent;
202
+ }
203
+ if (!hasAllowJsdoc(sourceCode, anchor, allowTag)) {
204
+ var _node_id;
205
+ var name = getFunctionDisplayName(node);
206
+ context.report({
207
+ node: (_node_id = node.id) !== null && _node_id !== void 0 ? _node_id : node,
208
+ messageId: 'tooManyParams',
209
+ data: {
210
+ name: name,
211
+ count: String(params.length)
212
+ }
213
+ });
214
+ }
215
+ }
216
+ }
217
+ }
218
+ return {
219
+ FunctionDeclaration: checkFunction,
220
+ FunctionExpression: checkFunction,
221
+ ArrowFunctionExpression: checkFunction
222
+ };
223
+ }
224
+ };
225
+ }
226
+ /**
227
+ * ESLint rule recommending a single config object when a function takes more than two positional
228
+ * parameters (default `maxParams: 2`, i.e. fires at 3+ args). Intended to be configured at the
229
+ * `warn` severity. Pair with `prefer-config-object-hard` for a stricter cap.
230
+ *
231
+ * @see `dbx__note__typescript-programming` → Prefer Single Config Object
232
+ */ createPreferConfigObjectRule({
233
+ defaultMaxParams: DEFAULT_MAX_PARAMS_WARN,
234
+ description: 'Prefer a single config object when a function takes more than two positional parameters.'
235
+ });
236
+ /**
237
+ * Hard-stop variant of `prefer-config-object`. Fires when a function takes more than four positional
238
+ * parameters (default `maxParams: 4`, i.e. fires at 5+ args). Intended to be configured at the
239
+ * `error` severity so genuinely unwieldy signatures break the build even when the softer warn-level
240
+ * rule is disabled or downgraded.
241
+ *
242
+ * @see `dbx__note__typescript-programming` → Prefer Single Config Object
243
+ */ createPreferConfigObjectRule({
244
+ defaultMaxParams: DEFAULT_MAX_PARAMS_HARD,
245
+ description: 'Reject function signatures with more than four positional parameters; require a single config object.'
246
+ });
247
+
248
+ function _array_like_to_array$h(arr, len) {
249
+ if (len == null || len > arr.length) len = arr.length;
250
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
251
+ return arr2;
252
+ }
253
+ function _array_with_holes$a(arr) {
254
+ if (Array.isArray(arr)) return arr;
255
+ }
256
+ function _iterable_to_array_limit$a(arr, i) {
257
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
258
+ if (_i == null) return;
259
+ var _arr = [];
260
+ var _n = true;
261
+ var _d = false;
262
+ var _s, _e;
263
+ try {
264
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
265
+ _arr.push(_s.value);
266
+ if (i && _arr.length === i) break;
267
+ }
268
+ } catch (err) {
269
+ _d = true;
270
+ _e = err;
271
+ } finally{
272
+ try {
273
+ if (!_n && _i["return"] != null) _i["return"]();
274
+ } finally{
275
+ if (_d) throw _e;
276
+ }
277
+ }
278
+ return _arr;
279
+ }
280
+ function _non_iterable_rest$a() {
281
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
282
+ }
283
+ function _sliced_to_array$a(arr, i) {
284
+ return _array_with_holes$a(arr) || _iterable_to_array_limit$a(arr, i) || _unsupported_iterable_to_array$h(arr, i) || _non_iterable_rest$a();
285
+ }
286
+ function _unsupported_iterable_to_array$h(o, minLen) {
287
+ if (!o) return;
288
+ if (typeof o === "string") return _array_like_to_array$h(o, minLen);
289
+ var n = Object.prototype.toString.call(o).slice(8, -1);
290
+ if (n === "Object" && o.constructor) n = o.constructor.name;
291
+ if (n === "Map" || n === "Set") return Array.from(n);
292
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$h(o, minLen);
293
+ }
294
+ var TAG_LINE_REGEX = /^@([A-Za-z_]\w*)\s*(.*)$/;
295
+ var TYPE_ANNOTATION_REGEX = /^\{([^}]*)\}\s*(.*)$/;
296
+ var PARAM_NAME_REGEX = /^([A-Za-z_$][A-Za-z0-9_$.[\]]*)\s*(.*)$/;
297
+ var LINE_PREFIX_REGEX = /^(\s*\*?\s?)(.*)$/;
298
+ /**
299
+ * Strips the leading whitespace + `*` + optional space prefix from a JSDoc body line and reports the length stripped.
300
+ *
301
+ * @param raw - The raw line as it appears in `comment.value`.
302
+ * @returns A `{ text, prefixLength }` pair.
303
+ *
304
+ * @example
305
+ * ```ts
306
+ * stripPrefix(' * @param x - desc'); // { text: '@param x - desc', prefixLength: 3 }
307
+ * stripPrefix(' *'); // { text: '', prefixLength: 2 }
308
+ * stripPrefix('* hello'); // { text: 'hello', prefixLength: 2 }
309
+ * ```
310
+ */ function stripPrefix(raw) {
311
+ var match = LINE_PREFIX_REGEX.exec(raw);
312
+ var result = {
313
+ text: raw,
314
+ prefixLength: 0
315
+ };
316
+ if (match) {
317
+ result.text = match[2];
318
+ result.prefixLength = match[1].length;
319
+ }
320
+ return result;
321
+ }
322
+ /**
323
+ * Splits the raw comment value into per-line views with prefix/offset metadata.
324
+ *
325
+ * @param commentValue - The `value` of an ESLint Block comment.
326
+ * @returns Array of parsed line records in source order.
327
+ */ function buildParsedLines(commentValue) {
328
+ var rawLines = commentValue.split('\n');
329
+ var runningOffset = 0;
330
+ return rawLines.map(function(raw, index) {
331
+ var _stripPrefix = stripPrefix(raw), stripped = _stripPrefix.text, prefixLength = _stripPrefix.prefixLength;
332
+ var text = stripped.trimEnd();
333
+ var blank = text.length === 0;
334
+ var valueOffsetStart = runningOffset;
335
+ var textOffsetStart = runningOffset + prefixLength;
336
+ runningOffset += raw.length + 1; // +1 for the consumed `\n` (overshoots on last line, harmless)
337
+ return {
338
+ raw: raw,
339
+ text: text,
340
+ blank: blank,
341
+ index: index,
342
+ valueOffsetStart: valueOffsetStart,
343
+ textOffsetStart: textOffsetStart
344
+ };
345
+ });
346
+ }
347
+ /**
348
+ * Computes, per line, whether it sits inside a fenced code block (a ```` ``` ```` block, typically
349
+ * an `@example` body) and therefore must not be treated as a tag boundary. Fence delimiter lines
350
+ * themselves are flagged too. Without this, `@`-prefixed lines inside a fence — decorators like
351
+ * `@Global()` / `@Module()`, or JSDoc snippets — would be mis-parsed as standalone JSDoc tags.
352
+ *
353
+ * @param lines - Parsed lines in source order.
354
+ * @returns Boolean mask where `true` marks a line that must not start a tag.
355
+ */ function computeFenceMask(lines) {
356
+ var mask = lines.map(function() {
357
+ return false;
358
+ });
359
+ var fenceOpen = false;
360
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
361
+ try {
362
+ for(var _iterator = lines.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
363
+ var _step_value = _sliced_to_array$a(_step.value, 2), i = _step_value[0], line = _step_value[1];
364
+ var isDelimiter = line.text.trimStart().startsWith('```');
365
+ if (isDelimiter) {
366
+ mask[i] = true;
367
+ fenceOpen = !fenceOpen;
368
+ } else {
369
+ mask[i] = fenceOpen;
370
+ }
371
+ }
372
+ } catch (err) {
373
+ _didIteratorError = true;
374
+ _iteratorError = err;
375
+ } finally{
376
+ try {
377
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
378
+ _iterator.return();
379
+ }
380
+ } finally{
381
+ if (_didIteratorError) {
382
+ throw _iteratorError;
383
+ }
384
+ }
385
+ }
386
+ return mask;
387
+ }
388
+ /**
389
+ * Returns true when the line at `index` opens a JSDoc tag and is not masked out by a code fence.
390
+ *
391
+ * @param lines - Parsed lines in source order.
392
+ * @param fenceMask - Mask from {@link computeFenceMask} marking fenced lines.
393
+ * @param index - Line index to test.
394
+ * @returns True when the line begins a tag that should be treated as a tag boundary.
395
+ */ function isTagStart(lines, fenceMask, index) {
396
+ return !fenceMask[index] && TAG_LINE_REGEX.test(lines[index].text);
397
+ }
398
+ /**
399
+ * Returns the index of the first line that begins with a JSDoc `@tag`, or `-1` when none exists.
400
+ *
401
+ * @param lines - Parsed lines in source order.
402
+ * @param fenceMask - Mask from {@link computeFenceMask} marking fenced lines.
403
+ * @returns Zero-based line index of the first tag, or `-1` when no tag is present.
404
+ */ function findFirstTagIndex(lines, fenceMask) {
405
+ var firstTagIndex = -1;
406
+ for(var i = 0; i < lines.length; i += 1){
407
+ if (isTagStart(lines, fenceMask, i)) {
408
+ firstTagIndex = i;
409
+ break;
410
+ }
411
+ }
412
+ return firstTagIndex;
413
+ }
414
+ /**
415
+ * Trims leading and trailing blank lines from a contiguous run of description lines.
416
+ *
417
+ * @param descriptionLines - Description-section lines before any tag.
418
+ * @returns Sub-array with surrounding blank lines stripped.
419
+ */ function trimBlankBoundaries(descriptionLines) {
420
+ var descStart = 0;
421
+ var descEnd = descriptionLines.length;
422
+ while(descStart < descEnd && descriptionLines[descStart].blank)descStart += 1;
423
+ while(descEnd > descStart && descriptionLines[descEnd - 1].blank)descEnd -= 1;
424
+ return descriptionLines.slice(descStart, descEnd);
425
+ }
426
+ /**
427
+ * Splits the trimmed description lines into paragraphs separated by blank-line runs.
428
+ *
429
+ * @param trimmedDescription - Description lines with surrounding blank lines removed.
430
+ * @returns Paragraph strings joined by `\n`.
431
+ */ function buildDescriptionParagraphs(trimmedDescription) {
432
+ var descriptionParagraphs = [];
433
+ var paragraphBuffer = [];
434
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
435
+ try {
436
+ for(var _iterator = trimmedDescription[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
437
+ var line = _step.value;
438
+ if (line.blank) {
439
+ if (paragraphBuffer.length > 0) {
440
+ descriptionParagraphs.push(paragraphBuffer.join('\n'));
441
+ paragraphBuffer = [];
442
+ }
443
+ } else {
444
+ paragraphBuffer.push(line.text);
445
+ }
446
+ }
447
+ } catch (err) {
448
+ _didIteratorError = true;
449
+ _iteratorError = err;
450
+ } finally{
451
+ try {
452
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
453
+ _iterator.return();
454
+ }
455
+ } finally{
456
+ if (_didIteratorError) {
457
+ throw _iteratorError;
458
+ }
459
+ }
460
+ }
461
+ if (paragraphBuffer.length > 0) {
462
+ descriptionParagraphs.push(paragraphBuffer.join('\n'));
463
+ }
464
+ return descriptionParagraphs;
465
+ }
466
+ /**
467
+ * Pulls an optional `{Type}` annotation off the front of a tag remainder.
468
+ *
469
+ * @param remainder - The tag-line text after the `@tagName` prefix.
470
+ * @returns The annotation (or `undefined`) plus the remaining text.
471
+ */ function extractTypeAnnotation(remainder) {
472
+ var type;
473
+ var rest = remainder;
474
+ var typeMatch = TYPE_ANNOTATION_REGEX.exec(remainder);
475
+ if (typeMatch) {
476
+ type = typeMatch[1];
477
+ rest = typeMatch[2];
478
+ }
479
+ return {
480
+ type: type,
481
+ rest: rest
482
+ };
483
+ }
484
+ /**
485
+ * Pulls an optional parameter name off the front of a `@param` tag remainder.
486
+ *
487
+ * @param tagName - Tag name (only `'param'` extracts a name; other tags pass through).
488
+ * @param remainder - The tag-line text after the optional `{Type}` annotation.
489
+ * @returns The parameter name (or `undefined`) plus the remaining text.
490
+ */ function extractParamName(tagName, remainder) {
491
+ var name;
492
+ var rest = remainder;
493
+ if (tagName === 'param') {
494
+ var nameMatch = PARAM_NAME_REGEX.exec(remainder);
495
+ if (nameMatch) {
496
+ name = nameMatch[1];
497
+ rest = nameMatch[2];
498
+ }
499
+ }
500
+ return {
501
+ name: name,
502
+ rest: rest
503
+ };
504
+ }
505
+ /**
506
+ * Collects the tag line at `startIndex` plus every following non-tag continuation line.
507
+ *
508
+ * @param lines - All parsed lines in the comment.
509
+ * @param fenceMask - Mask from {@link computeFenceMask} marking fenced lines.
510
+ * @param startIndex - Index of the `@tag` opening line.
511
+ * @returns The collected tag lines and the index of the next unconsumed line.
512
+ */ function collectTagLines(lines, fenceMask, startIndex) {
513
+ var tagLines = [
514
+ lines[startIndex]
515
+ ];
516
+ var j = startIndex + 1;
517
+ while(j < lines.length){
518
+ if (isTagStart(lines, fenceMask, j)) break;
519
+ tagLines.push(lines[j]);
520
+ j += 1;
521
+ }
522
+ return {
523
+ tagLines: tagLines,
524
+ nextIndex: j
525
+ };
526
+ }
527
+ /**
528
+ * Joins the on-line remainder and continuation-line text into a tag description, dropping trailing
529
+ * blank lines while preserving interior blanks.
530
+ *
531
+ * @param remainder - The on-line remainder after stripping `@tagName {Type} name`.
532
+ * @param tagLines - All lines that belong to the tag (including the header line at index 0).
533
+ * @returns Description text joined by `\n`.
534
+ */ function buildTagDescription(remainder, tagLines) {
535
+ var descriptionParts = [];
536
+ if (remainder.length > 0) descriptionParts.push(remainder);
537
+ for(var k = 1; k < tagLines.length; k += 1){
538
+ descriptionParts.push(tagLines[k].text);
539
+ }
540
+ while(descriptionParts.length > 0 && descriptionParts.at(-1).trim().length === 0){
541
+ descriptionParts.pop();
542
+ }
543
+ return descriptionParts.join('\n');
544
+ }
545
+ /**
546
+ * Builds a single parsed-tag record starting at `startIndex` in the line array.
547
+ *
548
+ * @param lines - All parsed lines in the comment.
549
+ * @param fenceMask - Mask from {@link computeFenceMask} marking fenced lines.
550
+ * @param startIndex - Index of the `@tag` opening line.
551
+ * @returns The parsed tag and the next unconsumed line index.
552
+ */ function parseTagAt(lines, fenceMask, startIndex) {
553
+ var line = lines[startIndex];
554
+ var match = TAG_LINE_REGEX.exec(line.text);
555
+ var tagName = match[1];
556
+ var _extractTypeAnnotation = extractTypeAnnotation(match[2]), type = _extractTypeAnnotation.type, afterType = _extractTypeAnnotation.rest;
557
+ var _extractParamName = extractParamName(tagName, afterType), name = _extractParamName.name, afterName = _extractParamName.rest;
558
+ var _collectTagLines = collectTagLines(lines, fenceMask, startIndex), tagLines = _collectTagLines.tagLines, nextIndex = _collectTagLines.nextIndex;
559
+ var description = buildTagDescription(afterName, tagLines);
560
+ return {
561
+ tag: {
562
+ tag: tagName,
563
+ name: name,
564
+ type: type,
565
+ description: description,
566
+ lines: tagLines,
567
+ startLineIndex: startIndex,
568
+ endLineIndex: nextIndex - 1
569
+ },
570
+ nextIndex: nextIndex
571
+ };
572
+ }
573
+ /**
574
+ * Parses every `@tag` block starting from `firstTagIndex` to the end of the line array.
575
+ *
576
+ * @param lines - All parsed lines in the comment.
577
+ * @param fenceMask - Mask from {@link computeFenceMask} marking fenced lines.
578
+ * @param firstTagIndex - Index where tag parsing should begin (`-1` skips entirely).
579
+ * @returns All parsed tags in source order.
580
+ */ function parseTags(lines, fenceMask, firstTagIndex) {
581
+ var tags = [];
582
+ if (firstTagIndex !== -1) {
583
+ var i = firstTagIndex;
584
+ while(i < lines.length){
585
+ if (!isTagStart(lines, fenceMask, i)) {
586
+ i += 1;
587
+ continue;
588
+ }
589
+ var _parseTagAt = parseTagAt(lines, fenceMask, i), tag = _parseTagAt.tag, nextIndex = _parseTagAt.nextIndex;
590
+ tags.push(tag);
591
+ i = nextIndex;
592
+ }
593
+ }
594
+ return tags;
595
+ }
596
+ /**
597
+ * Parses the value of an ESLint Block comment that represents a JSDoc into a structured form.
598
+ *
599
+ * @param commentValue - The `value` of an ESLint Block comment (text between `/*` and `*\/`, including the leading `*`).
600
+ * @returns A structured view of the JSDoc with description, paragraphs, and tags.
601
+ *
602
+ * @example
603
+ * ```ts
604
+ * const parsed = parseJsdocComment('*\n * Hello.\n *\n * @param x - The value.\n ');
605
+ * // parsed.description === 'Hello.'
606
+ * // parsed.tags[0].tag === 'param'
607
+ * // parsed.tags[0].name === 'x'
608
+ * // parsed.tags[0].description === 'The value.'
609
+ * ```
610
+ */ function parseJsdocComment(commentValue) {
611
+ var singleLine = !commentValue.includes('\n');
612
+ var lines = buildParsedLines(commentValue);
613
+ var fenceMask = computeFenceMask(lines);
614
+ var firstTagIndex = findFirstTagIndex(lines, fenceMask);
615
+ var descriptionLines = firstTagIndex === -1 ? lines.slice() : lines.slice(0, firstTagIndex);
616
+ var trimmedDescription = trimBlankBoundaries(descriptionLines);
617
+ var description = trimmedDescription.map(function(l) {
618
+ return l.text;
619
+ }).join('\n');
620
+ var descriptionParagraphs = buildDescriptionParagraphs(trimmedDescription);
621
+ var tags = parseTags(lines, fenceMask, firstTagIndex);
622
+ return {
623
+ lines: lines,
624
+ descriptionLines: descriptionLines,
625
+ description: description,
626
+ descriptionParagraphs: descriptionParagraphs,
627
+ tags: tags,
628
+ singleLine: singleLine
629
+ };
630
+ }
631
+
632
+ /**
633
+ * Shared helpers for the `@dbx<Family>` companion-tag ESLint rules. Mirrors the
634
+ * scanner schemas in `packages/dbx-cli/src/lib/mcp-scan/scan/*-extract.ts` so
635
+ * violations surface at lint time instead of at manifest-regeneration time.
636
+ *
637
+ * Each per-family rule supplies a {@link DbxTagFamilySpec} describing which
638
+ * companions are required/optional, what value format each accepts, and which
639
+ * messageId to emit when a check fails. The rule body itself only wires the
640
+ * visitors and message map; all value-format validation lives here.
641
+ */ function _array_like_to_array$g(arr, len) {
642
+ if (len == null || len > arr.length) len = arr.length;
643
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
644
+ return arr2;
645
+ }
646
+ function _array_with_holes$9(arr) {
647
+ if (Array.isArray(arr)) return arr;
648
+ }
649
+ function _iterable_to_array_limit$9(arr, i) {
650
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
651
+ if (_i == null) return;
652
+ var _arr = [];
653
+ var _n = true;
654
+ var _d = false;
655
+ var _s, _e;
656
+ try {
657
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
658
+ _arr.push(_s.value);
659
+ if (i && _arr.length === i) break;
660
+ }
661
+ } catch (err) {
662
+ _d = true;
663
+ _e = err;
664
+ } finally{
665
+ try {
666
+ if (!_n && _i["return"] != null) _i["return"]();
667
+ } finally{
668
+ if (_d) throw _e;
669
+ }
670
+ }
671
+ return _arr;
672
+ }
673
+ function _non_iterable_rest$9() {
674
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
675
+ }
676
+ function _sliced_to_array$9(arr, i) {
677
+ return _array_with_holes$9(arr) || _iterable_to_array_limit$9(arr, i) || _unsupported_iterable_to_array$g(arr, i) || _non_iterable_rest$9();
678
+ }
679
+ function _unsupported_iterable_to_array$g(o, minLen) {
680
+ if (!o) return;
681
+ if (typeof o === "string") return _array_like_to_array$g(o, minLen);
682
+ var n = Object.prototype.toString.call(o).slice(8, -1);
683
+ if (n === "Object" && o.constructor) n = o.constructor.name;
684
+ if (n === "Map" || n === "Set") return Array.from(n);
685
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$g(o, minLen);
686
+ }
687
+ /**
688
+ * Kebab-case slug pattern: lowercase letters/digits, words separated by single hyphens,
689
+ * starts with a letter. Used to validate slug/related/skill-ref values.
690
+ */ var KEBAB_SLUG_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
691
+ /**
692
+ * Pascal-case TypeScript identifier pattern (starts uppercase). Used for model
693
+ * identifiers and other `<ModelName>` style tag values.
694
+ */ var PASCAL_IDENTIFIER_PATTERN$1 = /^[A-Z][A-Za-z0-9_$]*$/;
695
+ /**
696
+ * Boolean tag-value vocabulary (case-insensitive). `''` and `true|yes` map to
697
+ * true; `false|no` map to false. Anything else is flagged as invalid.
698
+ */ var TRUE_TAG_VALUES = new Set([
699
+ '',
700
+ 'true',
701
+ 'yes'
702
+ ]);
703
+ var FALSE_TAG_VALUES = new Set([
704
+ 'false',
705
+ 'no'
706
+ ]);
707
+ /**
708
+ * Splits a comma-separated tag-value string into trimmed items, preserving order.
709
+ *
710
+ * @param value - Raw text following the tag name on a single line.
711
+ * @returns Non-empty trimmed segments in declaration order.
712
+ */ function splitCommaSeparated(value) {
713
+ return value.split(',').map(function(item) {
714
+ return item.trim();
715
+ }).filter(function(item) {
716
+ return item.length > 0;
717
+ });
718
+ }
719
+ /**
720
+ * Parses a boolean tag value using the workspace vocabulary
721
+ * (`''`/`true`/`yes` → true; `false`/`no` → false). Other inputs return `undefined`.
722
+ *
723
+ * @param text - The trimmed tag value text.
724
+ * @returns The parsed boolean, or `undefined` when the text is not in the vocabulary.
725
+ */ function parseBooleanTagValue(text) {
726
+ var lowered = text.trim().toLowerCase();
727
+ var result;
728
+ if (TRUE_TAG_VALUES.has(lowered)) {
729
+ result = true;
730
+ } else if (FALSE_TAG_VALUES.has(lowered)) {
731
+ result = false;
732
+ }
733
+ return result;
734
+ }
735
+ /**
736
+ * Returns the source-text offset of an offset-within-comment-value, given a Block comment node.
737
+ *
738
+ * @param commentNode - The ESLint Block comment AST node.
739
+ * @param valueOffset - The character offset within `comment.value`.
740
+ * @returns The character offset in the source file.
741
+ */ function commentValueToSourceOffset(commentNode, valueOffset) {
742
+ return commentNode.range[0] + 2 + valueOffset;
743
+ }
744
+ /**
745
+ * Returns the family marker + companion tag list for the given parsed JSDoc.
746
+ * Family membership is determined by tag-name prefix.
747
+ *
748
+ * @param parsed - The parsed JSDoc.
749
+ * @param marker - The bare family marker (e.g. `'dbxPipe'`).
750
+ * @returns The marker tag (if present), and all companion tags in source order.
751
+ */ function findFamilyTags(parsed, marker) {
752
+ var familyTags = parsed.tags.filter(function(t) {
753
+ return t.tag === marker || t.tag.startsWith(marker);
754
+ });
755
+ var markerTag = parsed.tags.find(function(t) {
756
+ return t.tag === marker;
757
+ });
758
+ return {
759
+ markerTag: markerTag,
760
+ familyTags: familyTags
761
+ };
762
+ }
763
+ /**
764
+ * Groups companion tags (those after the marker) by suffix. Marker entries are excluded.
765
+ *
766
+ * @param familyTags - The family tag list from {@link findFamilyTags}.
767
+ * @param marker - The bare family marker.
768
+ * @returns Lookup keyed by companion-suffix listing matching tags in declaration order.
769
+ */ function groupCompanionsBySuffix(familyTags, marker) {
770
+ var result = new Map();
771
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
772
+ try {
773
+ for(var _iterator = familyTags[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
774
+ var tag = _step.value;
775
+ var _result_get;
776
+ if (tag.tag === marker) continue;
777
+ var suffix = tag.tag.slice(marker.length);
778
+ var list = (_result_get = result.get(suffix)) !== null && _result_get !== void 0 ? _result_get : [];
779
+ list.push(tag);
780
+ result.set(suffix, list);
781
+ }
782
+ } catch (err) {
783
+ _didIteratorError = true;
784
+ _iteratorError = err;
785
+ } finally{
786
+ try {
787
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
788
+ _iterator.return();
789
+ }
790
+ } finally{
791
+ if (_didIteratorError) {
792
+ throw _iteratorError;
793
+ }
794
+ }
795
+ }
796
+ return result;
797
+ }
798
+ /**
799
+ * Per-format validators dispatched from {@link validateCompanionValue}. Each entry validates
800
+ * a single non-empty tag value and emits zero or more violations.
801
+ */ var VALUE_VALIDATORS = {
802
+ marker: function marker() {
803
+ return undefined;
804
+ },
805
+ 'free-text': function() {
806
+ return undefined;
807
+ },
808
+ 'comma-list-free-text': function() {
809
+ return undefined;
810
+ },
811
+ 'kebab-slug': function(param) {
812
+ var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
813
+ if (!KEBAB_SLUG_PATTERN.test(value)) emit({
814
+ kind: 'invalid-kebab',
815
+ suffix: spec.suffix,
816
+ value: value,
817
+ lineIndex: lineIndex
818
+ });
819
+ },
820
+ enum: function _enum(param) {
821
+ var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
822
+ var format = spec.format;
823
+ if (!format.values.includes(value)) emit({
824
+ kind: 'invalid-enum',
825
+ suffix: spec.suffix,
826
+ value: value,
827
+ allowed: format.values,
828
+ lineIndex: lineIndex
829
+ });
830
+ },
831
+ 'pascal-identifier': function(param) {
832
+ var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
833
+ if (!PASCAL_IDENTIFIER_PATTERN$1.test(value)) emit({
834
+ kind: 'invalid-pascal',
835
+ suffix: spec.suffix,
836
+ value: value,
837
+ lineIndex: lineIndex
838
+ });
839
+ },
840
+ 'comma-list-kebab-slug': function(param) {
841
+ var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
842
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
843
+ try {
844
+ for(var _iterator = splitCommaSeparated(value)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
845
+ var item = _step.value;
846
+ if (!KEBAB_SLUG_PATTERN.test(item)) emit({
847
+ kind: 'comma-item-not-kebab',
848
+ suffix: spec.suffix,
849
+ value: item,
850
+ lineIndex: lineIndex
851
+ });
852
+ }
853
+ } catch (err) {
854
+ _didIteratorError = true;
855
+ _iteratorError = err;
856
+ } finally{
857
+ try {
858
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
859
+ _iterator.return();
860
+ }
861
+ } finally{
862
+ if (_didIteratorError) {
863
+ throw _iteratorError;
864
+ }
865
+ }
866
+ }
867
+ },
868
+ 'comma-list-lowercase': function(param) {
869
+ var spec = param.spec, tag = param.tag, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
870
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
871
+ try {
872
+ for(var _iterator = splitCommaSeparated(value)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
873
+ var item = _step.value;
874
+ if (/[A-Z]/.test(item)) emit({
875
+ kind: 'tags-not-lowercase',
876
+ suffix: spec.suffix,
877
+ value: item,
878
+ lineIndex: lineIndex,
879
+ raw: tag
880
+ });
881
+ }
882
+ } catch (err) {
883
+ _didIteratorError = true;
884
+ _iteratorError = err;
885
+ } finally{
886
+ try {
887
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
888
+ _iterator.return();
889
+ }
890
+ } finally{
891
+ if (_didIteratorError) {
892
+ throw _iteratorError;
893
+ }
894
+ }
895
+ }
896
+ },
897
+ boolean: function boolean(param) {
898
+ var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
899
+ if (parseBooleanTagValue(value) === undefined) emit({
900
+ kind: 'invalid-boolean',
901
+ suffix: spec.suffix,
902
+ value: value,
903
+ lineIndex: lineIndex
904
+ });
905
+ }
906
+ };
907
+ /**
908
+ * Validates one companion tag's value against the configured {@link DbxTagFormat}
909
+ * and emits zero-or-more violations. Used by {@link checkDbxTagFamily} to keep
910
+ * each rule's body small.
911
+ *
912
+ * @param spec - The companion spec being validated.
913
+ * @param tags - All occurrences of the companion in source order.
914
+ * @param emit - Callback for each violation.
915
+ */ function validateCompanionValue(spec, tags, emit) {
916
+ if (spec.format.kind === 'marker') return;
917
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
918
+ try {
919
+ for(var _iterator = tags[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
920
+ var tag = _step.value;
921
+ var value = tag.description.trim();
922
+ var lineIndex = tag.startLineIndex;
923
+ if (value.length === 0) {
924
+ if (spec.format.kind !== 'boolean') emit({
925
+ kind: 'empty',
926
+ suffix: spec.suffix,
927
+ lineIndex: lineIndex
928
+ });
929
+ continue;
930
+ }
931
+ var validator = VALUE_VALIDATORS[spec.format.kind];
932
+ if (validator) validator({
933
+ spec: spec,
934
+ tag: tag,
935
+ value: value,
936
+ lineIndex: lineIndex,
937
+ emit: emit
938
+ });
939
+ }
940
+ } catch (err) {
941
+ _didIteratorError = true;
942
+ _iteratorError = err;
943
+ } finally{
944
+ try {
945
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
946
+ _iterator.return();
947
+ }
948
+ } finally{
949
+ if (_didIteratorError) {
950
+ throw _iteratorError;
951
+ }
952
+ }
953
+ }
954
+ }
955
+ function emitUnknownCompanions(groups, knownSuffixes, emit) {
956
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
957
+ try {
958
+ for(var _iterator = groups.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
959
+ var _step_value = _sliced_to_array$9(_step.value, 2), suffix = _step_value[0], instances = _step_value[1];
960
+ if (knownSuffixes.has(suffix)) continue;
961
+ var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
962
+ try {
963
+ for(var _iterator1 = instances[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
964
+ var tag = _step1.value;
965
+ emit({
966
+ kind: 'unknown',
967
+ suffix: suffix,
968
+ lineIndex: tag.startLineIndex
969
+ });
970
+ }
971
+ } catch (err) {
972
+ _didIteratorError1 = true;
973
+ _iteratorError1 = err;
974
+ } finally{
975
+ try {
976
+ if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
977
+ _iterator1.return();
978
+ }
979
+ } finally{
980
+ if (_didIteratorError1) {
981
+ throw _iteratorError1;
982
+ }
983
+ }
984
+ }
985
+ }
986
+ } catch (err) {
987
+ _didIteratorError = true;
988
+ _iteratorError = err;
989
+ } finally{
990
+ try {
991
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
992
+ _iterator.return();
993
+ }
994
+ } finally{
995
+ if (_didIteratorError) {
996
+ throw _iteratorError;
997
+ }
998
+ }
999
+ }
1000
+ }
1001
+ function emitDuplicateCompanions(companion, instances, emit) {
1002
+ for(var i = 1; i < instances.length; i += 1){
1003
+ emit({
1004
+ kind: 'duplicate',
1005
+ suffix: companion.suffix,
1006
+ lineIndex: instances[i].startLineIndex
1007
+ });
1008
+ }
1009
+ }
1010
+ function checkCompanion(input) {
1011
+ var companion = input.companion, instances = input.instances, markerLineIndex = input.markerLineIndex, emit = input.emit;
1012
+ if (instances.length === 0) {
1013
+ if (companion.required) emit({
1014
+ kind: 'missing',
1015
+ suffix: companion.suffix,
1016
+ lineIndex: markerLineIndex
1017
+ });
1018
+ return;
1019
+ }
1020
+ if (!companion.multiple && instances.length > 1) emitDuplicateCompanions(companion, instances, emit);
1021
+ validateCompanionValue(companion, instances, emit);
1022
+ }
1023
+ /**
1024
+ * Validates a `@dbx<Family>` marker plus its companion tags against the supplied spec.
1025
+ * Reports unknown companions, missing required companions, duplicates, and per-companion
1026
+ * value violations through `input.emit`.
1027
+ *
1028
+ * @param input - Parsed JSDoc, family spec, resolved marker/companion tags, and emit sink.
1029
+ *
1030
+ * @example
1031
+ * ```ts
1032
+ * checkDbxTagFamily({ parsed, spec, markerTag, familyTags, emit });
1033
+ * ```
1034
+ */ function checkDbxTagFamily(input) {
1035
+ var spec = input.spec, markerTag = input.markerTag, familyTags = input.familyTags, emit = input.emit;
1036
+ var knownSuffixes = new Set(spec.companions.map(function(c) {
1037
+ return c.suffix;
1038
+ }));
1039
+ var groups = groupCompanionsBySuffix(familyTags, spec.marker);
1040
+ emitUnknownCompanions(groups, knownSuffixes, emit);
1041
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
1042
+ try {
1043
+ for(var _iterator = spec.companions[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
1044
+ var companion = _step.value;
1045
+ var _groups_get;
1046
+ var instances = (_groups_get = groups.get(companion.suffix)) !== null && _groups_get !== void 0 ? _groups_get : [];
1047
+ checkCompanion({
1048
+ companion: companion,
1049
+ instances: instances,
1050
+ markerLineIndex: markerTag.startLineIndex,
1051
+ emit: emit
1052
+ });
1053
+ }
1054
+ } catch (err) {
1055
+ _didIteratorError = true;
1056
+ _iteratorError = err;
1057
+ } finally{
1058
+ try {
1059
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
1060
+ _iterator.return();
1061
+ }
1062
+ } finally{
1063
+ if (_didIteratorError) {
1064
+ throw _iteratorError;
1065
+ }
1066
+ }
1067
+ }
1068
+ }
1069
+ /**
1070
+ * Translates a JSDoc-line violation into an ESLint `context.report()` call by computing the
1071
+ * source range of the offending line and attaching the supplied message + optional fixer.
1072
+ *
1073
+ * @param input - Reporting context (comment node, parsed JSDoc, source code, line index, message id, optional data + fixer, report sink).
1074
+ *
1075
+ * @example
1076
+ * ```ts
1077
+ * reportOnJsdocLine({ commentNode, parsed, sourceCode, lineIndex: tag.startLineIndex, messageId: 'unknown', report: context.report });
1078
+ * ```
1079
+ */ function reportOnJsdocLine(input) {
1080
+ var _ref, _ref1;
1081
+ var _line_text;
1082
+ var commentNode = input.commentNode, parsed = input.parsed, sourceCode = input.sourceCode, lineIndex = input.lineIndex, messageId = input.messageId, data = input.data, report = input.report, fix = input.fix;
1083
+ var line = parsed.lines[lineIndex];
1084
+ var startInValue = (_ref = line === null || line === void 0 ? void 0 : line.textOffsetStart) !== null && _ref !== void 0 ? _ref : 0;
1085
+ var endInValue = startInValue + ((_ref1 = line === null || line === void 0 ? void 0 : (_line_text = line.text) === null || _line_text === void 0 ? void 0 : _line_text.length) !== null && _ref1 !== void 0 ? _ref1 : 0);
1086
+ var start = commentValueToSourceOffset(commentNode, startInValue);
1087
+ var end = commentValueToSourceOffset(commentNode, endInValue);
1088
+ report({
1089
+ loc: {
1090
+ type: 'SourceLocation',
1091
+ start: sourceCode.getLocFromIndex(start),
1092
+ end: sourceCode.getLocFromIndex(end)
1093
+ },
1094
+ messageId: messageId,
1095
+ data: data,
1096
+ fix: fix
1097
+ });
1098
+ }
1099
+ /**
1100
+ * Computes an auto-fix descriptor that lowercases every token after a `@dbx<Family>Tags` tag
1101
+ * name. Returns `undefined` when the line is already canonical so the caller can short-circuit.
1102
+ *
1103
+ * @param input - Fix context (comment node, parsed JSDoc, source code, target tag).
1104
+ * @returns Replacement range + text when a rewrite is needed; otherwise `undefined`.
1105
+ *
1106
+ * @example
1107
+ * ```ts
1108
+ * const fix = buildLowercaseTagsFix({ commentNode, parsed, sourceCode, tag });
1109
+ * ```
1110
+ */ function buildLowercaseTagsFix(input) {
1111
+ var commentNode = input.commentNode, parsed = input.parsed, sourceCode = input.sourceCode, tag = input.tag;
1112
+ var tagLine = parsed.lines[tag.startLineIndex];
1113
+ var result;
1114
+ if (tagLine) {
1115
+ var tagLineSourceStart = commentValueToSourceOffset(commentNode, tagLine.textOffsetStart);
1116
+ var tagLineSourceEnd = tagLineSourceStart + tagLine.text.length;
1117
+ var sourceText = sourceCode.getText();
1118
+ var lineSource = sourceText.slice(tagLineSourceStart, tagLineSourceEnd);
1119
+ var lowered = lineSource.replace(/^(@[A-Za-z]+\s+)(.*)$/, function(_match, prefix, body) {
1120
+ return "".concat(prefix).concat(body.toLowerCase());
1121
+ });
1122
+ if (lowered !== lineSource) {
1123
+ result = {
1124
+ startOffset: tagLineSourceStart,
1125
+ endOffset: tagLineSourceEnd,
1126
+ replacement: lowered
1127
+ };
1128
+ }
1129
+ }
1130
+ return result;
1131
+ }
1132
+
10
1133
  function _array_like_to_array$f(arr, len) {
11
1134
  if (len == null || len > arr.length) len = arr.length;
12
1135
  for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
@@ -729,10 +1852,10 @@ function _unsupported_iterable_to_array$e(o, minLen) {
729
1852
  var registry = createImportRegistry();
730
1853
  var stack = [];
731
1854
  function jsdocHasMarker(anchor) {
732
- var jsdoc = eslint.leadingJsdocFor(sourceCode, anchor);
1855
+ var jsdoc = leadingJsdocFor(sourceCode, anchor);
733
1856
  var result = false;
734
1857
  if (jsdoc) {
735
- var parsed = eslint.parseJsdocComment(jsdoc.value);
1858
+ var parsed = parseJsdocComment(jsdoc.value);
736
1859
  result = parsed.tags.some(function(t) {
737
1860
  return t.tag === markerTag;
738
1861
  });
@@ -911,10 +2034,10 @@ var DEFAULT_STRIPPABLE_SUFFIXES = [
911
2034
  var allowedSuffixes = (_options_allowedSuffixes = options.allowedSuffixes) !== null && _options_allowedSuffixes !== void 0 ? _options_allowedSuffixes : [];
912
2035
  var strippableSuffixes = (_options_strippableSuffixes = options.strippableSuffixes) !== null && _options_strippableSuffixes !== void 0 ? _options_strippableSuffixes : DEFAULT_STRIPPABLE_SUFFIXES;
913
2036
  function isTagged(anchor) {
914
- var jsdoc = eslint.leadingJsdocFor(sourceCode, anchor);
2037
+ var jsdoc = leadingJsdocFor(sourceCode, anchor);
915
2038
  var result = false;
916
2039
  if (jsdoc) {
917
- var parsed = eslint.parseJsdocComment(jsdoc.value);
2040
+ var parsed = parseJsdocComment(jsdoc.value);
918
2041
  result = parsed.tags.some(function(t) {
919
2042
  return t.tag === markerTag;
920
2043
  });
@@ -1180,7 +2303,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1180
2303
  };
1181
2304
  function handleCommaItem(input) {
1182
2305
  var commentNode = input.commentNode, parsed = input.parsed, suffix = input.suffix, value = input.value, lineIndex = input.lineIndex;
1183
- if (suffix === 'Related') eslint.reportOnJsdocLine({
2306
+ if (suffix === 'Related') reportOnJsdocLine({
1184
2307
  commentNode: commentNode,
1185
2308
  parsed: parsed,
1186
2309
  sourceCode: sourceCode,
@@ -1191,7 +2314,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1191
2314
  },
1192
2315
  report: context.report
1193
2316
  });
1194
- else if (suffix === 'SkillRefs') eslint.reportOnJsdocLine({
2317
+ else if (suffix === 'SkillRefs') reportOnJsdocLine({
1195
2318
  commentNode: commentNode,
1196
2319
  parsed: parsed,
1197
2320
  sourceCode: sourceCode,
@@ -1205,7 +2328,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1205
2328
  }
1206
2329
  function handleTagsNotLowercase(commentNode, parsed, v) {
1207
2330
  if (v.suffix !== 'Tags') return;
1208
- var fix = eslint.buildLowercaseTagsFix({
2331
+ var fix = buildLowercaseTagsFix({
1209
2332
  commentNode: commentNode,
1210
2333
  parsed: parsed,
1211
2334
  sourceCode: sourceCode,
@@ -1217,7 +2340,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1217
2340
  fix.endOffset
1218
2341
  ], fix.replacement);
1219
2342
  } : undefined;
1220
- eslint.reportOnJsdocLine({
2343
+ reportOnJsdocLine({
1221
2344
  commentNode: commentNode,
1222
2345
  parsed: parsed,
1223
2346
  sourceCode: sourceCode,
@@ -1234,7 +2357,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1234
2357
  switch(v.kind){
1235
2358
  case 'missing':
1236
2359
  case 'empty':
1237
- if (v.suffix === 'Model') eslint.reportOnJsdocLine({
2360
+ if (v.suffix === 'Model') reportOnJsdocLine({
1238
2361
  commentNode: commentNode,
1239
2362
  parsed: parsed,
1240
2363
  sourceCode: sourceCode,
@@ -1244,7 +2367,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1244
2367
  });
1245
2368
  break;
1246
2369
  case 'invalid-kebab':
1247
- if (v.suffix === 'Slug') eslint.reportOnJsdocLine({
2370
+ if (v.suffix === 'Slug') reportOnJsdocLine({
1248
2371
  commentNode: commentNode,
1249
2372
  parsed: parsed,
1250
2373
  sourceCode: sourceCode,
@@ -1257,7 +2380,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1257
2380
  });
1258
2381
  break;
1259
2382
  case 'invalid-pascal':
1260
- if (v.suffix === 'Model') eslint.reportOnJsdocLine({
2383
+ if (v.suffix === 'Model') reportOnJsdocLine({
1261
2384
  commentNode: commentNode,
1262
2385
  parsed: parsed,
1263
2386
  sourceCode: sourceCode,
@@ -1270,7 +2393,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1270
2393
  });
1271
2394
  break;
1272
2395
  case 'invalid-enum':
1273
- if (v.suffix === 'Scope') eslint.reportOnJsdocLine({
2396
+ if (v.suffix === 'Scope') reportOnJsdocLine({
1274
2397
  commentNode: commentNode,
1275
2398
  parsed: parsed,
1276
2399
  sourceCode: sourceCode,
@@ -1284,7 +2407,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1284
2407
  });
1285
2408
  break;
1286
2409
  case 'invalid-boolean':
1287
- eslint.reportOnJsdocLine({
2410
+ reportOnJsdocLine({
1288
2411
  commentNode: commentNode,
1289
2412
  parsed: parsed,
1290
2413
  sourceCode: sourceCode,
@@ -1310,7 +2433,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1310
2433
  handleTagsNotLowercase(commentNode, parsed, v);
1311
2434
  break;
1312
2435
  case 'unknown':
1313
- eslint.reportOnJsdocLine({
2436
+ reportOnJsdocLine({
1314
2437
  commentNode: commentNode,
1315
2438
  parsed: parsed,
1316
2439
  sourceCode: sourceCode,
@@ -1324,7 +2447,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1324
2447
  });
1325
2448
  break;
1326
2449
  case 'duplicate':
1327
- eslint.reportOnJsdocLine({
2450
+ reportOnJsdocLine({
1328
2451
  commentNode: commentNode,
1329
2452
  parsed: parsed,
1330
2453
  sourceCode: sourceCode,
@@ -1339,11 +2462,10 @@ var DEFAULT_KNOWN_COMPANIONS = [
1339
2462
  }
1340
2463
  }
1341
2464
  function checkJsdoc(commentNode, parsed) {
1342
- var _findFamilyTags = eslint.findFamilyTags(parsed, spec.marker), markerTag = _findFamilyTags.markerTag, familyTags = _findFamilyTags.familyTags;
2465
+ var _findFamilyTags = findFamilyTags(parsed, spec.marker), markerTag = _findFamilyTags.markerTag, familyTags = _findFamilyTags.familyTags;
1343
2466
  if (familyTags.length === 0 || requireBareMarker && !markerTag) return;
1344
2467
  var triggerTag = markerTag !== null && markerTag !== void 0 ? markerTag : familyTags[0];
1345
- eslint.checkDbxTagFamily({
1346
- parsed: parsed,
2468
+ checkDbxTagFamily({
1347
2469
  spec: spec,
1348
2470
  markerTag: triggerTag,
1349
2471
  familyTags: familyTags,
@@ -1364,7 +2486,7 @@ var DEFAULT_KNOWN_COMPANIONS = [
1364
2486
  });
1365
2487
  var result = false;
1366
2488
  if (tag) {
1367
- var value = eslint.parseBooleanTagValue(tag.description.trim());
2489
+ var value = parseBooleanTagValue(tag.description.trim());
1368
2490
  result = value === true;
1369
2491
  }
1370
2492
  return result;
@@ -1505,10 +2627,10 @@ var DEFAULT_KNOWN_COMPANIONS = [
1505
2627
  function checkFunction(node) {
1506
2628
  if (!node.body) return;
1507
2629
  var anchor = node.parent && (node.parent.type === 'ExportNamedDeclaration' || node.parent.type === 'ExportDefaultDeclaration') ? node.parent : node;
1508
- var commentNode = eslint.leadingJsdocFor(sourceCode, eslint.getStatementAnchor(anchor));
2630
+ var commentNode = leadingJsdocFor(sourceCode, getStatementAnchor(anchor));
1509
2631
  if (!commentNode) return;
1510
- var parsed = eslint.parseJsdocComment(commentNode.value);
1511
- var markerTag = eslint.findFamilyTags(parsed, spec.marker).markerTag;
2632
+ var parsed = parseJsdocComment(commentNode.value);
2633
+ var markerTag = findFamilyTags(parsed, spec.marker).markerTag;
1512
2634
  if (!markerTag && requireBareMarker) return;
1513
2635
  checkJsdoc(commentNode, parsed);
1514
2636
  checkBody(node, parsed);
@@ -1606,10 +2728,10 @@ function _type_of$8(obj) {
1606
2728
  (_options_presumedTaggedSuffix = options.presumedTaggedSuffix) !== null && _options_presumedTaggedSuffix !== void 0 ? _options_presumedTaggedSuffix : DEFAULT_PRESUMED_TAGGED_SUFFIX;
1607
2729
  var registry = createImportRegistry();
1608
2730
  function jsdocFlagsDispatcher(anchor) {
1609
- var jsdoc = eslint.leadingJsdocFor(sourceCode, anchor);
2731
+ var jsdoc = leadingJsdocFor(sourceCode, anchor);
1610
2732
  var result = false;
1611
2733
  if (jsdoc) {
1612
- var parsed = eslint.parseJsdocComment(jsdoc.value);
2734
+ var parsed = parseJsdocComment(jsdoc.value);
1613
2735
  var hasMarker = parsed.tags.some(function(t) {
1614
2736
  return t.tag === markerTag;
1615
2737
  });
@@ -1618,7 +2740,7 @@ function _type_of$8(obj) {
1618
2740
  return t.tag === dispatcherTagName;
1619
2741
  });
1620
2742
  if (dispatcherTag) {
1621
- var value = eslint.parseBooleanTagValue(dispatcherTag.description.trim());
2743
+ var value = parseBooleanTagValue(dispatcherTag.description.trim());
1622
2744
  result = value === true;
1623
2745
  }
1624
2746
  }
@@ -1982,7 +3104,7 @@ function _unsupported_iterable_to_array$c(o, minLen) {
1982
3104
  */ var MODEL_FIREBASE_CRUD_FUNCTION_CONFIG_MAP_TYPE_NAME = 'ModelFirebaseCrudFunctionConfigMap';
1983
3105
  /**
1984
3106
  * CRUD verb names supported by `ModelFirebaseCrudFunctionConfigMap`. Mirrors the
1985
- * `create | read | update | delete | query` verbs in the type definition at
3107
+ * `create | read | update | delete | query | invoke` verbs in the type definition at
1986
3108
  * `packages/firebase/src/lib/client/function/model.function.factory.ts`.
1987
3109
  */ var DEFAULT_CRUD_VERB_NAMES = [
1988
3110
  'create',
@@ -15052,7 +16174,7 @@ function reportModelKey(context, modelKey) {
15052
16174
  }
15053
16175
  var anchor = anchorInfo.anchor, reportNode = anchorInfo.reportNode;
15054
16176
  var exportName = (_ref = reportNode === null || reportNode === void 0 ? void 0 : reportNode.name) !== null && _ref !== void 0 ? _ref : '<anonymous>';
15055
- var jsdoc = eslint.leadingJsdocFor(sourceCode, anchor);
16177
+ var jsdoc = leadingJsdocFor(sourceCode, anchor);
15056
16178
  if (jsdoc == null) {
15057
16179
  context.report({
15058
16180
  node: reportNode,
@@ -15063,7 +16185,7 @@ function reportModelKey(context, modelKey) {
15063
16185
  });
15064
16186
  return;
15065
16187
  }
15066
- var parsed = eslint.parseJsdocComment(jsdoc.value);
16188
+ var parsed = parseJsdocComment(jsdoc.value);
15067
16189
  var matches = parsed.tags.filter(function(t) {
15068
16190
  return t.tag === tagName;
15069
16191
  });
@@ -15287,9 +16409,9 @@ var MODEL_TYPE_VALUE_PATTERN = /^[a-z][A-Za-z0-9_$]*$/;
15287
16409
  var _ref;
15288
16410
  var _node_id;
15289
16411
  var anchor = node.parent && (node.parent.type === 'ExportNamedDeclaration' || node.parent.type === 'ExportDefaultDeclaration') ? node.parent : node;
15290
- var jsdoc = eslint.leadingJsdocFor(sourceCode, anchor);
16412
+ var jsdoc = leadingJsdocFor(sourceCode, anchor);
15291
16413
  if (jsdoc == null) return;
15292
- var parsed = eslint.parseJsdocComment(jsdoc.value);
16414
+ var parsed = parseJsdocComment(jsdoc.value);
15293
16415
  var hasMarker = parsed.tags.some(function(t) {
15294
16416
  return t.tag === modelMarkerTag;
15295
16417
  });
@@ -15368,7 +16490,7 @@ function modelTypesFromFactoryComment(comment, factoryTag) {
15368
16490
  var out = [];
15369
16491
  var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
15370
16492
  try {
15371
- for(var _iterator = eslint.parseJsdocComment(comment.value).tags[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
16493
+ for(var _iterator = parseJsdocComment(comment.value).tags[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
15372
16494
  var tag = _step.value;
15373
16495
  if (tag.tag !== factoryTag) continue;
15374
16496
  var firstToken = tag.description.trim().split(/\s+/)[0];
@@ -15521,7 +16643,7 @@ function reportMutuallyExclusiveMarkers(ctx, markers) {
15521
16643
  });
15522
16644
  if (exclusiveMarkers.length <= 1) return;
15523
16645
  for(var i = 1; i < exclusiveMarkers.length; i += 1){
15524
- eslint.reportOnJsdocLine({
16646
+ reportOnJsdocLine({
15525
16647
  commentNode: ctx.commentNode,
15526
16648
  parsed: ctx.parsed,
15527
16649
  sourceCode: ctx.sourceCode,
@@ -15541,7 +16663,7 @@ function reportPropertyOnlyTags(ctx, companions) {
15541
16663
  try {
15542
16664
  for(var _iterator1 = ((_companions_get = companions.get(propOnly)) !== null && _companions_get !== void 0 ? _companions_get : [])[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
15543
16665
  var tag = _step1.value;
15544
- eslint.reportOnJsdocLine({
16666
+ reportOnJsdocLine({
15545
16667
  commentNode: ctx.commentNode,
15546
16668
  parsed: ctx.parsed,
15547
16669
  sourceCode: ctx.sourceCode,
@@ -15594,7 +16716,7 @@ function reportUnknownModelCompanions(ctx, companions, knownCompanions) {
15594
16716
  try {
15595
16717
  for(var _iterator1 = instances[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
15596
16718
  var tag = _step1.value;
15597
- eslint.reportOnJsdocLine({
16719
+ reportOnJsdocLine({
15598
16720
  commentNode: ctx.commentNode,
15599
16721
  parsed: ctx.parsed,
15600
16722
  sourceCode: ctx.sourceCode,
@@ -15647,7 +16769,7 @@ function reportModelDuplicates(ctx, companions, knownCompanions) {
15647
16769
  var instances = (_companions_get = companions.get(suffix)) !== null && _companions_get !== void 0 ? _companions_get : [];
15648
16770
  if (instances.length <= 1) continue;
15649
16771
  for(var i = 1; i < instances.length; i += 1){
15650
- eslint.reportOnJsdocLine({
16772
+ reportOnJsdocLine({
15651
16773
  commentNode: ctx.commentNode,
15652
16774
  parsed: ctx.parsed,
15653
16775
  sourceCode: ctx.sourceCode,
@@ -15681,7 +16803,7 @@ function reportArchetypeTag(ctx, tag) {
15681
16803
  var spaceIdx = text.indexOf(' ');
15682
16804
  var slug = spaceIdx >= 0 ? text.slice(0, spaceIdx).trim() : text;
15683
16805
  if (!ARCHETYPE_SLUG_PATTERN.test(slug)) {
15684
- eslint.reportOnJsdocLine({
16806
+ reportOnJsdocLine({
15685
16807
  commentNode: ctx.commentNode,
15686
16808
  parsed: ctx.parsed,
15687
16809
  sourceCode: ctx.sourceCode,
@@ -15703,7 +16825,7 @@ function reportArchetypeTag(ctx, tag) {
15703
16825
  var trimmed = pair.trim();
15704
16826
  if (trimmed.length === 0) continue;
15705
16827
  if (!ARCHETYPE_AXIS_PATTERN.test(trimmed)) {
15706
- eslint.reportOnJsdocLine({
16828
+ reportOnJsdocLine({
15707
16829
  commentNode: ctx.commentNode,
15708
16830
  parsed: ctx.parsed,
15709
16831
  sourceCode: ctx.sourceCode,
@@ -15734,7 +16856,7 @@ function reportArchetypeTag(ctx, tag) {
15734
16856
  function reportReadTag(ctx, tag) {
15735
16857
  var value = tag.description.trim();
15736
16858
  if (value.length === 0) {
15737
- eslint.reportOnJsdocLine({
16859
+ reportOnJsdocLine({
15738
16860
  commentNode: ctx.commentNode,
15739
16861
  parsed: ctx.parsed,
15740
16862
  sourceCode: ctx.sourceCode,
@@ -15749,7 +16871,7 @@ function reportReadTag(ctx, tag) {
15749
16871
  }
15750
16872
  var firstToken = value.split(/\s+/)[0];
15751
16873
  if (!READ_LEVEL_VALUES.includes(firstToken)) {
15752
- eslint.reportOnJsdocLine({
16874
+ reportOnJsdocLine({
15753
16875
  commentNode: ctx.commentNode,
15754
16876
  parsed: ctx.parsed,
15755
16877
  sourceCode: ctx.sourceCode,
@@ -15770,7 +16892,7 @@ function reportMissingRead(ctx, markers, companions) {
15770
16892
  });
15771
16893
  if (rootMarker == null) return;
15772
16894
  if (((_companions_get = companions.get('Read')) !== null && _companions_get !== void 0 ? _companions_get : []).length > 0) return;
15773
- eslint.reportOnJsdocLine({
16895
+ reportOnJsdocLine({
15774
16896
  commentNode: ctx.commentNode,
15775
16897
  parsed: ctx.parsed,
15776
16898
  sourceCode: ctx.sourceCode,
@@ -15785,7 +16907,7 @@ function reportMissingRead(ctx, markers, companions) {
15785
16907
  function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
15786
16908
  var text = tag.description.trim();
15787
16909
  if (text.length === 0) {
15788
- eslint.reportOnJsdocLine({
16910
+ reportOnJsdocLine({
15789
16911
  commentNode: ctx.commentNode,
15790
16912
  parsed: ctx.parsed,
15791
16913
  sourceCode: ctx.sourceCode,
@@ -15793,7 +16915,7 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
15793
16915
  messageId: 'compositeKeyMissingFrom',
15794
16916
  report: ctx.report
15795
16917
  });
15796
- eslint.reportOnJsdocLine({
16918
+ reportOnJsdocLine({
15797
16919
  commentNode: ctx.commentNode,
15798
16920
  parsed: ctx.parsed,
15799
16921
  sourceCode: ctx.sourceCode,
@@ -15831,7 +16953,7 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
15831
16953
  }
15832
16954
  }
15833
16955
  if (!hasFrom) {
15834
- eslint.reportOnJsdocLine({
16956
+ reportOnJsdocLine({
15835
16957
  commentNode: ctx.commentNode,
15836
16958
  parsed: ctx.parsed,
15837
16959
  sourceCode: ctx.sourceCode,
@@ -15841,7 +16963,7 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
15841
16963
  });
15842
16964
  }
15843
16965
  if (encoding == null) {
15844
- eslint.reportOnJsdocLine({
16966
+ reportOnJsdocLine({
15845
16967
  commentNode: ctx.commentNode,
15846
16968
  parsed: ctx.parsed,
15847
16969
  sourceCode: ctx.sourceCode,
@@ -15850,7 +16972,7 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
15850
16972
  report: ctx.report
15851
16973
  });
15852
16974
  } else if (!allowedEncodings.includes(encoding)) {
15853
- eslint.reportOnJsdocLine({
16975
+ reportOnJsdocLine({
15854
16976
  commentNode: ctx.commentNode,
15855
16977
  parsed: ctx.parsed,
15856
16978
  sourceCode: ctx.sourceCode,
@@ -15987,7 +17109,7 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
15987
17109
  var requireRead = options.requireRead !== false;
15988
17110
  function checkInterfaceJsdoc(commentNode) {
15989
17111
  var _companions_get, _companions_get1, _companions_get2, _companions_get3;
15990
- var parsed = eslint.parseJsdocComment(commentNode.value);
17112
+ var parsed = parseJsdocComment(commentNode.value);
15991
17113
  var _collectInterfaceTags = collectInterfaceTags(parsed), markers = _collectInterfaceTags.markers, companions = _collectInterfaceTags.companions;
15992
17114
  if (markers.length === 0 && companions.size === 0 || requireBareMarker && markers.length === 0) return;
15993
17115
  var ctx = {
@@ -16027,7 +17149,7 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
16027
17149
  var _tag_description_trim_split_;
16028
17150
  var value = (_tag_description_trim_split_ = tag1.description.trim().split(/\s+/)[0]) !== null && _tag_description_trim_split_ !== void 0 ? _tag_description_trim_split_ : '';
16029
17151
  if (value.length > 0 && !PASCAL_IDENTIFIER_PATTERN.test(value)) {
16030
- eslint.reportOnJsdocLine({
17152
+ reportOnJsdocLine({
16031
17153
  commentNode: commentNode,
16032
17154
  parsed: parsed,
16033
17155
  sourceCode: sourceCode,
@@ -16098,7 +17220,7 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
16098
17220
  }
16099
17221
  function visitInterface(node) {
16100
17222
  var anchor = node.parent && (node.parent.type === 'ExportNamedDeclaration' || node.parent.type === 'ExportDefaultDeclaration') ? node.parent : node;
16101
- var jsdoc = eslint.leadingJsdocFor(sourceCode, anchor);
17223
+ var jsdoc = leadingJsdocFor(sourceCode, anchor);
16102
17224
  if (jsdoc) checkInterfaceJsdoc(jsdoc);
16103
17225
  }
16104
17226
  return {
@@ -16107,6 +17229,415 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
16107
17229
  }
16108
17230
  };
16109
17231
 
17232
+ var SPEC_SUFFIX = '.spec.ts';
17233
+ /**
17234
+ * Classifies a spec filename against the conventions for the given parent
17235
+ * folder. Pure function — never touches the filesystem.
17236
+ *
17237
+ * @param config - Inputs.
17238
+ * @param config.filename - Bare filename including the `.spec.ts` suffix
17239
+ * (e.g. `job.scenario.requirement.spec.ts`).
17240
+ * @param config.parentFolderName - Name of the directory containing the file
17241
+ * (e.g. `job`). Used to detect cross-group misplacement.
17242
+ * @returns The classification.
17243
+ */ function classifySpecFile(config) {
17244
+ var filename = config.filename, parentFolderName = config.parentFolderName;
17245
+ var result;
17246
+ if (!filename.endsWith(SPEC_SUFFIX)) {
17247
+ result = {
17248
+ filename: filename,
17249
+ group: '',
17250
+ kind: 'non-spec',
17251
+ subgroups: [],
17252
+ isCanonical: false
17253
+ };
17254
+ } else {
17255
+ var _parts_;
17256
+ var stem = filename.slice(0, -SPEC_SUFFIX.length);
17257
+ var parts = stem.split('.');
17258
+ var group = (_parts_ = parts[0]) !== null && _parts_ !== void 0 ? _parts_ : '';
17259
+ if (group !== parentFolderName) {
17260
+ result = {
17261
+ filename: filename,
17262
+ group: group,
17263
+ kind: 'non-group',
17264
+ subgroups: [],
17265
+ isCanonical: false
17266
+ };
17267
+ } else {
17268
+ var rest = parts.slice(1);
17269
+ result = classifyRemainingSegments({
17270
+ filename: filename,
17271
+ group: group,
17272
+ rest: rest
17273
+ });
17274
+ }
17275
+ }
17276
+ return result;
17277
+ }
17278
+ function classifyRemainingSegments(config) {
17279
+ var filename = config.filename, group = config.group, rest = config.rest;
17280
+ var result;
17281
+ var crudIdx = rest.indexOf('crud');
17282
+ var scenarioIdx = rest.indexOf('scenario');
17283
+ if (crudIdx === 0) {
17284
+ if (rest.length === 1) {
17285
+ result = {
17286
+ filename: filename,
17287
+ group: group,
17288
+ kind: 'crud',
17289
+ subgroups: [],
17290
+ isCanonical: true
17291
+ };
17292
+ } else {
17293
+ result = {
17294
+ filename: filename,
17295
+ group: group,
17296
+ kind: 'crud-subgroup',
17297
+ subgroups: rest.slice(1),
17298
+ isCanonical: true
17299
+ };
17300
+ }
17301
+ } else if (scenarioIdx === 0) {
17302
+ if (rest.length === 1) {
17303
+ result = {
17304
+ filename: filename,
17305
+ group: group,
17306
+ kind: 'scenario',
17307
+ subgroups: [],
17308
+ isCanonical: true
17309
+ };
17310
+ } else {
17311
+ result = {
17312
+ filename: filename,
17313
+ group: group,
17314
+ kind: 'scenario-subgroup',
17315
+ subgroups: rest.slice(1),
17316
+ isCanonical: true
17317
+ };
17318
+ }
17319
+ } else if (crudIdx > 0) {
17320
+ var subgroups = rest.filter(function(_, i) {
17321
+ return i !== crudIdx;
17322
+ });
17323
+ var recommendedRename = buildCanonicalFilename({
17324
+ group: group,
17325
+ bucket: 'crud',
17326
+ subgroups: subgroups
17327
+ });
17328
+ result = {
17329
+ filename: filename,
17330
+ group: group,
17331
+ kind: 'crud-misplaced',
17332
+ subgroups: subgroups,
17333
+ isCanonical: false,
17334
+ recommendedRename: recommendedRename,
17335
+ driftReason: '`crud` segment is not directly after the group name.'
17336
+ };
17337
+ } else if (scenarioIdx > 0) {
17338
+ var subgroups1 = rest.filter(function(_, i) {
17339
+ return i !== scenarioIdx;
17340
+ });
17341
+ var recommendedRename1 = buildCanonicalFilename({
17342
+ group: group,
17343
+ bucket: 'scenario',
17344
+ subgroups: subgroups1
17345
+ });
17346
+ result = {
17347
+ filename: filename,
17348
+ group: group,
17349
+ kind: 'scenario-misplaced',
17350
+ subgroups: subgroups1,
17351
+ isCanonical: false,
17352
+ recommendedRename: recommendedRename1,
17353
+ driftReason: '`scenario` segment is not directly after the group name.'
17354
+ };
17355
+ } else if (rest.length === 0) {
17356
+ var recommendedRename2 = buildCanonicalFilename({
17357
+ group: group,
17358
+ bucket: 'scenario',
17359
+ subgroups: []
17360
+ });
17361
+ result = {
17362
+ filename: filename,
17363
+ group: group,
17364
+ kind: 'no-bucket',
17365
+ subgroups: [],
17366
+ isCanonical: false,
17367
+ recommendedRename: recommendedRename2,
17368
+ driftReason: 'Missing `crud` or `scenario` segment.'
17369
+ };
17370
+ } else {
17371
+ var recommendedRename3 = buildCanonicalFilename({
17372
+ group: group,
17373
+ bucket: 'scenario',
17374
+ subgroups: rest
17375
+ });
17376
+ result = {
17377
+ filename: filename,
17378
+ group: group,
17379
+ kind: 'no-bucket',
17380
+ subgroups: rest,
17381
+ isCanonical: false,
17382
+ recommendedRename: recommendedRename3,
17383
+ driftReason: 'Missing `crud` or `scenario` segment — defaulting suggestion to `scenario`.'
17384
+ };
17385
+ }
17386
+ return result;
17387
+ }
17388
+ /**
17389
+ * Renders a canonical spec filename for the given group + bucket + subgroup
17390
+ * chain. Pure data — used both by drift remediation and by the
17391
+ * `recommendSpecPath()` helper below.
17392
+ *
17393
+ * @param config - Inputs.
17394
+ * @param config.group - The model-group name (e.g. `job`).
17395
+ * @param config.bucket - `crud` or `scenario`.
17396
+ * @param config.subgroups - Optional ordered subgroup segments
17397
+ * (e.g. `['requirement','worker']`).
17398
+ * @returns The canonical filename (e.g. `job.scenario.requirement.worker.spec.ts`).
17399
+ */ function buildCanonicalFilename(config) {
17400
+ var group = config.group, bucket = config.bucket, subgroups = config.subgroups;
17401
+ var tail = subgroups.length === 0 ? '' : ".".concat(subgroups.join('.'));
17402
+ return "".concat(group, ".").concat(bucket).concat(tail, ".spec.ts");
17403
+ }
17404
+
17405
+ /**
17406
+ * Default subpath segment (relative to an app source root) below which the
17407
+ * rule expects model-group function folders. Matches the convention used by
17408
+ * `<apiDir>/src/app/function/<group>/`.
17409
+ */ var DEFAULT_FUNCTION_DIR_SEGMENT = 'src/app/function';
17410
+ function matchFunctionSpecPath(filename, functionDirSegment) {
17411
+ var result;
17412
+ if (filename.endsWith('.spec.ts')) {
17413
+ var normalized = filename.split(node_path.sep).join('/');
17414
+ var marker = "/".concat(functionDirSegment, "/");
17415
+ var markerIdx = normalized.indexOf(marker);
17416
+ if (markerIdx >= 0) {
17417
+ var afterMarker = normalized.slice(markerIdx + marker.length);
17418
+ var parts = afterMarker.split('/');
17419
+ if (parts.length === 2) {
17420
+ var _parts_, _parts_1;
17421
+ result = {
17422
+ filename: (_parts_ = parts[1]) !== null && _parts_ !== void 0 ? _parts_ : '',
17423
+ parentFolderName: (_parts_1 = parts[0]) !== null && _parts_1 !== void 0 ? _parts_1 : ''
17424
+ };
17425
+ } else if (parts.length > 2) {
17426
+ result = {
17427
+ filename: node_path.basename(filename),
17428
+ parentFolderName: node_path.basename(node_path.dirname(filename))
17429
+ };
17430
+ }
17431
+ }
17432
+ }
17433
+ return result;
17434
+ }
17435
+ /**
17436
+ * ESLint rule that enforces the canonical naming convention for Firebase
17437
+ * Functions API spec files: every `.spec.ts` under
17438
+ * `<apiDir>/src/app/function/<group>/` must be `<group>.crud[.<sub>...].spec.ts`
17439
+ * or `<group>.scenario[.<sub>...].spec.ts`. Drift forms surface a rename
17440
+ * suggestion derived from the shared `classifySpecFile` classifier in
17441
+ * `@dereekb/util`, so this rule and the `dbx_model_test_validate_app` MCP
17442
+ * tool never diverge.
17443
+ *
17444
+ * Not auto-fixable: renaming files (and updating any imports/snapshots they
17445
+ * carry) is outside the safe scope of an ESLint autofix.
17446
+ */ var FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE = {
17447
+ meta: {
17448
+ type: 'suggestion',
17449
+ fixable: undefined,
17450
+ docs: {
17451
+ description: 'Require API spec filenames under `src/app/function/<group>/` to follow the `<group>.crud[.<sub>...].spec.ts` / `<group>.scenario[.<sub>...].spec.ts` convention.',
17452
+ recommended: true
17453
+ },
17454
+ messages: {
17455
+ testFileDriftRename: '`{{filename}}`: {{reason}} Rename to `{{recommendedRename}}`.',
17456
+ testFileMissingBucket: '`{{filename}}`: missing `crud` / `scenario` segment. Rename to `{{recommendedRename}}` (default) or to a `crud` variant if the tests are CRUD-flavored.',
17457
+ testFileNonGroupPlacement: '`{{filename}}`: first segment `{{group}}` does not match the parent folder `{{parentFolderName}}`. Move into `{{group}}/` or rename the prefix to match the current folder.'
17458
+ },
17459
+ schema: [
17460
+ {
17461
+ type: 'object',
17462
+ additionalProperties: false,
17463
+ properties: {
17464
+ functionDirSegment: {
17465
+ type: 'string'
17466
+ }
17467
+ }
17468
+ }
17469
+ ]
17470
+ },
17471
+ create: function create(context) {
17472
+ var _context_options_, _options_functionDirSegment;
17473
+ var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
17474
+ var functionDirSegment = (_options_functionDirSegment = options.functionDirSegment) !== null && _options_functionDirSegment !== void 0 ? _options_functionDirSegment : DEFAULT_FUNCTION_DIR_SEGMENT;
17475
+ var matched = matchFunctionSpecPath(context.filename, functionDirSegment);
17476
+ function check(programNode) {
17477
+ if (!matched) return;
17478
+ var classification = classifySpecFile({
17479
+ filename: matched.filename,
17480
+ parentFolderName: matched.parentFolderName
17481
+ });
17482
+ if (classification.isCanonical) return;
17483
+ if (classification.kind === 'non-spec') return;
17484
+ if (classification.kind === 'crud-misplaced' || classification.kind === 'scenario-misplaced') {
17485
+ var _classification_driftReason, _classification_recommendedRename;
17486
+ context.report({
17487
+ node: programNode,
17488
+ messageId: 'testFileDriftRename',
17489
+ data: {
17490
+ filename: classification.filename,
17491
+ reason: (_classification_driftReason = classification.driftReason) !== null && _classification_driftReason !== void 0 ? _classification_driftReason : 'segment order does not match the convention.',
17492
+ recommendedRename: (_classification_recommendedRename = classification.recommendedRename) !== null && _classification_recommendedRename !== void 0 ? _classification_recommendedRename : ''
17493
+ }
17494
+ });
17495
+ } else if (classification.kind === 'no-bucket') {
17496
+ var _classification_recommendedRename1;
17497
+ context.report({
17498
+ node: programNode,
17499
+ messageId: 'testFileMissingBucket',
17500
+ data: {
17501
+ filename: classification.filename,
17502
+ recommendedRename: (_classification_recommendedRename1 = classification.recommendedRename) !== null && _classification_recommendedRename1 !== void 0 ? _classification_recommendedRename1 : ''
17503
+ }
17504
+ });
17505
+ } else if (classification.kind === 'non-group') {
17506
+ context.report({
17507
+ node: programNode,
17508
+ messageId: 'testFileNonGroupPlacement',
17509
+ data: {
17510
+ filename: classification.filename,
17511
+ group: classification.group,
17512
+ parentFolderName: matched.parentFolderName
17513
+ }
17514
+ });
17515
+ }
17516
+ }
17517
+ return {
17518
+ Program: function Program(node) {
17519
+ return check(node);
17520
+ }
17521
+ };
17522
+ }
17523
+ };
17524
+
17525
+ function isGroupIndex(filename, functionDirSegment) {
17526
+ var normalized = filename.split(node_path.sep).join('/');
17527
+ var marker = "/".concat(functionDirSegment, "/");
17528
+ var markerIdx = normalized.indexOf(marker);
17529
+ var result = false;
17530
+ if (markerIdx >= 0) {
17531
+ var afterMarker = normalized.slice(markerIdx + marker.length);
17532
+ var parts = afterMarker.split('/');
17533
+ result = parts.length === 2 && parts[1] === 'index.ts';
17534
+ }
17535
+ return result;
17536
+ }
17537
+ function hasCrudSpec(groupDir, group) {
17538
+ var found = false;
17539
+ try {
17540
+ var entries = node_fs.readdirSync(groupDir);
17541
+ var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
17542
+ try {
17543
+ for(var _iterator = entries[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
17544
+ var entry = _step.value;
17545
+ var classification = classifySpecFile({
17546
+ filename: entry,
17547
+ parentFolderName: group
17548
+ });
17549
+ if (classification.kind === 'crud' || classification.kind === 'crud-subgroup') {
17550
+ found = true;
17551
+ break;
17552
+ }
17553
+ }
17554
+ } catch (err) {
17555
+ _didIteratorError = true;
17556
+ _iteratorError = err;
17557
+ } finally{
17558
+ try {
17559
+ if (!_iteratorNormalCompletion && _iterator.return != null) {
17560
+ _iterator.return();
17561
+ }
17562
+ } finally{
17563
+ if (_didIteratorError) {
17564
+ throw _iteratorError;
17565
+ }
17566
+ }
17567
+ }
17568
+ } catch (unused) {
17569
+ // Directory unreadable — treat as no crud spec; rule will emit the warning
17570
+ // and the user can investigate. We do NOT swallow the error silently
17571
+ // in any other way.
17572
+ }
17573
+ return found;
17574
+ }
17575
+ /**
17576
+ * ESLint rule that fires on every `<apiDir>/src/app/function/<group>/index.ts`
17577
+ * (the canonical anchor file for a Firebase Functions group) and verifies
17578
+ * that the same folder contains a `<group>.crud.spec.ts` (or any
17579
+ * `<group>.crud.<sub>.spec.ts` variant) sibling. Mirrors the coverage check
17580
+ * in `dbx_model_test_validate_app` so editor + CI lint flag missing CRUD
17581
+ * coverage without needing the MCP audit.
17582
+ *
17583
+ * Not auto-fixable: creating a spec file shell with sensible test cases is
17584
+ * outside the safe scope of an ESLint autofix.
17585
+ */ var FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE = {
17586
+ meta: {
17587
+ type: 'suggestion',
17588
+ fixable: undefined,
17589
+ docs: {
17590
+ description: 'Require every API model-group function folder to have a `<group>.crud.spec.ts` covering its CRUD function map.',
17591
+ recommended: true
17592
+ },
17593
+ messages: {
17594
+ modelGroupMissingCrudSpec: 'Model group `{{group}}` has no `{{expectedFilename}}`. Add it covering the CRUD function map (create/read/update/delete + permission/error paths).'
17595
+ },
17596
+ schema: [
17597
+ {
17598
+ type: 'object',
17599
+ additionalProperties: false,
17600
+ properties: {
17601
+ functionDirSegment: {
17602
+ type: 'string'
17603
+ }
17604
+ }
17605
+ }
17606
+ ]
17607
+ },
17608
+ create: function create(context) {
17609
+ var _context_options_, _options_functionDirSegment;
17610
+ var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
17611
+ var functionDirSegment = (_options_functionDirSegment = options.functionDirSegment) !== null && _options_functionDirSegment !== void 0 ? _options_functionDirSegment : DEFAULT_FUNCTION_DIR_SEGMENT;
17612
+ var filename = context.filename;
17613
+ var isAnchor = isGroupIndex(filename, functionDirSegment);
17614
+ function check(programNode) {
17615
+ if (!isAnchor) return;
17616
+ var groupDir = node_path.dirname(filename);
17617
+ var group = node_path.basename(groupDir);
17618
+ if (hasCrudSpec(groupDir, group)) return;
17619
+ var expectedFilename = buildCanonicalFilename({
17620
+ group: group,
17621
+ bucket: 'crud',
17622
+ subgroups: []
17623
+ });
17624
+ context.report({
17625
+ node: programNode,
17626
+ messageId: 'modelGroupMissingCrudSpec',
17627
+ data: {
17628
+ group: group,
17629
+ expectedFilename: expectedFilename
17630
+ }
17631
+ });
17632
+ }
17633
+ return {
17634
+ Program: function Program(node) {
17635
+ return check(node);
17636
+ }
17637
+ };
17638
+ }
17639
+ };
17640
+
16110
17641
  /**
16111
17642
  * ESLint plugin for `@dereekb/firebase` rules.
16112
17643
  *
@@ -16126,7 +17657,9 @@ function reportCompositeKeyTag(ctx, tag, allowedEncodings) {
16126
17657
  'require-firestore-rule-for-service-model': FIREBASE_REQUIRE_FIRESTORE_RULE_FOR_SERVICE_MODEL_RULE,
16127
17658
  'require-dbx-model-service-factory-tag': FIREBASE_REQUIRE_DBX_MODEL_SERVICE_FACTORY_TAG_RULE,
16128
17659
  'require-service-factory-for-dbx-model': FIREBASE_REQUIRE_SERVICE_FACTORY_FOR_DBX_MODEL_RULE,
16129
- 'require-dbx-model-companion-tags': FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE
17660
+ 'require-dbx-model-companion-tags': FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE,
17661
+ 'require-canonical-api-spec-filename': FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE,
17662
+ 'require-api-crud-spec-for-group': FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE
16130
17663
  }
16131
17664
  };
16132
17665
  /**
@@ -16146,6 +17679,7 @@ exports.DEFAULT_DISCOVERY_EXCLUDED_DIRS = DEFAULT_DISCOVERY_EXCLUDED_DIRS;
16146
17679
  exports.DEFAULT_FACTORY_SEARCH_ROOTS = DEFAULT_FACTORY_SEARCH_ROOTS;
16147
17680
  exports.DEFAULT_FACTORY_TAG = DEFAULT_FACTORY_TAG;
16148
17681
  exports.DEFAULT_FIRESTORE_RULES_FILENAME = DEFAULT_FIRESTORE_RULES_FILENAME;
17682
+ exports.DEFAULT_FUNCTION_DIR_SEGMENT = DEFAULT_FUNCTION_DIR_SEGMENT;
16149
17683
  exports.DEFAULT_IDENTITY_FACTORY_NAME = DEFAULT_IDENTITY_FACTORY_NAME;
16150
17684
  exports.DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES = DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES;
16151
17685
  exports.DEFAULT_MODEL_MARKER_TAG = DEFAULT_MODEL_MARKER_TAG;
@@ -16158,7 +17692,9 @@ exports.FIREBASE_ESLINT_PLUGIN = FIREBASE_ESLINT_PLUGIN;
16158
17692
  exports.FIREBASE_MODEL_SERVICE_FACTORY_MODULE = FIREBASE_MODEL_SERVICE_FACTORY_MODULE;
16159
17693
  exports.FIREBASE_MODEL_SERVICE_FACTORY_NAME = FIREBASE_MODEL_SERVICE_FACTORY_NAME;
16160
17694
  exports.FIREBASE_MODULE = FIREBASE_MODULE;
17695
+ exports.FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE = FIREBASE_REQUIRE_API_CRUD_SPEC_FOR_GROUP_RULE;
16161
17696
  exports.FIREBASE_REQUIRE_API_DETAILS_FOR_CRUD_FUNCTION_RULE = FIREBASE_REQUIRE_API_DETAILS_FOR_CRUD_FUNCTION_RULE;
17697
+ exports.FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE = FIREBASE_REQUIRE_CANONICAL_API_SPEC_FILENAME_RULE;
16162
17698
  exports.FIREBASE_REQUIRE_COMPLETE_CRUD_FUNCTION_CONFIG_MAP_RULE = FIREBASE_REQUIRE_COMPLETE_CRUD_FUNCTION_CONFIG_MAP_RULE;
16163
17699
  exports.FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE = FIREBASE_REQUIRE_DBX_MODEL_COMPANION_TAGS_RULE;
16164
17700
  exports.FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_COMPANION_TAGS_RULE = FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_COMPANION_TAGS_RULE;