@dereekb/firebase 13.11.15 → 13.11.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/eslint/index.cjs.default.js +1 -0
- package/eslint/index.cjs.js +2290 -0
- package/eslint/index.cjs.mjs +2 -0
- package/eslint/index.d.ts +1 -0
- package/eslint/index.esm.js +2277 -0
- package/eslint/package.json +24 -0
- package/eslint/src/index.d.ts +1 -0
- package/eslint/src/lib/comments.d.ts +112 -0
- package/eslint/src/lib/dbx-tag-families.d.ts +280 -0
- package/eslint/src/lib/index.d.ts +6 -0
- package/eslint/src/lib/jsdoc-parser.d.ts +116 -0
- package/eslint/src/lib/plugin.d.ts +28 -0
- package/eslint/src/lib/require-dbx-model-firebase-index-companion-tags.rule.d.ts +47 -0
- package/eslint/src/lib/require-dbx-model-firebase-index-query-suffix.rule.d.ts +44 -0
- package/eslint/src/lib/require-dbx-model-firebase-index-valid-dispatcher.rule.d.ts +54 -0
- package/eslint/src/lib/require-tagged-firestore-constraints.rule.d.ts +46 -0
- package/eslint/src/lib/util.d.ts +107 -0
- package/package.json +11 -5
- package/test/package.json +6 -6
|
@@ -0,0 +1,2277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns the outermost statement node for a FunctionDeclaration — its `ExportNamedDeclaration`
|
|
3
|
+
* or `ExportDefaultDeclaration` parent if exported, otherwise the declaration itself. This is the
|
|
4
|
+
* node ESLint attaches leading comments to.
|
|
5
|
+
*
|
|
6
|
+
* @param node - The FunctionDeclaration AST node.
|
|
7
|
+
* @returns The statement node ESLint attaches leading comments to.
|
|
8
|
+
*/ function getStatementAnchor(node) {
|
|
9
|
+
return node.parent && (node.parent.type === 'ExportNamedDeclaration' || node.parent.type === 'ExportDefaultDeclaration') ? node.parent : node;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Returns the JSDoc Block comment immediately preceding `anchor`, or `null` when
|
|
13
|
+
* the anchor has no JSDoc leader. Used by the `@dbx<Family>` companion-tag rules
|
|
14
|
+
* to locate the tagged declaration's documentation.
|
|
15
|
+
*
|
|
16
|
+
* @param sourceCode - The ESLint `SourceCode` object.
|
|
17
|
+
* @param anchor - The statement-level node ESLint attaches leading comments to.
|
|
18
|
+
* @returns The JSDoc block comment, or null when none is present.
|
|
19
|
+
*/ function leadingJsdocFor(sourceCode, anchor) {
|
|
20
|
+
var comments = sourceCode.getCommentsBefore(anchor) || [];
|
|
21
|
+
var result = null;
|
|
22
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
23
|
+
try {
|
|
24
|
+
for(var _iterator = comments[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
25
|
+
var comment = _step.value;
|
|
26
|
+
if (comment.type === 'Block' && typeof comment.value === 'string' && comment.value.startsWith('*')) {
|
|
27
|
+
result = comment;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
} catch (err) {
|
|
31
|
+
_didIteratorError = true;
|
|
32
|
+
_iteratorError = err;
|
|
33
|
+
} finally{
|
|
34
|
+
try {
|
|
35
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
36
|
+
_iterator.return();
|
|
37
|
+
}
|
|
38
|
+
} finally{
|
|
39
|
+
if (_didIteratorError) {
|
|
40
|
+
throw _iteratorError;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
return result;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function _array_like_to_array$3(arr, len) {
|
|
48
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
49
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
50
|
+
return arr2;
|
|
51
|
+
}
|
|
52
|
+
function _array_with_holes$1(arr) {
|
|
53
|
+
if (Array.isArray(arr)) return arr;
|
|
54
|
+
}
|
|
55
|
+
function _iterable_to_array_limit$1(arr, i) {
|
|
56
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
57
|
+
if (_i == null) return;
|
|
58
|
+
var _arr = [];
|
|
59
|
+
var _n = true;
|
|
60
|
+
var _d = false;
|
|
61
|
+
var _s, _e;
|
|
62
|
+
try {
|
|
63
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
64
|
+
_arr.push(_s.value);
|
|
65
|
+
if (i && _arr.length === i) break;
|
|
66
|
+
}
|
|
67
|
+
} catch (err) {
|
|
68
|
+
_d = true;
|
|
69
|
+
_e = err;
|
|
70
|
+
} finally{
|
|
71
|
+
try {
|
|
72
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
73
|
+
} finally{
|
|
74
|
+
if (_d) throw _e;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return _arr;
|
|
78
|
+
}
|
|
79
|
+
function _non_iterable_rest$1() {
|
|
80
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
81
|
+
}
|
|
82
|
+
function _sliced_to_array$1(arr, i) {
|
|
83
|
+
return _array_with_holes$1(arr) || _iterable_to_array_limit$1(arr, i) || _unsupported_iterable_to_array$3(arr, i) || _non_iterable_rest$1();
|
|
84
|
+
}
|
|
85
|
+
function _unsupported_iterable_to_array$3(o, minLen) {
|
|
86
|
+
if (!o) return;
|
|
87
|
+
if (typeof o === "string") return _array_like_to_array$3(o, minLen);
|
|
88
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
89
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
90
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
91
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$3(o, minLen);
|
|
92
|
+
}
|
|
93
|
+
var TAG_LINE_REGEX = /^@([A-Za-z_]\w*)\s*(.*)$/;
|
|
94
|
+
var TYPE_ANNOTATION_REGEX = /^\{([^}]*)\}\s*(.*)$/;
|
|
95
|
+
var PARAM_NAME_REGEX = /^([A-Za-z_$][A-Za-z0-9_$.[\]]*)\s*(.*)$/;
|
|
96
|
+
var LINE_PREFIX_REGEX = /^(\s*\*?\s?)(.*)$/;
|
|
97
|
+
/**
|
|
98
|
+
* Strips the leading whitespace + `*` + optional space prefix from a JSDoc body line and reports the length stripped.
|
|
99
|
+
*
|
|
100
|
+
* @param raw - The raw line as it appears in `comment.value`.
|
|
101
|
+
* @returns A `{ text, prefixLength }` pair.
|
|
102
|
+
*
|
|
103
|
+
* @example
|
|
104
|
+
* ```ts
|
|
105
|
+
* stripPrefix(' * @param x - desc'); // { text: '@param x - desc', prefixLength: 3 }
|
|
106
|
+
* stripPrefix(' *'); // { text: '', prefixLength: 2 }
|
|
107
|
+
* stripPrefix('* hello'); // { text: 'hello', prefixLength: 2 }
|
|
108
|
+
* ```
|
|
109
|
+
*/ function stripPrefix(raw) {
|
|
110
|
+
var match = LINE_PREFIX_REGEX.exec(raw);
|
|
111
|
+
var result = {
|
|
112
|
+
text: raw,
|
|
113
|
+
prefixLength: 0
|
|
114
|
+
};
|
|
115
|
+
if (match) {
|
|
116
|
+
result.text = match[2];
|
|
117
|
+
result.prefixLength = match[1].length;
|
|
118
|
+
}
|
|
119
|
+
return result;
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Splits the raw comment value into per-line views with prefix/offset metadata.
|
|
123
|
+
*
|
|
124
|
+
* @param commentValue - The `value` of an ESLint Block comment.
|
|
125
|
+
* @returns Array of parsed line records in source order.
|
|
126
|
+
*/ function buildParsedLines(commentValue) {
|
|
127
|
+
var rawLines = commentValue.split('\n');
|
|
128
|
+
var runningOffset = 0;
|
|
129
|
+
return rawLines.map(function(raw, index) {
|
|
130
|
+
var _stripPrefix = stripPrefix(raw), stripped = _stripPrefix.text, prefixLength = _stripPrefix.prefixLength;
|
|
131
|
+
var text = stripped.trimEnd();
|
|
132
|
+
var blank = text.length === 0;
|
|
133
|
+
var valueOffsetStart = runningOffset;
|
|
134
|
+
var textOffsetStart = runningOffset + prefixLength;
|
|
135
|
+
runningOffset += raw.length + 1; // +1 for the consumed `\n` (overshoots on last line, harmless)
|
|
136
|
+
return {
|
|
137
|
+
raw: raw,
|
|
138
|
+
text: text,
|
|
139
|
+
blank: blank,
|
|
140
|
+
index: index,
|
|
141
|
+
valueOffsetStart: valueOffsetStart,
|
|
142
|
+
textOffsetStart: textOffsetStart
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Returns the index of the first line that begins with a JSDoc `@tag`, or `-1` when none exists.
|
|
148
|
+
*
|
|
149
|
+
* @param lines - Parsed lines in source order.
|
|
150
|
+
* @returns Zero-based line index of the first tag, or `-1` when no tag is present.
|
|
151
|
+
*/ function findFirstTagIndex(lines) {
|
|
152
|
+
var firstTagIndex = -1;
|
|
153
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
154
|
+
try {
|
|
155
|
+
for(var _iterator = lines.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
156
|
+
var _step_value = _sliced_to_array$1(_step.value, 2), i = _step_value[0], line = _step_value[1];
|
|
157
|
+
if (TAG_LINE_REGEX.test(line.text)) {
|
|
158
|
+
firstTagIndex = i;
|
|
159
|
+
break;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
} catch (err) {
|
|
163
|
+
_didIteratorError = true;
|
|
164
|
+
_iteratorError = err;
|
|
165
|
+
} finally{
|
|
166
|
+
try {
|
|
167
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
168
|
+
_iterator.return();
|
|
169
|
+
}
|
|
170
|
+
} finally{
|
|
171
|
+
if (_didIteratorError) {
|
|
172
|
+
throw _iteratorError;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
return firstTagIndex;
|
|
177
|
+
}
|
|
178
|
+
/**
|
|
179
|
+
* Trims leading and trailing blank lines from a contiguous run of description lines.
|
|
180
|
+
*
|
|
181
|
+
* @param descriptionLines - Description-section lines before any tag.
|
|
182
|
+
* @returns Sub-array with surrounding blank lines stripped.
|
|
183
|
+
*/ function trimBlankBoundaries(descriptionLines) {
|
|
184
|
+
var descStart = 0;
|
|
185
|
+
var descEnd = descriptionLines.length;
|
|
186
|
+
while(descStart < descEnd && descriptionLines[descStart].blank)descStart += 1;
|
|
187
|
+
while(descEnd > descStart && descriptionLines[descEnd - 1].blank)descEnd -= 1;
|
|
188
|
+
return descriptionLines.slice(descStart, descEnd);
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* Splits the trimmed description lines into paragraphs separated by blank-line runs.
|
|
192
|
+
*
|
|
193
|
+
* @param trimmedDescription - Description lines with surrounding blank lines removed.
|
|
194
|
+
* @returns Paragraph strings joined by `\n`.
|
|
195
|
+
*/ function buildDescriptionParagraphs(trimmedDescription) {
|
|
196
|
+
var descriptionParagraphs = [];
|
|
197
|
+
var paragraphBuffer = [];
|
|
198
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
199
|
+
try {
|
|
200
|
+
for(var _iterator = trimmedDescription[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
201
|
+
var line = _step.value;
|
|
202
|
+
if (line.blank) {
|
|
203
|
+
if (paragraphBuffer.length > 0) {
|
|
204
|
+
descriptionParagraphs.push(paragraphBuffer.join('\n'));
|
|
205
|
+
paragraphBuffer = [];
|
|
206
|
+
}
|
|
207
|
+
} else {
|
|
208
|
+
paragraphBuffer.push(line.text);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (err) {
|
|
212
|
+
_didIteratorError = true;
|
|
213
|
+
_iteratorError = err;
|
|
214
|
+
} finally{
|
|
215
|
+
try {
|
|
216
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
217
|
+
_iterator.return();
|
|
218
|
+
}
|
|
219
|
+
} finally{
|
|
220
|
+
if (_didIteratorError) {
|
|
221
|
+
throw _iteratorError;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (paragraphBuffer.length > 0) {
|
|
226
|
+
descriptionParagraphs.push(paragraphBuffer.join('\n'));
|
|
227
|
+
}
|
|
228
|
+
return descriptionParagraphs;
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Pulls an optional `{Type}` annotation off the front of a tag remainder.
|
|
232
|
+
*
|
|
233
|
+
* @param remainder - The tag-line text after the `@tagName` prefix.
|
|
234
|
+
* @returns The annotation (or `undefined`) plus the remaining text.
|
|
235
|
+
*/ function extractTypeAnnotation(remainder) {
|
|
236
|
+
var type;
|
|
237
|
+
var rest = remainder;
|
|
238
|
+
var typeMatch = TYPE_ANNOTATION_REGEX.exec(remainder);
|
|
239
|
+
if (typeMatch) {
|
|
240
|
+
type = typeMatch[1];
|
|
241
|
+
rest = typeMatch[2];
|
|
242
|
+
}
|
|
243
|
+
return {
|
|
244
|
+
type: type,
|
|
245
|
+
rest: rest
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Pulls an optional parameter name off the front of a `@param` tag remainder.
|
|
250
|
+
*
|
|
251
|
+
* @param tagName - Tag name (only `'param'` extracts a name; other tags pass through).
|
|
252
|
+
* @param remainder - The tag-line text after the optional `{Type}` annotation.
|
|
253
|
+
* @returns The parameter name (or `undefined`) plus the remaining text.
|
|
254
|
+
*/ function extractParamName(tagName, remainder) {
|
|
255
|
+
var name;
|
|
256
|
+
var rest = remainder;
|
|
257
|
+
if (tagName === 'param') {
|
|
258
|
+
var nameMatch = PARAM_NAME_REGEX.exec(remainder);
|
|
259
|
+
if (nameMatch) {
|
|
260
|
+
name = nameMatch[1];
|
|
261
|
+
rest = nameMatch[2];
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
return {
|
|
265
|
+
name: name,
|
|
266
|
+
rest: rest
|
|
267
|
+
};
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* Collects the tag line at `startIndex` plus every following non-tag continuation line.
|
|
271
|
+
*
|
|
272
|
+
* @param lines - All parsed lines in the comment.
|
|
273
|
+
* @param startIndex - Index of the `@tag` opening line.
|
|
274
|
+
* @returns The collected tag lines and the index of the next unconsumed line.
|
|
275
|
+
*/ function collectTagLines(lines, startIndex) {
|
|
276
|
+
var tagLines = [
|
|
277
|
+
lines[startIndex]
|
|
278
|
+
];
|
|
279
|
+
var j = startIndex + 1;
|
|
280
|
+
while(j < lines.length){
|
|
281
|
+
if (TAG_LINE_REGEX.test(lines[j].text)) break;
|
|
282
|
+
tagLines.push(lines[j]);
|
|
283
|
+
j += 1;
|
|
284
|
+
}
|
|
285
|
+
return {
|
|
286
|
+
tagLines: tagLines,
|
|
287
|
+
nextIndex: j
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Joins the on-line remainder and continuation-line text into a tag description, dropping trailing
|
|
292
|
+
* blank lines while preserving interior blanks.
|
|
293
|
+
*
|
|
294
|
+
* @param remainder - The on-line remainder after stripping `@tagName {Type} name`.
|
|
295
|
+
* @param tagLines - All lines that belong to the tag (including the header line at index 0).
|
|
296
|
+
* @returns Description text joined by `\n`.
|
|
297
|
+
*/ function buildTagDescription(remainder, tagLines) {
|
|
298
|
+
var descriptionParts = [];
|
|
299
|
+
if (remainder.length > 0) descriptionParts.push(remainder);
|
|
300
|
+
for(var k = 1; k < tagLines.length; k += 1){
|
|
301
|
+
descriptionParts.push(tagLines[k].text);
|
|
302
|
+
}
|
|
303
|
+
while(descriptionParts.length > 0 && descriptionParts.at(-1).trim().length === 0){
|
|
304
|
+
descriptionParts.pop();
|
|
305
|
+
}
|
|
306
|
+
return descriptionParts.join('\n');
|
|
307
|
+
}
|
|
308
|
+
/**
|
|
309
|
+
* Builds a single parsed-tag record starting at `startIndex` in the line array.
|
|
310
|
+
*
|
|
311
|
+
* @param lines - All parsed lines in the comment.
|
|
312
|
+
* @param startIndex - Index of the `@tag` opening line.
|
|
313
|
+
* @returns The parsed tag and the next unconsumed line index.
|
|
314
|
+
*/ function parseTagAt(lines, startIndex) {
|
|
315
|
+
var line = lines[startIndex];
|
|
316
|
+
var match = TAG_LINE_REGEX.exec(line.text);
|
|
317
|
+
var tagName = match[1];
|
|
318
|
+
var _extractTypeAnnotation = extractTypeAnnotation(match[2]), type = _extractTypeAnnotation.type, afterType = _extractTypeAnnotation.rest;
|
|
319
|
+
var _extractParamName = extractParamName(tagName, afterType), name = _extractParamName.name, afterName = _extractParamName.rest;
|
|
320
|
+
var _collectTagLines = collectTagLines(lines, startIndex), tagLines = _collectTagLines.tagLines, nextIndex = _collectTagLines.nextIndex;
|
|
321
|
+
var description = buildTagDescription(afterName, tagLines);
|
|
322
|
+
return {
|
|
323
|
+
tag: {
|
|
324
|
+
tag: tagName,
|
|
325
|
+
name: name,
|
|
326
|
+
type: type,
|
|
327
|
+
description: description,
|
|
328
|
+
lines: tagLines,
|
|
329
|
+
startLineIndex: startIndex,
|
|
330
|
+
endLineIndex: nextIndex - 1
|
|
331
|
+
},
|
|
332
|
+
nextIndex: nextIndex
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Parses every `@tag` block starting from `firstTagIndex` to the end of the line array.
|
|
337
|
+
*
|
|
338
|
+
* @param lines - All parsed lines in the comment.
|
|
339
|
+
* @param firstTagIndex - Index where tag parsing should begin (`-1` skips entirely).
|
|
340
|
+
* @returns All parsed tags in source order.
|
|
341
|
+
*/ function parseTags(lines, firstTagIndex) {
|
|
342
|
+
var tags = [];
|
|
343
|
+
if (firstTagIndex !== -1) {
|
|
344
|
+
var i = firstTagIndex;
|
|
345
|
+
while(i < lines.length){
|
|
346
|
+
if (!TAG_LINE_REGEX.test(lines[i].text)) {
|
|
347
|
+
i += 1;
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
var _parseTagAt = parseTagAt(lines, i), tag = _parseTagAt.tag, nextIndex = _parseTagAt.nextIndex;
|
|
351
|
+
tags.push(tag);
|
|
352
|
+
i = nextIndex;
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
return tags;
|
|
356
|
+
}
|
|
357
|
+
/**
|
|
358
|
+
* Parses the value of an ESLint Block comment that represents a JSDoc into a structured form.
|
|
359
|
+
*
|
|
360
|
+
* @param commentValue - The `value` of an ESLint Block comment (text between `/*` and `*\/`, including the leading `*`).
|
|
361
|
+
* @returns A structured view of the JSDoc with description, paragraphs, and tags.
|
|
362
|
+
*
|
|
363
|
+
* @example
|
|
364
|
+
* ```ts
|
|
365
|
+
* const parsed = parseJsdocComment('*\n * Hello.\n *\n * @param x - The value.\n ');
|
|
366
|
+
* // parsed.description === 'Hello.'
|
|
367
|
+
* // parsed.tags[0].tag === 'param'
|
|
368
|
+
* // parsed.tags[0].name === 'x'
|
|
369
|
+
* // parsed.tags[0].description === 'The value.'
|
|
370
|
+
* ```
|
|
371
|
+
*/ function parseJsdocComment(commentValue) {
|
|
372
|
+
var singleLine = !commentValue.includes('\n');
|
|
373
|
+
var lines = buildParsedLines(commentValue);
|
|
374
|
+
var firstTagIndex = findFirstTagIndex(lines);
|
|
375
|
+
var descriptionLines = firstTagIndex === -1 ? lines.slice() : lines.slice(0, firstTagIndex);
|
|
376
|
+
var trimmedDescription = trimBlankBoundaries(descriptionLines);
|
|
377
|
+
var description = trimmedDescription.map(function(l) {
|
|
378
|
+
return l.text;
|
|
379
|
+
}).join('\n');
|
|
380
|
+
var descriptionParagraphs = buildDescriptionParagraphs(trimmedDescription);
|
|
381
|
+
var tags = parseTags(lines, firstTagIndex);
|
|
382
|
+
return {
|
|
383
|
+
lines: lines,
|
|
384
|
+
descriptionLines: descriptionLines,
|
|
385
|
+
description: description,
|
|
386
|
+
descriptionParagraphs: descriptionParagraphs,
|
|
387
|
+
tags: tags,
|
|
388
|
+
singleLine: singleLine
|
|
389
|
+
};
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
function _array_like_to_array$2(arr, len) {
|
|
393
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
394
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
395
|
+
return arr2;
|
|
396
|
+
}
|
|
397
|
+
function _array_without_holes$1(arr) {
|
|
398
|
+
if (Array.isArray(arr)) return _array_like_to_array$2(arr);
|
|
399
|
+
}
|
|
400
|
+
function _iterable_to_array$1(iter) {
|
|
401
|
+
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
402
|
+
}
|
|
403
|
+
function _non_iterable_spread$1() {
|
|
404
|
+
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
405
|
+
}
|
|
406
|
+
function _to_consumable_array$1(arr) {
|
|
407
|
+
return _array_without_holes$1(arr) || _iterable_to_array$1(arr) || _unsupported_iterable_to_array$2(arr) || _non_iterable_spread$1();
|
|
408
|
+
}
|
|
409
|
+
function _unsupported_iterable_to_array$2(o, minLen) {
|
|
410
|
+
if (!o) return;
|
|
411
|
+
if (typeof o === "string") return _array_like_to_array$2(o, minLen);
|
|
412
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
413
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
414
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
415
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$2(o, minLen);
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Module that publishes the `@dereekb/firebase` Firestore constraint factories (`where`, `orderBy`, etc.).
|
|
419
|
+
*/ var FIREBASE_MODULE = '@dereekb/firebase';
|
|
420
|
+
/**
|
|
421
|
+
* JSDoc tag name that marks an exported query factory whose body should be scanned by
|
|
422
|
+
* `dbx-components-mcp`'s index extractor (`packages/dbx-components-mcp/src/scan/model-firebase-index-extract.ts`).
|
|
423
|
+
*/ var DBX_MODEL_FIREBASE_INDEX_MARKER = 'dbxModelFirebaseIndex';
|
|
424
|
+
/**
|
|
425
|
+
* The canonical suffix expected on every `@dbxModelFirebaseIndex`-tagged factory name.
|
|
426
|
+
*/ var QUERY_SUFFIX = 'Query';
|
|
427
|
+
/**
|
|
428
|
+
* Index-affecting Firestore constraint factory identifiers exported from `@dereekb/firebase`
|
|
429
|
+
* (see `packages/firebase/src/lib/common/firestore/query/constraint.ts`). These shape the
|
|
430
|
+
* composite index Firestore needs to satisfy the query — calls to them must originate inside
|
|
431
|
+
* a `@dbxModelFirebaseIndex`-tagged function so the dbx-components-mcp index extractor can
|
|
432
|
+
* collect them.
|
|
433
|
+
*
|
|
434
|
+
* `where` and `orderBy` are the only constraint factories that influence composite indexes;
|
|
435
|
+
* pagination/cursor factories (`limit`, `limitToLast`, `whereDocumentId`, `startAt`/`After`,
|
|
436
|
+
* `endAt`/`Before`) only narrow the cursor/window of an already-indexed query and may be
|
|
437
|
+
* composed freely outside tagged factories — see {@link DEFAULT_PAGINATION_CONSTRAINT_NAMES}.
|
|
438
|
+
*/ var DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES = [
|
|
439
|
+
'where',
|
|
440
|
+
'orderBy'
|
|
441
|
+
];
|
|
442
|
+
/**
|
|
443
|
+
* Pagination/cursor Firestore constraint factory identifiers exported from `@dereekb/firebase`.
|
|
444
|
+
* These do not influence composite indexes and may be composed externally — e.g. a generic
|
|
445
|
+
* pagination helper that appends `limit` + `startAfter` onto a caller-supplied tagged-query
|
|
446
|
+
* constraint array. The "tagged-firestore-constraints" rule does not flag calls to these by
|
|
447
|
+
* default.
|
|
448
|
+
*/ var DEFAULT_PAGINATION_CONSTRAINT_NAMES = [
|
|
449
|
+
'limit',
|
|
450
|
+
'limitToLast',
|
|
451
|
+
'whereDocumentId',
|
|
452
|
+
'startAt',
|
|
453
|
+
'startAfter',
|
|
454
|
+
'endAt',
|
|
455
|
+
'endBefore'
|
|
456
|
+
];
|
|
457
|
+
/**
|
|
458
|
+
* Combined list of every Firestore constraint factory exported from `@dereekb/firebase`. Used
|
|
459
|
+
* by the body-coherence rule to ensure a tagged factory body contains at least one constraint
|
|
460
|
+
* call of any kind (index-affecting or pagination) before warning that the marker is orphaned.
|
|
461
|
+
*/ var DEFAULT_CONSTRAINT_FACTORY_NAMES = _to_consumable_array$1(DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES).concat(_to_consumable_array$1(DEFAULT_PAGINATION_CONSTRAINT_NAMES));
|
|
462
|
+
/**
|
|
463
|
+
* Creates an empty {@link ImportRegistry}.
|
|
464
|
+
*
|
|
465
|
+
* @returns A fresh empty registry.
|
|
466
|
+
*/ function createImportRegistry() {
|
|
467
|
+
return {
|
|
468
|
+
bySource: new Map(),
|
|
469
|
+
localToSource: new Map(),
|
|
470
|
+
localToImported: new Map()
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
/**
|
|
474
|
+
* Records an `ImportDeclaration` node in the registry.
|
|
475
|
+
*
|
|
476
|
+
* @param registry - The registry to mutate.
|
|
477
|
+
* @param node - The ImportDeclaration AST node.
|
|
478
|
+
*/ function trackImportDeclaration(registry, node) {
|
|
479
|
+
var _node_source;
|
|
480
|
+
var source = (_node_source = node.source) === null || _node_source === void 0 ? void 0 : _node_source.value;
|
|
481
|
+
if (source) {
|
|
482
|
+
var _registry_bySource_get, _node_specifiers;
|
|
483
|
+
var localNames = (_registry_bySource_get = registry.bySource.get(source)) !== null && _registry_bySource_get !== void 0 ? _registry_bySource_get : new Set();
|
|
484
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
485
|
+
try {
|
|
486
|
+
for(var _iterator = ((_node_specifiers = node.specifiers) !== null && _node_specifiers !== void 0 ? _node_specifiers : [])[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
487
|
+
var specifier = _step.value;
|
|
488
|
+
if (specifier.type === 'ImportSpecifier' || specifier.type === 'ImportDefaultSpecifier' || specifier.type === 'ImportNamespaceSpecifier') {
|
|
489
|
+
var _specifier_local;
|
|
490
|
+
var localName = (_specifier_local = specifier.local) === null || _specifier_local === void 0 ? void 0 : _specifier_local.name;
|
|
491
|
+
if (localName) {
|
|
492
|
+
var _ref;
|
|
493
|
+
var _specifier_imported;
|
|
494
|
+
localNames.add(localName);
|
|
495
|
+
registry.localToSource.set(localName, source);
|
|
496
|
+
var importedName = specifier.type === 'ImportSpecifier' ? (_ref = (_specifier_imported = specifier.imported) === null || _specifier_imported === void 0 ? void 0 : _specifier_imported.name) !== null && _ref !== void 0 ? _ref : localName : localName;
|
|
497
|
+
registry.localToImported.set(localName, importedName);
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
} catch (err) {
|
|
502
|
+
_didIteratorError = true;
|
|
503
|
+
_iteratorError = err;
|
|
504
|
+
} finally{
|
|
505
|
+
try {
|
|
506
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
507
|
+
_iterator.return();
|
|
508
|
+
}
|
|
509
|
+
} finally{
|
|
510
|
+
if (_didIteratorError) {
|
|
511
|
+
throw _iteratorError;
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
registry.bySource.set(source, localNames);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
518
|
+
/**
|
|
519
|
+
* Returns true when the given local identifier name was imported from the given module.
|
|
520
|
+
*
|
|
521
|
+
* @param registry - The import registry built from the file's import declarations.
|
|
522
|
+
* @param localName - The local identifier (as it appears in code).
|
|
523
|
+
* @param fromSource - The expected source-module string.
|
|
524
|
+
* @returns True when the local name maps to the given source.
|
|
525
|
+
*/ function isImportedFrom(registry, localName, fromSource) {
|
|
526
|
+
return registry.localToSource.get(localName) === fromSource;
|
|
527
|
+
}
|
|
528
|
+
/**
|
|
529
|
+
* Returns the statement-level anchor node that ESLint attaches leading comments to for the
|
|
530
|
+
* given function-like node. For function declarations this is the declaration (or its
|
|
531
|
+
* `Export*Declaration` wrapper); for arrow/function expressions assigned to a variable, it
|
|
532
|
+
* is the variable declaration (or its `Export*Declaration` wrapper); otherwise `null`.
|
|
533
|
+
*
|
|
534
|
+
* @param node - The function-like AST node (FunctionDeclaration / FunctionExpression / ArrowFunctionExpression).
|
|
535
|
+
* @returns The anchor statement node, or null when the function has no JSDoc-anchorable container.
|
|
536
|
+
*/ function getFunctionJsdocAnchor(node) {
|
|
537
|
+
var result = null;
|
|
538
|
+
if (node.type === 'FunctionDeclaration') {
|
|
539
|
+
result = node.parent && (node.parent.type === 'ExportNamedDeclaration' || node.parent.type === 'ExportDefaultDeclaration') ? node.parent : node;
|
|
540
|
+
} else if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
|
|
541
|
+
var declarator = node.parent;
|
|
542
|
+
if ((declarator === null || declarator === void 0 ? void 0 : declarator.type) === 'VariableDeclarator') {
|
|
543
|
+
var declaration = declarator.parent;
|
|
544
|
+
if ((declaration === null || declaration === void 0 ? void 0 : declaration.type) === 'VariableDeclaration') {
|
|
545
|
+
var _declaration_parent, _declaration_parent1;
|
|
546
|
+
result = ((_declaration_parent = declaration.parent) === null || _declaration_parent === void 0 ? void 0 : _declaration_parent.type) === 'ExportNamedDeclaration' || ((_declaration_parent1 = declaration.parent) === null || _declaration_parent1 === void 0 ? void 0 : _declaration_parent1.type) === 'ExportDefaultDeclaration' ? declaration.parent : declaration;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
}
|
|
550
|
+
return result;
|
|
551
|
+
}
|
|
552
|
+
/**
|
|
553
|
+
* Returns the function name when resolvable: `node.id.name` for declarations, or the
|
|
554
|
+
* containing `VariableDeclarator.id.name` for arrow/function expressions. Returns `null`
|
|
555
|
+
* when the function is anonymous in an unrecognized context.
|
|
556
|
+
*
|
|
557
|
+
* @param node - The function-like AST node.
|
|
558
|
+
* @returns The function name, or null when anonymous.
|
|
559
|
+
*/ function getFunctionName(node) {
|
|
560
|
+
var _node_id;
|
|
561
|
+
var result = null;
|
|
562
|
+
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && ((_node_id = node.id) === null || _node_id === void 0 ? void 0 : _node_id.type) === 'Identifier') {
|
|
563
|
+
result = node.id.name;
|
|
564
|
+
} else if (node.type === 'FunctionExpression' || node.type === 'ArrowFunctionExpression') {
|
|
565
|
+
var _declarator_id;
|
|
566
|
+
var declarator = node.parent;
|
|
567
|
+
if ((declarator === null || declarator === void 0 ? void 0 : declarator.type) === 'VariableDeclarator' && ((_declarator_id = declarator.id) === null || _declarator_id === void 0 ? void 0 : _declarator_id.type) === 'Identifier') {
|
|
568
|
+
result = declarator.id.name;
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
return result;
|
|
572
|
+
}
|
|
573
|
+
/**
|
|
574
|
+
* Returns the name-bearing AST node for reporting on a function: the `id` for a
|
|
575
|
+
* declaration, the `VariableDeclarator.id` for an arrow assignment, or the function node
|
|
576
|
+
* itself as a last resort.
|
|
577
|
+
*
|
|
578
|
+
* @param node - The function-like AST node.
|
|
579
|
+
* @returns The node to attach a `context.report` location to.
|
|
580
|
+
*/ function getFunctionNameNode(node) {
|
|
581
|
+
var result = node;
|
|
582
|
+
if ((node.type === 'FunctionDeclaration' || node.type === 'FunctionExpression') && node.id) {
|
|
583
|
+
result = node.id;
|
|
584
|
+
} else if (node.type === 'ArrowFunctionExpression' || node.type === 'FunctionExpression') {
|
|
585
|
+
var declarator = node.parent;
|
|
586
|
+
if ((declarator === null || declarator === void 0 ? void 0 : declarator.type) === 'VariableDeclarator' && declarator.id) {
|
|
587
|
+
result = declarator.id;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
return result;
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
function _array_like_to_array$1(arr, len) {
|
|
594
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
595
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
596
|
+
return arr2;
|
|
597
|
+
}
|
|
598
|
+
function _array_without_holes(arr) {
|
|
599
|
+
if (Array.isArray(arr)) return _array_like_to_array$1(arr);
|
|
600
|
+
}
|
|
601
|
+
function _iterable_to_array(iter) {
|
|
602
|
+
if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter);
|
|
603
|
+
}
|
|
604
|
+
function _non_iterable_spread() {
|
|
605
|
+
throw new TypeError("Invalid attempt to spread non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
606
|
+
}
|
|
607
|
+
function _to_consumable_array(arr) {
|
|
608
|
+
return _array_without_holes(arr) || _iterable_to_array(arr) || _unsupported_iterable_to_array$1(arr) || _non_iterable_spread();
|
|
609
|
+
}
|
|
610
|
+
function _unsupported_iterable_to_array$1(o, minLen) {
|
|
611
|
+
if (!o) return;
|
|
612
|
+
if (typeof o === "string") return _array_like_to_array$1(o, minLen);
|
|
613
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
614
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
615
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
616
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array$1(o, minLen);
|
|
617
|
+
}
|
|
618
|
+
/**
|
|
619
|
+
* ESLint rule that forbids inline `@dereekb/firebase` Firestore constraint factory calls
|
|
620
|
+
* (`where`, `orderBy`, `limit`, ...) outside a function whose leading JSDoc carries the
|
|
621
|
+
* `@dbxModelFirebaseIndex` marker. The dbx-components-mcp index extractor only sees
|
|
622
|
+
* constraints inside tagged factories — anything else is invisible to index generation
|
|
623
|
+
* and must be extracted into a dedicated `*Query` factory.
|
|
624
|
+
*
|
|
625
|
+
* Not auto-fixable: extracting an inline constraint into a new function requires choosing
|
|
626
|
+
* a name, signature, and call-site replacement that are outside the safe scope of an ESLint
|
|
627
|
+
* autofix.
|
|
628
|
+
*/ var FIREBASE_REQUIRE_TAGGED_FIRESTORE_CONSTRAINTS_RULE = {
|
|
629
|
+
meta: {
|
|
630
|
+
type: 'problem',
|
|
631
|
+
fixable: undefined,
|
|
632
|
+
docs: {
|
|
633
|
+
description: 'Disallow `@dereekb/firebase` index-affecting Firestore constraint factory calls (`where`, `orderBy`) outside a `@dbxModelFirebaseIndex`-tagged query factory. Pagination/cursor factories (`limit`, `limitToLast`, `whereDocumentId`, `startAt`/`After`, `endAt`/`Before`) do not affect composite indexes and are not flagged.',
|
|
634
|
+
recommended: true
|
|
635
|
+
},
|
|
636
|
+
messages: {
|
|
637
|
+
inlineConstraintOutsideTaggedQuery: '`{{name}}(...)` from `@dereekb/firebase` must be called inside a `@dbxModelFirebaseIndex`-tagged query factory. Extract this constraint into a dedicated `*Query` function tagged with `@dbxModelFirebaseIndex` so dbx-components-mcp can index it, then run `dbx-cli-generate-firestore-indexes --component <dir>` to regenerate `firestore.indexes.json`.'
|
|
638
|
+
},
|
|
639
|
+
schema: [
|
|
640
|
+
{
|
|
641
|
+
type: 'object',
|
|
642
|
+
additionalProperties: false,
|
|
643
|
+
properties: {
|
|
644
|
+
constraintNames: {
|
|
645
|
+
type: 'array',
|
|
646
|
+
items: {
|
|
647
|
+
type: 'string'
|
|
648
|
+
}
|
|
649
|
+
},
|
|
650
|
+
additionalConstraintNames: {
|
|
651
|
+
type: 'array',
|
|
652
|
+
items: {
|
|
653
|
+
type: 'string'
|
|
654
|
+
}
|
|
655
|
+
},
|
|
656
|
+
allowedImportSources: {
|
|
657
|
+
type: 'array',
|
|
658
|
+
items: {
|
|
659
|
+
type: 'string'
|
|
660
|
+
}
|
|
661
|
+
},
|
|
662
|
+
markerTag: {
|
|
663
|
+
type: 'string'
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
]
|
|
668
|
+
},
|
|
669
|
+
create: function create(context) {
|
|
670
|
+
var _context_options_, _options_constraintNames, _options_additionalConstraintNames, _options_allowedImportSources, _options_markerTag;
|
|
671
|
+
var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
|
|
672
|
+
var sourceCode = context.sourceCode;
|
|
673
|
+
var baseNames = (_options_constraintNames = options.constraintNames) !== null && _options_constraintNames !== void 0 ? _options_constraintNames : DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES;
|
|
674
|
+
var constraintNames = new Set(_to_consumable_array(baseNames).concat(_to_consumable_array((_options_additionalConstraintNames = options.additionalConstraintNames) !== null && _options_additionalConstraintNames !== void 0 ? _options_additionalConstraintNames : [])));
|
|
675
|
+
var allowedSources = new Set((_options_allowedImportSources = options.allowedImportSources) !== null && _options_allowedImportSources !== void 0 ? _options_allowedImportSources : [
|
|
676
|
+
FIREBASE_MODULE
|
|
677
|
+
]);
|
|
678
|
+
var markerTag = (_options_markerTag = options.markerTag) !== null && _options_markerTag !== void 0 ? _options_markerTag : DBX_MODEL_FIREBASE_INDEX_MARKER;
|
|
679
|
+
var registry = createImportRegistry();
|
|
680
|
+
var stack = [];
|
|
681
|
+
function jsdocHasMarker(anchor) {
|
|
682
|
+
var jsdoc = leadingJsdocFor(sourceCode, anchor);
|
|
683
|
+
var result = false;
|
|
684
|
+
if (jsdoc) {
|
|
685
|
+
var parsed = parseJsdocComment(jsdoc.value);
|
|
686
|
+
result = parsed.tags.some(function(t) {
|
|
687
|
+
return t.tag === markerTag;
|
|
688
|
+
});
|
|
689
|
+
}
|
|
690
|
+
return result;
|
|
691
|
+
}
|
|
692
|
+
function pushFrame(node) {
|
|
693
|
+
var _ref;
|
|
694
|
+
var anchor = getFunctionJsdocAnchor(node);
|
|
695
|
+
var tagged = anchor ? jsdocHasMarker(anchor) : false;
|
|
696
|
+
var parent = stack.length > 0 ? stack[stack.length - 1] : null;
|
|
697
|
+
var taggedDeep = tagged || ((_ref = parent === null || parent === void 0 ? void 0 : parent.taggedDeep) !== null && _ref !== void 0 ? _ref : false);
|
|
698
|
+
stack.push({
|
|
699
|
+
node: node,
|
|
700
|
+
tagged: tagged,
|
|
701
|
+
taggedDeep: taggedDeep
|
|
702
|
+
});
|
|
703
|
+
}
|
|
704
|
+
function popFrame(node) {
|
|
705
|
+
if (stack.length > 0 && stack[stack.length - 1].node === node) {
|
|
706
|
+
stack.pop();
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
function isTrackedConstraintCall(node) {
|
|
710
|
+
var _node_callee;
|
|
711
|
+
var result = null;
|
|
712
|
+
if (((_node_callee = node.callee) === null || _node_callee === void 0 ? void 0 : _node_callee.type) === 'Identifier') {
|
|
713
|
+
var localName = node.callee.name;
|
|
714
|
+
var source = registry.localToSource.get(localName);
|
|
715
|
+
if (source && allowedSources.has(source) && isImportedFrom(registry, localName, source)) {
|
|
716
|
+
var _registry_localToImported_get;
|
|
717
|
+
var importedName = (_registry_localToImported_get = registry.localToImported.get(localName)) !== null && _registry_localToImported_get !== void 0 ? _registry_localToImported_get : localName;
|
|
718
|
+
if (constraintNames.has(importedName)) {
|
|
719
|
+
result = importedName;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
return result;
|
|
724
|
+
}
|
|
725
|
+
return {
|
|
726
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
727
|
+
return trackImportDeclaration(registry, node);
|
|
728
|
+
},
|
|
729
|
+
FunctionDeclaration: function FunctionDeclaration(node) {
|
|
730
|
+
return pushFrame(node);
|
|
731
|
+
},
|
|
732
|
+
'FunctionDeclaration:exit': function(node) {
|
|
733
|
+
return popFrame(node);
|
|
734
|
+
},
|
|
735
|
+
FunctionExpression: function FunctionExpression(node) {
|
|
736
|
+
return pushFrame(node);
|
|
737
|
+
},
|
|
738
|
+
'FunctionExpression:exit': function(node) {
|
|
739
|
+
return popFrame(node);
|
|
740
|
+
},
|
|
741
|
+
ArrowFunctionExpression: function ArrowFunctionExpression(node) {
|
|
742
|
+
return pushFrame(node);
|
|
743
|
+
},
|
|
744
|
+
'ArrowFunctionExpression:exit': function(node) {
|
|
745
|
+
return popFrame(node);
|
|
746
|
+
},
|
|
747
|
+
CallExpression: function CallExpression(node) {
|
|
748
|
+
var name = isTrackedConstraintCall(node);
|
|
749
|
+
if (name) {
|
|
750
|
+
var taggedAncestor = stack.some(function(frame) {
|
|
751
|
+
return frame.tagged;
|
|
752
|
+
});
|
|
753
|
+
if (!taggedAncestor) {
|
|
754
|
+
context.report({
|
|
755
|
+
node: node,
|
|
756
|
+
messageId: 'inlineConstraintOutsideTaggedQuery',
|
|
757
|
+
data: {
|
|
758
|
+
name: name
|
|
759
|
+
}
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
};
|
|
767
|
+
|
|
768
|
+
var DEFAULT_STRIPPABLE_SUFFIXES = [
|
|
769
|
+
'Filter',
|
|
770
|
+
'Constraints',
|
|
771
|
+
'Builder'
|
|
772
|
+
];
|
|
773
|
+
/**
|
|
774
|
+
* Builds a rename suggestion: strips a recognized non-canonical suffix and appends the canonical
|
|
775
|
+
* `Query` suffix; otherwise appends `Query` directly.
|
|
776
|
+
*
|
|
777
|
+
* @param name - The current function name.
|
|
778
|
+
* @param suffix - The canonical suffix (default `'Query'`).
|
|
779
|
+
* @param strippable - Suffixes to strip before appending (default `['Filter', 'Constraints', 'Builder']`).
|
|
780
|
+
* @returns The suggested renamed identifier.
|
|
781
|
+
*/ function buildRenameSuggestion(name, suffix, strippable) {
|
|
782
|
+
var result = "".concat(name).concat(suffix);
|
|
783
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
784
|
+
try {
|
|
785
|
+
for(var _iterator = strippable[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
786
|
+
var s = _step.value;
|
|
787
|
+
if (name.endsWith(s) && name.length > s.length) {
|
|
788
|
+
result = "".concat(name.slice(0, -s.length)).concat(suffix);
|
|
789
|
+
break;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
} catch (err) {
|
|
793
|
+
_didIteratorError = true;
|
|
794
|
+
_iteratorError = err;
|
|
795
|
+
} finally{
|
|
796
|
+
try {
|
|
797
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
798
|
+
_iterator.return();
|
|
799
|
+
}
|
|
800
|
+
} finally{
|
|
801
|
+
if (_didIteratorError) {
|
|
802
|
+
throw _iteratorError;
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
return result;
|
|
807
|
+
}
|
|
808
|
+
/**
|
|
809
|
+
* ESLint rule that enforces the canonical `Query` suffix on every `@dbxModelFirebaseIndex`-tagged
|
|
810
|
+
* function. Mirrors the naming used by the canonical fixtures in
|
|
811
|
+
* `packages/dbx-components-mcp/src/scan/model-firebase-index-extract.spec.ts` and production
|
|
812
|
+
* factories in `packages/firebase/src/lib/model/**\/*.query.ts`.
|
|
813
|
+
*
|
|
814
|
+
* Not auto-fixable: renaming across files (call sites + tests) is outside the safe scope of an
|
|
815
|
+
* ESLint autofix.
|
|
816
|
+
*/ var FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_QUERY_SUFFIX_RULE = {
|
|
817
|
+
meta: {
|
|
818
|
+
type: 'suggestion',
|
|
819
|
+
fixable: undefined,
|
|
820
|
+
docs: {
|
|
821
|
+
description: 'Require the canonical `Query` suffix on `@dbxModelFirebaseIndex`-tagged factories.',
|
|
822
|
+
recommended: true
|
|
823
|
+
},
|
|
824
|
+
messages: {
|
|
825
|
+
queryFactoryNameMustEndWithQuery: '`@dbxModelFirebaseIndex`-tagged factory `{{name}}` must end with `{{suffix}}`. Rename to e.g. `{{suggestion}}` so dbx-components-mcp can identify it consistently.',
|
|
826
|
+
anonymousQueryFactory: '`@dbxModelFirebaseIndex`-tagged factories must be named (must end with `{{suffix}}`); anonymous functions are not allowed.'
|
|
827
|
+
},
|
|
828
|
+
schema: [
|
|
829
|
+
{
|
|
830
|
+
type: 'object',
|
|
831
|
+
additionalProperties: false,
|
|
832
|
+
properties: {
|
|
833
|
+
suffix: {
|
|
834
|
+
type: 'string'
|
|
835
|
+
},
|
|
836
|
+
markerTag: {
|
|
837
|
+
type: 'string'
|
|
838
|
+
},
|
|
839
|
+
allowedSuffixes: {
|
|
840
|
+
type: 'array',
|
|
841
|
+
items: {
|
|
842
|
+
type: 'string'
|
|
843
|
+
}
|
|
844
|
+
},
|
|
845
|
+
strippableSuffixes: {
|
|
846
|
+
type: 'array',
|
|
847
|
+
items: {
|
|
848
|
+
type: 'string'
|
|
849
|
+
}
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
}
|
|
853
|
+
]
|
|
854
|
+
},
|
|
855
|
+
create: function create(context) {
|
|
856
|
+
var _context_options_, _options_suffix, _options_markerTag, _options_allowedSuffixes, _options_strippableSuffixes;
|
|
857
|
+
var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
|
|
858
|
+
var sourceCode = context.sourceCode;
|
|
859
|
+
var suffix = (_options_suffix = options.suffix) !== null && _options_suffix !== void 0 ? _options_suffix : QUERY_SUFFIX;
|
|
860
|
+
var markerTag = (_options_markerTag = options.markerTag) !== null && _options_markerTag !== void 0 ? _options_markerTag : DBX_MODEL_FIREBASE_INDEX_MARKER;
|
|
861
|
+
var allowedSuffixes = (_options_allowedSuffixes = options.allowedSuffixes) !== null && _options_allowedSuffixes !== void 0 ? _options_allowedSuffixes : [];
|
|
862
|
+
var strippableSuffixes = (_options_strippableSuffixes = options.strippableSuffixes) !== null && _options_strippableSuffixes !== void 0 ? _options_strippableSuffixes : DEFAULT_STRIPPABLE_SUFFIXES;
|
|
863
|
+
function isTagged(anchor) {
|
|
864
|
+
var jsdoc = leadingJsdocFor(sourceCode, anchor);
|
|
865
|
+
var result = false;
|
|
866
|
+
if (jsdoc) {
|
|
867
|
+
var parsed = parseJsdocComment(jsdoc.value);
|
|
868
|
+
result = parsed.tags.some(function(t) {
|
|
869
|
+
return t.tag === markerTag;
|
|
870
|
+
});
|
|
871
|
+
}
|
|
872
|
+
return result;
|
|
873
|
+
}
|
|
874
|
+
function nameMatchesSuffix(name) {
|
|
875
|
+
var matches = name.endsWith(suffix);
|
|
876
|
+
if (!matches) {
|
|
877
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
878
|
+
try {
|
|
879
|
+
for(var _iterator = allowedSuffixes[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
880
|
+
var allowed = _step.value;
|
|
881
|
+
if (name.endsWith(allowed)) {
|
|
882
|
+
matches = true;
|
|
883
|
+
break;
|
|
884
|
+
}
|
|
885
|
+
}
|
|
886
|
+
} catch (err) {
|
|
887
|
+
_didIteratorError = true;
|
|
888
|
+
_iteratorError = err;
|
|
889
|
+
} finally{
|
|
890
|
+
try {
|
|
891
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
892
|
+
_iterator.return();
|
|
893
|
+
}
|
|
894
|
+
} finally{
|
|
895
|
+
if (_didIteratorError) {
|
|
896
|
+
throw _iteratorError;
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
return matches;
|
|
902
|
+
}
|
|
903
|
+
function check(node) {
|
|
904
|
+
var anchor = getFunctionJsdocAnchor(node);
|
|
905
|
+
if (!anchor || !isTagged(anchor)) return;
|
|
906
|
+
var name = getFunctionName(node);
|
|
907
|
+
if (!name) {
|
|
908
|
+
context.report({
|
|
909
|
+
node: getFunctionNameNode(node),
|
|
910
|
+
messageId: 'anonymousQueryFactory',
|
|
911
|
+
data: {
|
|
912
|
+
suffix: suffix
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
return;
|
|
916
|
+
}
|
|
917
|
+
if (!nameMatchesSuffix(name)) {
|
|
918
|
+
var suggestion = buildRenameSuggestion(name, suffix, strippableSuffixes);
|
|
919
|
+
context.report({
|
|
920
|
+
node: getFunctionNameNode(node),
|
|
921
|
+
messageId: 'queryFactoryNameMustEndWithQuery',
|
|
922
|
+
data: {
|
|
923
|
+
name: name,
|
|
924
|
+
suffix: suffix,
|
|
925
|
+
suggestion: suggestion
|
|
926
|
+
}
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
}
|
|
930
|
+
return {
|
|
931
|
+
FunctionDeclaration: function FunctionDeclaration(node) {
|
|
932
|
+
return check(node);
|
|
933
|
+
},
|
|
934
|
+
FunctionExpression: function FunctionExpression(node) {
|
|
935
|
+
return check(node);
|
|
936
|
+
},
|
|
937
|
+
ArrowFunctionExpression: function ArrowFunctionExpression(node) {
|
|
938
|
+
return check(node);
|
|
939
|
+
}
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
};
|
|
943
|
+
|
|
944
|
+
function _array_like_to_array(arr, len) {
|
|
945
|
+
if (len == null || len > arr.length) len = arr.length;
|
|
946
|
+
for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
|
|
947
|
+
return arr2;
|
|
948
|
+
}
|
|
949
|
+
function _array_with_holes(arr) {
|
|
950
|
+
if (Array.isArray(arr)) return arr;
|
|
951
|
+
}
|
|
952
|
+
function _iterable_to_array_limit(arr, i) {
|
|
953
|
+
var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
|
|
954
|
+
if (_i == null) return;
|
|
955
|
+
var _arr = [];
|
|
956
|
+
var _n = true;
|
|
957
|
+
var _d = false;
|
|
958
|
+
var _s, _e;
|
|
959
|
+
try {
|
|
960
|
+
for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
|
|
961
|
+
_arr.push(_s.value);
|
|
962
|
+
if (i && _arr.length === i) break;
|
|
963
|
+
}
|
|
964
|
+
} catch (err) {
|
|
965
|
+
_d = true;
|
|
966
|
+
_e = err;
|
|
967
|
+
} finally{
|
|
968
|
+
try {
|
|
969
|
+
if (!_n && _i["return"] != null) _i["return"]();
|
|
970
|
+
} finally{
|
|
971
|
+
if (_d) throw _e;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
return _arr;
|
|
975
|
+
}
|
|
976
|
+
function _non_iterable_rest() {
|
|
977
|
+
throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
|
|
978
|
+
}
|
|
979
|
+
function _sliced_to_array(arr, i) {
|
|
980
|
+
return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
|
|
981
|
+
}
|
|
982
|
+
function _unsupported_iterable_to_array(o, minLen) {
|
|
983
|
+
if (!o) return;
|
|
984
|
+
if (typeof o === "string") return _array_like_to_array(o, minLen);
|
|
985
|
+
var n = Object.prototype.toString.call(o).slice(8, -1);
|
|
986
|
+
if (n === "Object" && o.constructor) n = o.constructor.name;
|
|
987
|
+
if (n === "Map" || n === "Set") return Array.from(n);
|
|
988
|
+
if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
|
|
989
|
+
}
|
|
990
|
+
/**
|
|
991
|
+
* Shared helpers for the `@dbx<Family>` companion-tag ESLint rules. Mirrors the
|
|
992
|
+
* scanner schemas in `packages/dbx-components-mcp/src/scan/*-extract.ts` so
|
|
993
|
+
* violations surface at lint time instead of at manifest-regeneration time.
|
|
994
|
+
*
|
|
995
|
+
* Each per-family rule supplies a {@link DbxTagFamilySpec} describing which
|
|
996
|
+
* companions are required/optional, what value format each accepts, and which
|
|
997
|
+
* messageId to emit when a check fails. The rule body itself only wires the
|
|
998
|
+
* visitors and message map; all value-format validation lives here.
|
|
999
|
+
*/ /**
|
|
1000
|
+
* Kebab-case slug pattern: lowercase letters/digits, words separated by single hyphens,
|
|
1001
|
+
* starts with a letter. Used to validate slug/related/skill-ref values.
|
|
1002
|
+
*/ var KEBAB_SLUG_PATTERN = /^[a-z][a-z0-9]*(-[a-z0-9]+)*$/;
|
|
1003
|
+
/**
|
|
1004
|
+
* Pascal-case TypeScript identifier pattern (starts uppercase). Used for model
|
|
1005
|
+
* identifiers and other `<ModelName>` style tag values.
|
|
1006
|
+
*/ var PASCAL_IDENTIFIER_PATTERN = /^[A-Z][A-Za-z0-9_$]*$/;
|
|
1007
|
+
/**
|
|
1008
|
+
* Boolean tag-value vocabulary (case-insensitive). `''` and `true|yes` map to
|
|
1009
|
+
* true; `false|no` map to false. Anything else is flagged as invalid.
|
|
1010
|
+
*/ var TRUE_TAG_VALUES = new Set([
|
|
1011
|
+
'',
|
|
1012
|
+
'true',
|
|
1013
|
+
'yes'
|
|
1014
|
+
]);
|
|
1015
|
+
var FALSE_TAG_VALUES = new Set([
|
|
1016
|
+
'false',
|
|
1017
|
+
'no'
|
|
1018
|
+
]);
|
|
1019
|
+
/**
|
|
1020
|
+
* Splits a comma-separated tag-value string into trimmed items, preserving order.
|
|
1021
|
+
*
|
|
1022
|
+
* @param value - Raw text following the tag name on a single line.
|
|
1023
|
+
* @returns Non-empty trimmed segments in declaration order.
|
|
1024
|
+
*/ function splitCommaSeparated(value) {
|
|
1025
|
+
return value.split(',').map(function(item) {
|
|
1026
|
+
return item.trim();
|
|
1027
|
+
}).filter(function(item) {
|
|
1028
|
+
return item.length > 0;
|
|
1029
|
+
});
|
|
1030
|
+
}
|
|
1031
|
+
/**
|
|
1032
|
+
* Parses a boolean tag value using the workspace vocabulary
|
|
1033
|
+
* (`''`/`true`/`yes` → true; `false`/`no` → false). Other inputs return `undefined`.
|
|
1034
|
+
*
|
|
1035
|
+
* @param text - The trimmed tag value text.
|
|
1036
|
+
* @returns The parsed boolean, or `undefined` when the text is not in the vocabulary.
|
|
1037
|
+
*/ function parseBooleanTagValue(text) {
|
|
1038
|
+
var lowered = text.trim().toLowerCase();
|
|
1039
|
+
var result;
|
|
1040
|
+
if (TRUE_TAG_VALUES.has(lowered)) {
|
|
1041
|
+
result = true;
|
|
1042
|
+
} else if (FALSE_TAG_VALUES.has(lowered)) {
|
|
1043
|
+
result = false;
|
|
1044
|
+
}
|
|
1045
|
+
return result;
|
|
1046
|
+
}
|
|
1047
|
+
/**
|
|
1048
|
+
* Returns the source-text offset of an offset-within-comment-value, given a Block comment node.
|
|
1049
|
+
*
|
|
1050
|
+
* @param commentNode - The ESLint Block comment AST node.
|
|
1051
|
+
* @param valueOffset - The character offset within `comment.value`.
|
|
1052
|
+
* @returns The character offset in the source file.
|
|
1053
|
+
*/ function commentValueToSourceOffset(commentNode, valueOffset) {
|
|
1054
|
+
return commentNode.range[0] + 2 + valueOffset;
|
|
1055
|
+
}
|
|
1056
|
+
/**
|
|
1057
|
+
* Returns the family marker + companion tag list for the given parsed JSDoc.
|
|
1058
|
+
* Family membership is determined by tag-name prefix.
|
|
1059
|
+
*
|
|
1060
|
+
* @param parsed - The parsed JSDoc.
|
|
1061
|
+
* @param marker - The bare family marker (e.g. `'dbxPipe'`).
|
|
1062
|
+
* @returns The marker tag (if present), and all companion tags in source order.
|
|
1063
|
+
*/ function findFamilyTags(parsed, marker) {
|
|
1064
|
+
var familyTags = parsed.tags.filter(function(t) {
|
|
1065
|
+
return t.tag === marker || t.tag.startsWith(marker);
|
|
1066
|
+
});
|
|
1067
|
+
var markerTag = parsed.tags.find(function(t) {
|
|
1068
|
+
return t.tag === marker;
|
|
1069
|
+
});
|
|
1070
|
+
return {
|
|
1071
|
+
markerTag: markerTag,
|
|
1072
|
+
familyTags: familyTags
|
|
1073
|
+
};
|
|
1074
|
+
}
|
|
1075
|
+
/**
|
|
1076
|
+
* Groups companion tags (those after the marker) by suffix. Marker entries are excluded.
|
|
1077
|
+
*
|
|
1078
|
+
* @param familyTags - The family tag list from {@link findFamilyTags}.
|
|
1079
|
+
* @param marker - The bare family marker.
|
|
1080
|
+
* @returns Lookup keyed by companion-suffix listing matching tags in declaration order.
|
|
1081
|
+
*/ function groupCompanionsBySuffix(familyTags, marker) {
|
|
1082
|
+
var result = new Map();
|
|
1083
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1084
|
+
try {
|
|
1085
|
+
for(var _iterator = familyTags[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1086
|
+
var tag = _step.value;
|
|
1087
|
+
var _result_get;
|
|
1088
|
+
if (tag.tag === marker) continue;
|
|
1089
|
+
var suffix = tag.tag.slice(marker.length);
|
|
1090
|
+
var list = (_result_get = result.get(suffix)) !== null && _result_get !== void 0 ? _result_get : [];
|
|
1091
|
+
list.push(tag);
|
|
1092
|
+
result.set(suffix, list);
|
|
1093
|
+
}
|
|
1094
|
+
} catch (err) {
|
|
1095
|
+
_didIteratorError = true;
|
|
1096
|
+
_iteratorError = err;
|
|
1097
|
+
} finally{
|
|
1098
|
+
try {
|
|
1099
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1100
|
+
_iterator.return();
|
|
1101
|
+
}
|
|
1102
|
+
} finally{
|
|
1103
|
+
if (_didIteratorError) {
|
|
1104
|
+
throw _iteratorError;
|
|
1105
|
+
}
|
|
1106
|
+
}
|
|
1107
|
+
}
|
|
1108
|
+
return result;
|
|
1109
|
+
}
|
|
1110
|
+
/**
|
|
1111
|
+
* Per-format validators dispatched from {@link validateCompanionValue}. Each entry validates
|
|
1112
|
+
* a single non-empty tag value and emits zero or more violations.
|
|
1113
|
+
*/ var VALUE_VALIDATORS = {
|
|
1114
|
+
marker: function marker() {
|
|
1115
|
+
return undefined;
|
|
1116
|
+
},
|
|
1117
|
+
'free-text': function() {
|
|
1118
|
+
return undefined;
|
|
1119
|
+
},
|
|
1120
|
+
'comma-list-free-text': function() {
|
|
1121
|
+
return undefined;
|
|
1122
|
+
},
|
|
1123
|
+
'kebab-slug': function(param) {
|
|
1124
|
+
var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
|
|
1125
|
+
if (!KEBAB_SLUG_PATTERN.test(value)) emit({
|
|
1126
|
+
kind: 'invalid-kebab',
|
|
1127
|
+
suffix: spec.suffix,
|
|
1128
|
+
value: value,
|
|
1129
|
+
lineIndex: lineIndex
|
|
1130
|
+
});
|
|
1131
|
+
},
|
|
1132
|
+
enum: function _enum(param) {
|
|
1133
|
+
var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
|
|
1134
|
+
var format = spec.format;
|
|
1135
|
+
if (!format.values.includes(value)) emit({
|
|
1136
|
+
kind: 'invalid-enum',
|
|
1137
|
+
suffix: spec.suffix,
|
|
1138
|
+
value: value,
|
|
1139
|
+
allowed: format.values,
|
|
1140
|
+
lineIndex: lineIndex
|
|
1141
|
+
});
|
|
1142
|
+
},
|
|
1143
|
+
'pascal-identifier': function(param) {
|
|
1144
|
+
var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
|
|
1145
|
+
if (!PASCAL_IDENTIFIER_PATTERN.test(value)) emit({
|
|
1146
|
+
kind: 'invalid-pascal',
|
|
1147
|
+
suffix: spec.suffix,
|
|
1148
|
+
value: value,
|
|
1149
|
+
lineIndex: lineIndex
|
|
1150
|
+
});
|
|
1151
|
+
},
|
|
1152
|
+
'comma-list-kebab-slug': function(param) {
|
|
1153
|
+
var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
|
|
1154
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1155
|
+
try {
|
|
1156
|
+
for(var _iterator = splitCommaSeparated(value)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1157
|
+
var item = _step.value;
|
|
1158
|
+
if (!KEBAB_SLUG_PATTERN.test(item)) emit({
|
|
1159
|
+
kind: 'comma-item-not-kebab',
|
|
1160
|
+
suffix: spec.suffix,
|
|
1161
|
+
value: item,
|
|
1162
|
+
lineIndex: lineIndex
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
} catch (err) {
|
|
1166
|
+
_didIteratorError = true;
|
|
1167
|
+
_iteratorError = err;
|
|
1168
|
+
} finally{
|
|
1169
|
+
try {
|
|
1170
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1171
|
+
_iterator.return();
|
|
1172
|
+
}
|
|
1173
|
+
} finally{
|
|
1174
|
+
if (_didIteratorError) {
|
|
1175
|
+
throw _iteratorError;
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
1179
|
+
},
|
|
1180
|
+
'comma-list-lowercase': function(param) {
|
|
1181
|
+
var spec = param.spec, tag = param.tag, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
|
|
1182
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1183
|
+
try {
|
|
1184
|
+
for(var _iterator = splitCommaSeparated(value)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1185
|
+
var item = _step.value;
|
|
1186
|
+
if (/[A-Z]/.test(item)) emit({
|
|
1187
|
+
kind: 'tags-not-lowercase',
|
|
1188
|
+
suffix: spec.suffix,
|
|
1189
|
+
value: item,
|
|
1190
|
+
lineIndex: lineIndex,
|
|
1191
|
+
raw: tag
|
|
1192
|
+
});
|
|
1193
|
+
}
|
|
1194
|
+
} catch (err) {
|
|
1195
|
+
_didIteratorError = true;
|
|
1196
|
+
_iteratorError = err;
|
|
1197
|
+
} finally{
|
|
1198
|
+
try {
|
|
1199
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1200
|
+
_iterator.return();
|
|
1201
|
+
}
|
|
1202
|
+
} finally{
|
|
1203
|
+
if (_didIteratorError) {
|
|
1204
|
+
throw _iteratorError;
|
|
1205
|
+
}
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
},
|
|
1209
|
+
boolean: function boolean(param) {
|
|
1210
|
+
var spec = param.spec, value = param.value, lineIndex = param.lineIndex, emit = param.emit;
|
|
1211
|
+
if (parseBooleanTagValue(value) === undefined) emit({
|
|
1212
|
+
kind: 'invalid-boolean',
|
|
1213
|
+
suffix: spec.suffix,
|
|
1214
|
+
value: value,
|
|
1215
|
+
lineIndex: lineIndex
|
|
1216
|
+
});
|
|
1217
|
+
}
|
|
1218
|
+
};
|
|
1219
|
+
/**
|
|
1220
|
+
* Validates one companion tag's value against the configured {@link DbxTagFormat}
|
|
1221
|
+
* and emits zero-or-more violations. Used by {@link checkDbxTagFamily} to keep
|
|
1222
|
+
* each rule's body small.
|
|
1223
|
+
*
|
|
1224
|
+
* @param spec - The companion spec being validated.
|
|
1225
|
+
* @param tags - All occurrences of the companion in source order.
|
|
1226
|
+
* @param emit - Callback for each violation.
|
|
1227
|
+
*/ function validateCompanionValue(spec, tags, emit) {
|
|
1228
|
+
if (spec.format.kind === 'marker') return;
|
|
1229
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1230
|
+
try {
|
|
1231
|
+
for(var _iterator = tags[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1232
|
+
var tag = _step.value;
|
|
1233
|
+
var value = tag.description.trim();
|
|
1234
|
+
var lineIndex = tag.startLineIndex;
|
|
1235
|
+
if (value.length === 0) {
|
|
1236
|
+
if (spec.format.kind !== 'boolean') emit({
|
|
1237
|
+
kind: 'empty',
|
|
1238
|
+
suffix: spec.suffix,
|
|
1239
|
+
lineIndex: lineIndex
|
|
1240
|
+
});
|
|
1241
|
+
continue;
|
|
1242
|
+
}
|
|
1243
|
+
var validator = VALUE_VALIDATORS[spec.format.kind];
|
|
1244
|
+
if (validator) validator({
|
|
1245
|
+
spec: spec,
|
|
1246
|
+
tag: tag,
|
|
1247
|
+
value: value,
|
|
1248
|
+
lineIndex: lineIndex,
|
|
1249
|
+
emit: emit
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
} catch (err) {
|
|
1253
|
+
_didIteratorError = true;
|
|
1254
|
+
_iteratorError = err;
|
|
1255
|
+
} finally{
|
|
1256
|
+
try {
|
|
1257
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1258
|
+
_iterator.return();
|
|
1259
|
+
}
|
|
1260
|
+
} finally{
|
|
1261
|
+
if (_didIteratorError) {
|
|
1262
|
+
throw _iteratorError;
|
|
1263
|
+
}
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
}
|
|
1267
|
+
function emitUnknownCompanions(groups, knownSuffixes, emit) {
|
|
1268
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1269
|
+
try {
|
|
1270
|
+
for(var _iterator = groups.entries()[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1271
|
+
var _step_value = _sliced_to_array(_step.value, 2), suffix = _step_value[0], instances = _step_value[1];
|
|
1272
|
+
if (knownSuffixes.has(suffix)) continue;
|
|
1273
|
+
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
1274
|
+
try {
|
|
1275
|
+
for(var _iterator1 = instances[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
1276
|
+
var tag = _step1.value;
|
|
1277
|
+
emit({
|
|
1278
|
+
kind: 'unknown',
|
|
1279
|
+
suffix: suffix,
|
|
1280
|
+
lineIndex: tag.startLineIndex
|
|
1281
|
+
});
|
|
1282
|
+
}
|
|
1283
|
+
} catch (err) {
|
|
1284
|
+
_didIteratorError1 = true;
|
|
1285
|
+
_iteratorError1 = err;
|
|
1286
|
+
} finally{
|
|
1287
|
+
try {
|
|
1288
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
1289
|
+
_iterator1.return();
|
|
1290
|
+
}
|
|
1291
|
+
} finally{
|
|
1292
|
+
if (_didIteratorError1) {
|
|
1293
|
+
throw _iteratorError1;
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
}
|
|
1297
|
+
}
|
|
1298
|
+
} catch (err) {
|
|
1299
|
+
_didIteratorError = true;
|
|
1300
|
+
_iteratorError = err;
|
|
1301
|
+
} finally{
|
|
1302
|
+
try {
|
|
1303
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1304
|
+
_iterator.return();
|
|
1305
|
+
}
|
|
1306
|
+
} finally{
|
|
1307
|
+
if (_didIteratorError) {
|
|
1308
|
+
throw _iteratorError;
|
|
1309
|
+
}
|
|
1310
|
+
}
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
function emitDuplicateCompanions(companion, instances, emit) {
|
|
1314
|
+
for(var i = 1; i < instances.length; i += 1){
|
|
1315
|
+
emit({
|
|
1316
|
+
kind: 'duplicate',
|
|
1317
|
+
suffix: companion.suffix,
|
|
1318
|
+
lineIndex: instances[i].startLineIndex
|
|
1319
|
+
});
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
function checkCompanion(input) {
|
|
1323
|
+
var companion = input.companion, instances = input.instances, markerLineIndex = input.markerLineIndex, emit = input.emit;
|
|
1324
|
+
if (instances.length === 0) {
|
|
1325
|
+
if (companion.required) emit({
|
|
1326
|
+
kind: 'missing',
|
|
1327
|
+
suffix: companion.suffix,
|
|
1328
|
+
lineIndex: markerLineIndex
|
|
1329
|
+
});
|
|
1330
|
+
return;
|
|
1331
|
+
}
|
|
1332
|
+
if (!companion.multiple && instances.length > 1) emitDuplicateCompanions(companion, instances, emit);
|
|
1333
|
+
validateCompanionValue(companion, instances, emit);
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* Validates a `@dbx<Family>` marker plus its companion tags against the supplied spec.
|
|
1337
|
+
* Reports unknown companions, missing required companions, duplicates, and per-companion
|
|
1338
|
+
* value violations through `input.emit`.
|
|
1339
|
+
*
|
|
1340
|
+
* @param input - Parsed JSDoc, family spec, resolved marker/companion tags, and emit sink.
|
|
1341
|
+
*
|
|
1342
|
+
* @example
|
|
1343
|
+
* ```ts
|
|
1344
|
+
* checkDbxTagFamily({ parsed, spec, markerTag, familyTags, emit });
|
|
1345
|
+
* ```
|
|
1346
|
+
*/ function checkDbxTagFamily(input) {
|
|
1347
|
+
var spec = input.spec, markerTag = input.markerTag, familyTags = input.familyTags, emit = input.emit;
|
|
1348
|
+
var knownSuffixes = new Set(spec.companions.map(function(c) {
|
|
1349
|
+
return c.suffix;
|
|
1350
|
+
}));
|
|
1351
|
+
var groups = groupCompanionsBySuffix(familyTags, spec.marker);
|
|
1352
|
+
emitUnknownCompanions(groups, knownSuffixes, emit);
|
|
1353
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1354
|
+
try {
|
|
1355
|
+
for(var _iterator = spec.companions[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1356
|
+
var companion = _step.value;
|
|
1357
|
+
var _groups_get;
|
|
1358
|
+
var instances = (_groups_get = groups.get(companion.suffix)) !== null && _groups_get !== void 0 ? _groups_get : [];
|
|
1359
|
+
checkCompanion({
|
|
1360
|
+
companion: companion,
|
|
1361
|
+
instances: instances,
|
|
1362
|
+
markerLineIndex: markerTag.startLineIndex,
|
|
1363
|
+
emit: emit
|
|
1364
|
+
});
|
|
1365
|
+
}
|
|
1366
|
+
} catch (err) {
|
|
1367
|
+
_didIteratorError = true;
|
|
1368
|
+
_iteratorError = err;
|
|
1369
|
+
} finally{
|
|
1370
|
+
try {
|
|
1371
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1372
|
+
_iterator.return();
|
|
1373
|
+
}
|
|
1374
|
+
} finally{
|
|
1375
|
+
if (_didIteratorError) {
|
|
1376
|
+
throw _iteratorError;
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
}
|
|
1380
|
+
}
|
|
1381
|
+
/**
|
|
1382
|
+
* Translates a JSDoc-line violation into an ESLint `context.report()` call by computing the
|
|
1383
|
+
* source range of the offending line and attaching the supplied message + optional fixer.
|
|
1384
|
+
*
|
|
1385
|
+
* @param input - Reporting context (comment node, parsed JSDoc, source code, line index, message id, optional data + fixer, report sink).
|
|
1386
|
+
*
|
|
1387
|
+
* @example
|
|
1388
|
+
* ```ts
|
|
1389
|
+
* reportOnJsdocLine({ commentNode, parsed, sourceCode, lineIndex: tag.startLineIndex, messageId: 'unknown', report: context.report });
|
|
1390
|
+
* ```
|
|
1391
|
+
*/ function reportOnJsdocLine(input) {
|
|
1392
|
+
var _ref, _ref1;
|
|
1393
|
+
var _line_text;
|
|
1394
|
+
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;
|
|
1395
|
+
var line = parsed.lines[lineIndex];
|
|
1396
|
+
var startInValue = (_ref = line === null || line === void 0 ? void 0 : line.textOffsetStart) !== null && _ref !== void 0 ? _ref : 0;
|
|
1397
|
+
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);
|
|
1398
|
+
var start = commentValueToSourceOffset(commentNode, startInValue);
|
|
1399
|
+
var end = commentValueToSourceOffset(commentNode, endInValue);
|
|
1400
|
+
report({
|
|
1401
|
+
loc: {
|
|
1402
|
+
type: 'SourceLocation',
|
|
1403
|
+
start: sourceCode.getLocFromIndex(start),
|
|
1404
|
+
end: sourceCode.getLocFromIndex(end)
|
|
1405
|
+
},
|
|
1406
|
+
messageId: messageId,
|
|
1407
|
+
data: data,
|
|
1408
|
+
fix: fix
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Computes an auto-fix descriptor that lowercases every token after a `@dbx<Family>Tags` tag
|
|
1413
|
+
* name. Returns `undefined` when the line is already canonical so the caller can short-circuit.
|
|
1414
|
+
*
|
|
1415
|
+
* @param input - Fix context (comment node, parsed JSDoc, source code, target tag).
|
|
1416
|
+
* @returns Replacement range + text when a rewrite is needed; otherwise `undefined`.
|
|
1417
|
+
*
|
|
1418
|
+
* @example
|
|
1419
|
+
* ```ts
|
|
1420
|
+
* const fix = buildLowercaseTagsFix({ commentNode, parsed, sourceCode, tag });
|
|
1421
|
+
* ```
|
|
1422
|
+
*/ function buildLowercaseTagsFix(input) {
|
|
1423
|
+
var commentNode = input.commentNode, parsed = input.parsed, sourceCode = input.sourceCode, tag = input.tag;
|
|
1424
|
+
var tagLine = parsed.lines[tag.startLineIndex];
|
|
1425
|
+
var result;
|
|
1426
|
+
if (tagLine) {
|
|
1427
|
+
var tagLineSourceStart = commentValueToSourceOffset(commentNode, tagLine.textOffsetStart);
|
|
1428
|
+
var tagLineSourceEnd = tagLineSourceStart + tagLine.text.length;
|
|
1429
|
+
var sourceText = sourceCode.getText();
|
|
1430
|
+
var lineSource = sourceText.slice(tagLineSourceStart, tagLineSourceEnd);
|
|
1431
|
+
var lowered = lineSource.replace(/^(@[A-Za-z]+\s+)(.*)$/, function(_match, prefix, body) {
|
|
1432
|
+
return "".concat(prefix).concat(body.toLowerCase());
|
|
1433
|
+
});
|
|
1434
|
+
if (lowered !== lineSource) {
|
|
1435
|
+
result = {
|
|
1436
|
+
startOffset: tagLineSourceStart,
|
|
1437
|
+
endOffset: tagLineSourceEnd,
|
|
1438
|
+
replacement: lowered
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
}
|
|
1442
|
+
return result;
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1445
|
+
function _type_of$1(obj) {
|
|
1446
|
+
"@swc/helpers - typeof";
|
|
1447
|
+
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
1448
|
+
}
|
|
1449
|
+
var DEFAULT_ALLOWED_SCOPES = [
|
|
1450
|
+
'COLLECTION',
|
|
1451
|
+
'COLLECTION_GROUP'
|
|
1452
|
+
];
|
|
1453
|
+
var DEFAULT_KNOWN_COMPANIONS = [
|
|
1454
|
+
'Slug',
|
|
1455
|
+
'Model',
|
|
1456
|
+
'Scope',
|
|
1457
|
+
'Dispatcher',
|
|
1458
|
+
'Manual',
|
|
1459
|
+
'Skip',
|
|
1460
|
+
'AllowArrayContainsAny',
|
|
1461
|
+
'Category',
|
|
1462
|
+
'Tags',
|
|
1463
|
+
'Related',
|
|
1464
|
+
'SkillRefs',
|
|
1465
|
+
'Path',
|
|
1466
|
+
'Helper'
|
|
1467
|
+
];
|
|
1468
|
+
/**
|
|
1469
|
+
* ESLint rule enforcing `@dbxModelFirebaseIndex` companion tags and body coherence.
|
|
1470
|
+
* Mirrors the scanner schema at
|
|
1471
|
+
* `packages/dbx-components-mcp/src/scan/model-firebase-index-extract.ts` (companion tag
|
|
1472
|
+
* validation) and additionally cross-checks the function body so the tag stays in sync
|
|
1473
|
+
* with the constraint calls it advertises.
|
|
1474
|
+
*/ var FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_COMPANION_TAGS_RULE = {
|
|
1475
|
+
meta: {
|
|
1476
|
+
type: 'suggestion',
|
|
1477
|
+
fixable: 'code',
|
|
1478
|
+
docs: {
|
|
1479
|
+
description: 'Require the canonical `@dbxModelFirebaseIndex*` companion tags on `@dbxModelFirebaseIndex`-tagged factories and verify the body matches the declared model.',
|
|
1480
|
+
recommended: true
|
|
1481
|
+
},
|
|
1482
|
+
messages: {
|
|
1483
|
+
missingModel: '`@dbxModelFirebaseIndex`-tagged factory is missing the required `@dbxModelFirebaseIndexModel <ModelName>` companion tag.',
|
|
1484
|
+
invalidModelIdentifier: '`@dbxModelFirebaseIndexModel` value `{{value}}` is not a valid PascalCase identifier.',
|
|
1485
|
+
invalidScope: '`@dbxModelFirebaseIndexScope` value `{{value}}` is not allowed. Use one of: {{allowed}}.',
|
|
1486
|
+
invalidBooleanValue: '`@dbxModelFirebaseIndex{{name}}` value `{{value}}` is not a recognized boolean.',
|
|
1487
|
+
slugNotKebab: '`@dbxModelFirebaseIndexSlug` value `{{value}}` is not a valid kebab-case slug.',
|
|
1488
|
+
tagsNotLowercase: '`@dbxModelFirebaseIndexTags` token `{{value}}` contains uppercase characters; tokens should be lowercase.',
|
|
1489
|
+
relatedNotKebab: '`@dbxModelFirebaseIndexRelated` item `{{value}}` is not a kebab-case slug.',
|
|
1490
|
+
skillRefsNotKebab: '`@dbxModelFirebaseIndexSkillRefs` item `{{value}}` is not a kebab-case slug.',
|
|
1491
|
+
unknownDbxModelFirebaseIndexTag: '`@dbxModelFirebaseIndex{{name}}` is not a recognized companion tag. Known companions: {{known}}.',
|
|
1492
|
+
duplicateCompanionTag: '`@dbxModelFirebaseIndex{{name}}` is declared more than once.',
|
|
1493
|
+
taggedFactoryHasNoConstraints: '`@dbxModelFirebaseIndex`-tagged factory `{{name}}` contains no `@dereekb/firebase` constraint calls. Add at least one (`where`/`orderBy`/...) or mark it `@dbxModelFirebaseIndexSkip true`.',
|
|
1494
|
+
modelTagMismatchesGenericArg: '`@dbxModelFirebaseIndexModel` is `{{tagValue}}` but the body uses `{{constraint}}<{{generic}}>(...)`. They should match.',
|
|
1495
|
+
nonLiteralFieldPathInTaggedQuery: 'Field path passed to `{{constraint}}(...)` in a `@dbxModelFirebaseIndex`-tagged factory must be a string literal so dbx-components-mcp can extract it.'
|
|
1496
|
+
},
|
|
1497
|
+
schema: [
|
|
1498
|
+
{
|
|
1499
|
+
type: 'object',
|
|
1500
|
+
additionalProperties: false,
|
|
1501
|
+
properties: {
|
|
1502
|
+
allowedScopes: {
|
|
1503
|
+
type: 'array',
|
|
1504
|
+
items: {
|
|
1505
|
+
type: 'string'
|
|
1506
|
+
}
|
|
1507
|
+
},
|
|
1508
|
+
knownCompanions: {
|
|
1509
|
+
type: 'array',
|
|
1510
|
+
items: {
|
|
1511
|
+
type: 'string'
|
|
1512
|
+
}
|
|
1513
|
+
},
|
|
1514
|
+
requireBareMarker: {
|
|
1515
|
+
type: 'boolean'
|
|
1516
|
+
},
|
|
1517
|
+
constraintNames: {
|
|
1518
|
+
type: 'array',
|
|
1519
|
+
items: {
|
|
1520
|
+
type: 'string'
|
|
1521
|
+
}
|
|
1522
|
+
},
|
|
1523
|
+
allowDynamicFieldPaths: {
|
|
1524
|
+
type: 'boolean'
|
|
1525
|
+
},
|
|
1526
|
+
checkBodyCoherence: {
|
|
1527
|
+
type: 'boolean'
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
}
|
|
1531
|
+
]
|
|
1532
|
+
},
|
|
1533
|
+
create: function create(context) {
|
|
1534
|
+
var _context_options_, _options_allowedScopes, _options_knownCompanions, _options_constraintNames;
|
|
1535
|
+
var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
|
|
1536
|
+
var sourceCode = context.sourceCode;
|
|
1537
|
+
var allowedScopes = (_options_allowedScopes = options.allowedScopes) !== null && _options_allowedScopes !== void 0 ? _options_allowedScopes : DEFAULT_ALLOWED_SCOPES;
|
|
1538
|
+
var knownCompanions = (_options_knownCompanions = options.knownCompanions) !== null && _options_knownCompanions !== void 0 ? _options_knownCompanions : DEFAULT_KNOWN_COMPANIONS;
|
|
1539
|
+
var requireBareMarker = options.requireBareMarker !== false;
|
|
1540
|
+
var constraintNames = new Set((_options_constraintNames = options.constraintNames) !== null && _options_constraintNames !== void 0 ? _options_constraintNames : DEFAULT_CONSTRAINT_FACTORY_NAMES);
|
|
1541
|
+
var allowDynamicFieldPaths = options.allowDynamicFieldPaths === true;
|
|
1542
|
+
var checkBodyCoherence = options.checkBodyCoherence !== false;
|
|
1543
|
+
var allCompanions = [
|
|
1544
|
+
{
|
|
1545
|
+
suffix: 'Slug',
|
|
1546
|
+
format: {
|
|
1547
|
+
kind: 'kebab-slug'
|
|
1548
|
+
}
|
|
1549
|
+
},
|
|
1550
|
+
{
|
|
1551
|
+
suffix: 'Model',
|
|
1552
|
+
required: true,
|
|
1553
|
+
format: {
|
|
1554
|
+
kind: 'pascal-identifier'
|
|
1555
|
+
}
|
|
1556
|
+
},
|
|
1557
|
+
{
|
|
1558
|
+
suffix: 'Scope',
|
|
1559
|
+
format: {
|
|
1560
|
+
kind: 'enum',
|
|
1561
|
+
values: allowedScopes
|
|
1562
|
+
}
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
suffix: 'Dispatcher',
|
|
1566
|
+
format: {
|
|
1567
|
+
kind: 'boolean'
|
|
1568
|
+
}
|
|
1569
|
+
},
|
|
1570
|
+
{
|
|
1571
|
+
suffix: 'Manual',
|
|
1572
|
+
format: {
|
|
1573
|
+
kind: 'boolean'
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
suffix: 'Skip',
|
|
1578
|
+
format: {
|
|
1579
|
+
kind: 'boolean'
|
|
1580
|
+
}
|
|
1581
|
+
},
|
|
1582
|
+
{
|
|
1583
|
+
suffix: 'AllowArrayContainsAny',
|
|
1584
|
+
format: {
|
|
1585
|
+
kind: 'boolean'
|
|
1586
|
+
}
|
|
1587
|
+
},
|
|
1588
|
+
{
|
|
1589
|
+
suffix: 'Category',
|
|
1590
|
+
format: {
|
|
1591
|
+
kind: 'free-text'
|
|
1592
|
+
}
|
|
1593
|
+
},
|
|
1594
|
+
{
|
|
1595
|
+
suffix: 'Tags',
|
|
1596
|
+
format: {
|
|
1597
|
+
kind: 'comma-list-lowercase'
|
|
1598
|
+
}
|
|
1599
|
+
},
|
|
1600
|
+
{
|
|
1601
|
+
suffix: 'Related',
|
|
1602
|
+
format: {
|
|
1603
|
+
kind: 'comma-list-kebab-slug'
|
|
1604
|
+
}
|
|
1605
|
+
},
|
|
1606
|
+
{
|
|
1607
|
+
suffix: 'SkillRefs',
|
|
1608
|
+
format: {
|
|
1609
|
+
kind: 'comma-list-kebab-slug'
|
|
1610
|
+
}
|
|
1611
|
+
},
|
|
1612
|
+
{
|
|
1613
|
+
suffix: 'Path',
|
|
1614
|
+
multiple: true,
|
|
1615
|
+
format: {
|
|
1616
|
+
kind: 'comma-list-free-text'
|
|
1617
|
+
}
|
|
1618
|
+
},
|
|
1619
|
+
{
|
|
1620
|
+
suffix: 'Helper',
|
|
1621
|
+
format: {
|
|
1622
|
+
kind: 'free-text'
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
];
|
|
1626
|
+
var spec = {
|
|
1627
|
+
marker: DBX_MODEL_FIREBASE_INDEX_MARKER,
|
|
1628
|
+
companions: allCompanions.filter(function(c) {
|
|
1629
|
+
return knownCompanions.includes(c.suffix);
|
|
1630
|
+
})
|
|
1631
|
+
};
|
|
1632
|
+
function handleCommaItem(input) {
|
|
1633
|
+
var commentNode = input.commentNode, parsed = input.parsed, suffix = input.suffix, value = input.value, lineIndex = input.lineIndex;
|
|
1634
|
+
if (suffix === 'Related') reportOnJsdocLine({
|
|
1635
|
+
commentNode: commentNode,
|
|
1636
|
+
parsed: parsed,
|
|
1637
|
+
sourceCode: sourceCode,
|
|
1638
|
+
lineIndex: lineIndex,
|
|
1639
|
+
messageId: 'relatedNotKebab',
|
|
1640
|
+
data: {
|
|
1641
|
+
value: value
|
|
1642
|
+
},
|
|
1643
|
+
report: context.report
|
|
1644
|
+
});
|
|
1645
|
+
else if (suffix === 'SkillRefs') reportOnJsdocLine({
|
|
1646
|
+
commentNode: commentNode,
|
|
1647
|
+
parsed: parsed,
|
|
1648
|
+
sourceCode: sourceCode,
|
|
1649
|
+
lineIndex: lineIndex,
|
|
1650
|
+
messageId: 'skillRefsNotKebab',
|
|
1651
|
+
data: {
|
|
1652
|
+
value: value
|
|
1653
|
+
},
|
|
1654
|
+
report: context.report
|
|
1655
|
+
});
|
|
1656
|
+
}
|
|
1657
|
+
function handleTagsNotLowercase(commentNode, parsed, v) {
|
|
1658
|
+
if (v.suffix !== 'Tags') return;
|
|
1659
|
+
var fix = buildLowercaseTagsFix({
|
|
1660
|
+
commentNode: commentNode,
|
|
1661
|
+
parsed: parsed,
|
|
1662
|
+
sourceCode: sourceCode,
|
|
1663
|
+
tag: v.raw
|
|
1664
|
+
});
|
|
1665
|
+
var fixer = fix ? function(fixer2) {
|
|
1666
|
+
return fixer2.replaceTextRange([
|
|
1667
|
+
fix.startOffset,
|
|
1668
|
+
fix.endOffset
|
|
1669
|
+
], fix.replacement);
|
|
1670
|
+
} : undefined;
|
|
1671
|
+
reportOnJsdocLine({
|
|
1672
|
+
commentNode: commentNode,
|
|
1673
|
+
parsed: parsed,
|
|
1674
|
+
sourceCode: sourceCode,
|
|
1675
|
+
lineIndex: v.lineIndex,
|
|
1676
|
+
messageId: 'tagsNotLowercase',
|
|
1677
|
+
data: {
|
|
1678
|
+
value: v.value
|
|
1679
|
+
},
|
|
1680
|
+
report: context.report,
|
|
1681
|
+
fix: fixer
|
|
1682
|
+
});
|
|
1683
|
+
}
|
|
1684
|
+
function handleViolation(commentNode, parsed, v) {
|
|
1685
|
+
switch(v.kind){
|
|
1686
|
+
case 'missing':
|
|
1687
|
+
case 'empty':
|
|
1688
|
+
if (v.suffix === 'Model') reportOnJsdocLine({
|
|
1689
|
+
commentNode: commentNode,
|
|
1690
|
+
parsed: parsed,
|
|
1691
|
+
sourceCode: sourceCode,
|
|
1692
|
+
lineIndex: v.lineIndex,
|
|
1693
|
+
messageId: 'missingModel',
|
|
1694
|
+
report: context.report
|
|
1695
|
+
});
|
|
1696
|
+
break;
|
|
1697
|
+
case 'invalid-kebab':
|
|
1698
|
+
if (v.suffix === 'Slug') reportOnJsdocLine({
|
|
1699
|
+
commentNode: commentNode,
|
|
1700
|
+
parsed: parsed,
|
|
1701
|
+
sourceCode: sourceCode,
|
|
1702
|
+
lineIndex: v.lineIndex,
|
|
1703
|
+
messageId: 'slugNotKebab',
|
|
1704
|
+
data: {
|
|
1705
|
+
value: v.value
|
|
1706
|
+
},
|
|
1707
|
+
report: context.report
|
|
1708
|
+
});
|
|
1709
|
+
break;
|
|
1710
|
+
case 'invalid-pascal':
|
|
1711
|
+
if (v.suffix === 'Model') reportOnJsdocLine({
|
|
1712
|
+
commentNode: commentNode,
|
|
1713
|
+
parsed: parsed,
|
|
1714
|
+
sourceCode: sourceCode,
|
|
1715
|
+
lineIndex: v.lineIndex,
|
|
1716
|
+
messageId: 'invalidModelIdentifier',
|
|
1717
|
+
data: {
|
|
1718
|
+
value: v.value
|
|
1719
|
+
},
|
|
1720
|
+
report: context.report
|
|
1721
|
+
});
|
|
1722
|
+
break;
|
|
1723
|
+
case 'invalid-enum':
|
|
1724
|
+
if (v.suffix === 'Scope') reportOnJsdocLine({
|
|
1725
|
+
commentNode: commentNode,
|
|
1726
|
+
parsed: parsed,
|
|
1727
|
+
sourceCode: sourceCode,
|
|
1728
|
+
lineIndex: v.lineIndex,
|
|
1729
|
+
messageId: 'invalidScope',
|
|
1730
|
+
data: {
|
|
1731
|
+
value: v.value,
|
|
1732
|
+
allowed: v.allowed.join(', ')
|
|
1733
|
+
},
|
|
1734
|
+
report: context.report
|
|
1735
|
+
});
|
|
1736
|
+
break;
|
|
1737
|
+
case 'invalid-boolean':
|
|
1738
|
+
reportOnJsdocLine({
|
|
1739
|
+
commentNode: commentNode,
|
|
1740
|
+
parsed: parsed,
|
|
1741
|
+
sourceCode: sourceCode,
|
|
1742
|
+
lineIndex: v.lineIndex,
|
|
1743
|
+
messageId: 'invalidBooleanValue',
|
|
1744
|
+
data: {
|
|
1745
|
+
name: v.suffix,
|
|
1746
|
+
value: v.value
|
|
1747
|
+
},
|
|
1748
|
+
report: context.report
|
|
1749
|
+
});
|
|
1750
|
+
break;
|
|
1751
|
+
case 'comma-item-not-kebab':
|
|
1752
|
+
handleCommaItem({
|
|
1753
|
+
commentNode: commentNode,
|
|
1754
|
+
parsed: parsed,
|
|
1755
|
+
suffix: v.suffix,
|
|
1756
|
+
value: v.value,
|
|
1757
|
+
lineIndex: v.lineIndex
|
|
1758
|
+
});
|
|
1759
|
+
break;
|
|
1760
|
+
case 'tags-not-lowercase':
|
|
1761
|
+
handleTagsNotLowercase(commentNode, parsed, v);
|
|
1762
|
+
break;
|
|
1763
|
+
case 'unknown':
|
|
1764
|
+
reportOnJsdocLine({
|
|
1765
|
+
commentNode: commentNode,
|
|
1766
|
+
parsed: parsed,
|
|
1767
|
+
sourceCode: sourceCode,
|
|
1768
|
+
lineIndex: v.lineIndex,
|
|
1769
|
+
messageId: 'unknownDbxModelFirebaseIndexTag',
|
|
1770
|
+
data: {
|
|
1771
|
+
name: v.suffix,
|
|
1772
|
+
known: knownCompanions.join(', ')
|
|
1773
|
+
},
|
|
1774
|
+
report: context.report
|
|
1775
|
+
});
|
|
1776
|
+
break;
|
|
1777
|
+
case 'duplicate':
|
|
1778
|
+
reportOnJsdocLine({
|
|
1779
|
+
commentNode: commentNode,
|
|
1780
|
+
parsed: parsed,
|
|
1781
|
+
sourceCode: sourceCode,
|
|
1782
|
+
lineIndex: v.lineIndex,
|
|
1783
|
+
messageId: 'duplicateCompanionTag',
|
|
1784
|
+
data: {
|
|
1785
|
+
name: v.suffix
|
|
1786
|
+
},
|
|
1787
|
+
report: context.report
|
|
1788
|
+
});
|
|
1789
|
+
break;
|
|
1790
|
+
}
|
|
1791
|
+
}
|
|
1792
|
+
function checkJsdoc(commentNode, parsed) {
|
|
1793
|
+
var _findFamilyTags = findFamilyTags(parsed, spec.marker), markerTag = _findFamilyTags.markerTag, familyTags = _findFamilyTags.familyTags;
|
|
1794
|
+
if (familyTags.length === 0 || requireBareMarker && !markerTag) return;
|
|
1795
|
+
var triggerTag = markerTag !== null && markerTag !== void 0 ? markerTag : familyTags[0];
|
|
1796
|
+
checkDbxTagFamily({
|
|
1797
|
+
spec: spec,
|
|
1798
|
+
markerTag: triggerTag,
|
|
1799
|
+
familyTags: familyTags,
|
|
1800
|
+
emit: function emit(v) {
|
|
1801
|
+
return handleViolation(commentNode, parsed, v);
|
|
1802
|
+
}
|
|
1803
|
+
});
|
|
1804
|
+
}
|
|
1805
|
+
function getModelTagValue(parsed) {
|
|
1806
|
+
var tag = parsed.tags.find(function(t) {
|
|
1807
|
+
return t.tag === "".concat(DBX_MODEL_FIREBASE_INDEX_MARKER, "Model");
|
|
1808
|
+
});
|
|
1809
|
+
return tag ? tag.description.trim() : null;
|
|
1810
|
+
}
|
|
1811
|
+
function getSkipTagValue(parsed) {
|
|
1812
|
+
var tag = parsed.tags.find(function(t) {
|
|
1813
|
+
return t.tag === "".concat(DBX_MODEL_FIREBASE_INDEX_MARKER, "Skip");
|
|
1814
|
+
});
|
|
1815
|
+
var result = false;
|
|
1816
|
+
if (tag) {
|
|
1817
|
+
var value = parseBooleanTagValue(tag.description.trim());
|
|
1818
|
+
result = value === true;
|
|
1819
|
+
}
|
|
1820
|
+
return result;
|
|
1821
|
+
}
|
|
1822
|
+
function collectConstraintCallsIntoArray(node, results) {
|
|
1823
|
+
var _node_callee;
|
|
1824
|
+
if (!node || (typeof node === "undefined" ? "undefined" : _type_of$1(node)) !== 'object') return;
|
|
1825
|
+
if (node.type === 'CallExpression' && ((_node_callee = node.callee) === null || _node_callee === void 0 ? void 0 : _node_callee.type) === 'Identifier' && constraintNames.has(node.callee.name)) {
|
|
1826
|
+
var _node_arguments;
|
|
1827
|
+
var genericName = extractGenericIdentifier(node);
|
|
1828
|
+
var firstArg = (_node_arguments = node.arguments) === null || _node_arguments === void 0 ? void 0 : _node_arguments[0];
|
|
1829
|
+
var fieldPathArgIsLiteral = (firstArg === null || firstArg === void 0 ? void 0 : firstArg.type) === 'Literal' && typeof firstArg.value === 'string';
|
|
1830
|
+
results.push({
|
|
1831
|
+
node: node,
|
|
1832
|
+
name: node.callee.name,
|
|
1833
|
+
genericName: genericName,
|
|
1834
|
+
fieldPathArgIsLiteral: fieldPathArgIsLiteral
|
|
1835
|
+
});
|
|
1836
|
+
}
|
|
1837
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1838
|
+
try {
|
|
1839
|
+
for(var _iterator = Object.keys(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1840
|
+
var key = _step.value;
|
|
1841
|
+
if (key === 'parent') continue;
|
|
1842
|
+
var value = node[key];
|
|
1843
|
+
if (Array.isArray(value)) {
|
|
1844
|
+
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
1845
|
+
try {
|
|
1846
|
+
for(var _iterator1 = value[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
1847
|
+
var child = _step1.value;
|
|
1848
|
+
if (child && (typeof child === "undefined" ? "undefined" : _type_of$1(child)) === 'object' && typeof child.type === 'string') collectConstraintCallsIntoArray(child, results);
|
|
1849
|
+
}
|
|
1850
|
+
} catch (err) {
|
|
1851
|
+
_didIteratorError1 = true;
|
|
1852
|
+
_iteratorError1 = err;
|
|
1853
|
+
} finally{
|
|
1854
|
+
try {
|
|
1855
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
1856
|
+
_iterator1.return();
|
|
1857
|
+
}
|
|
1858
|
+
} finally{
|
|
1859
|
+
if (_didIteratorError1) {
|
|
1860
|
+
throw _iteratorError1;
|
|
1861
|
+
}
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
} else if (value && (typeof value === "undefined" ? "undefined" : _type_of$1(value)) === 'object' && typeof value.type === 'string') {
|
|
1865
|
+
collectConstraintCallsIntoArray(value, results);
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
} catch (err) {
|
|
1869
|
+
_didIteratorError = true;
|
|
1870
|
+
_iteratorError = err;
|
|
1871
|
+
} finally{
|
|
1872
|
+
try {
|
|
1873
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1874
|
+
_iterator.return();
|
|
1875
|
+
}
|
|
1876
|
+
} finally{
|
|
1877
|
+
if (_didIteratorError) {
|
|
1878
|
+
throw _iteratorError;
|
|
1879
|
+
}
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
function extractGenericIdentifier(callNode) {
|
|
1884
|
+
var _callNode_typeArguments;
|
|
1885
|
+
var params = (_callNode_typeArguments = callNode.typeArguments) !== null && _callNode_typeArguments !== void 0 ? _callNode_typeArguments : callNode.typeParameters;
|
|
1886
|
+
var result = null;
|
|
1887
|
+
if (params && Array.isArray(params.params) && params.params.length > 0) {
|
|
1888
|
+
var _first_typeName;
|
|
1889
|
+
var first = params.params[0];
|
|
1890
|
+
if ((first === null || first === void 0 ? void 0 : first.type) === 'TSTypeReference' && ((_first_typeName = first.typeName) === null || _first_typeName === void 0 ? void 0 : _first_typeName.type) === 'Identifier') {
|
|
1891
|
+
result = first.typeName.name;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
return result;
|
|
1895
|
+
}
|
|
1896
|
+
function checkBody(node, parsed) {
|
|
1897
|
+
if (!checkBodyCoherence) return;
|
|
1898
|
+
var skip = getSkipTagValue(parsed);
|
|
1899
|
+
if (skip) return;
|
|
1900
|
+
var calls = [];
|
|
1901
|
+
if (node.body) collectConstraintCallsIntoArray(node.body, calls);
|
|
1902
|
+
if (calls.length === 0) {
|
|
1903
|
+
var _getFunctionName, _node_id;
|
|
1904
|
+
var name = (_getFunctionName = getFunctionName(node)) !== null && _getFunctionName !== void 0 ? _getFunctionName : '<anonymous>';
|
|
1905
|
+
context.report({
|
|
1906
|
+
node: (_node_id = node.id) !== null && _node_id !== void 0 ? _node_id : node,
|
|
1907
|
+
messageId: 'taggedFactoryHasNoConstraints',
|
|
1908
|
+
data: {
|
|
1909
|
+
name: name
|
|
1910
|
+
}
|
|
1911
|
+
});
|
|
1912
|
+
return;
|
|
1913
|
+
}
|
|
1914
|
+
var modelTagValue = getModelTagValue(parsed);
|
|
1915
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
1916
|
+
try {
|
|
1917
|
+
for(var _iterator = calls[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
1918
|
+
var call = _step.value;
|
|
1919
|
+
if (modelTagValue && call.genericName && call.genericName !== modelTagValue) {
|
|
1920
|
+
context.report({
|
|
1921
|
+
node: call.node,
|
|
1922
|
+
messageId: 'modelTagMismatchesGenericArg',
|
|
1923
|
+
data: {
|
|
1924
|
+
tagValue: modelTagValue,
|
|
1925
|
+
constraint: call.name,
|
|
1926
|
+
generic: call.genericName
|
|
1927
|
+
}
|
|
1928
|
+
});
|
|
1929
|
+
}
|
|
1930
|
+
if (!allowDynamicFieldPaths && !call.fieldPathArgIsLiteral && call.name !== 'limit' && call.name !== 'limitToLast' && call.name !== 'startAt' && call.name !== 'startAfter' && call.name !== 'endAt' && call.name !== 'endBefore') {
|
|
1931
|
+
context.report({
|
|
1932
|
+
node: call.node,
|
|
1933
|
+
messageId: 'nonLiteralFieldPathInTaggedQuery',
|
|
1934
|
+
data: {
|
|
1935
|
+
constraint: call.name
|
|
1936
|
+
}
|
|
1937
|
+
});
|
|
1938
|
+
}
|
|
1939
|
+
}
|
|
1940
|
+
} catch (err) {
|
|
1941
|
+
_didIteratorError = true;
|
|
1942
|
+
_iteratorError = err;
|
|
1943
|
+
} finally{
|
|
1944
|
+
try {
|
|
1945
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
1946
|
+
_iterator.return();
|
|
1947
|
+
}
|
|
1948
|
+
} finally{
|
|
1949
|
+
if (_didIteratorError) {
|
|
1950
|
+
throw _iteratorError;
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
}
|
|
1955
|
+
function checkFunction(node) {
|
|
1956
|
+
if (!node.body) return;
|
|
1957
|
+
var anchor = node.parent && (node.parent.type === 'ExportNamedDeclaration' || node.parent.type === 'ExportDefaultDeclaration') ? node.parent : node;
|
|
1958
|
+
var commentNode = leadingJsdocFor(sourceCode, getStatementAnchor(anchor));
|
|
1959
|
+
if (!commentNode) return;
|
|
1960
|
+
var parsed = parseJsdocComment(commentNode.value);
|
|
1961
|
+
var markerTag = findFamilyTags(parsed, spec.marker).markerTag;
|
|
1962
|
+
if (!markerTag && requireBareMarker) return;
|
|
1963
|
+
checkJsdoc(commentNode, parsed);
|
|
1964
|
+
checkBody(node, parsed);
|
|
1965
|
+
}
|
|
1966
|
+
return {
|
|
1967
|
+
FunctionDeclaration: function FunctionDeclaration(node) {
|
|
1968
|
+
return checkFunction(node);
|
|
1969
|
+
}
|
|
1970
|
+
};
|
|
1971
|
+
}
|
|
1972
|
+
};
|
|
1973
|
+
|
|
1974
|
+
function _type_of(obj) {
|
|
1975
|
+
"@swc/helpers - typeof";
|
|
1976
|
+
return obj && typeof Symbol !== "undefined" && obj.constructor === Symbol ? "symbol" : typeof obj;
|
|
1977
|
+
}
|
|
1978
|
+
/**
|
|
1979
|
+
* Default JSDoc tag name used to mark a factory as an index dispatcher (boolean tag).
|
|
1980
|
+
*/ var DEFAULT_DISPATCHER_TAG = 'dbxModelFirebaseIndexDispatcher';
|
|
1981
|
+
/**
|
|
1982
|
+
* Default suffix used to recognise a presumed-tagged query factory by name (single-file scope).
|
|
1983
|
+
*/ var DEFAULT_PRESUMED_TAGGED_SUFFIX = 'Query';
|
|
1984
|
+
/**
|
|
1985
|
+
* ESLint rule enforcing that `@dbxModelFirebaseIndexDispatcher`-tagged factories delegate to
|
|
1986
|
+
* other `@dbxModelFirebaseIndex`-tagged query factories instead of building constraints directly.
|
|
1987
|
+
*
|
|
1988
|
+
* A dispatcher must never:
|
|
1989
|
+
*
|
|
1990
|
+
* 1. Call a `@dereekb/firebase` constraint factory (`where`/`orderBy`/`limit`/...) directly.
|
|
1991
|
+
* 2. Construct its own `FirestoreQueryConstraint[]` array via `[].push(...)`-style assembly.
|
|
1992
|
+
*
|
|
1993
|
+
* Permitted bodies return another tagged factory's result directly, spread several tagged
|
|
1994
|
+
* factories' results into a return array, or use a conditional to pick between tagged
|
|
1995
|
+
* factories — matching the dispatcher pattern documented at
|
|
1996
|
+
* `packages/dbx-components-mcp/src/tools/validate-app-model-firebase-index.tool.ts`.
|
|
1997
|
+
*
|
|
1998
|
+
* Not auto-fixable: extracting an inline constraint into a sibling tagged factory requires
|
|
1999
|
+
* choosing a name + signature + call-site replacement that is outside the safe scope of an
|
|
2000
|
+
* ESLint autofix.
|
|
2001
|
+
*/ var FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_VALID_DISPATCHER_RULE = {
|
|
2002
|
+
meta: {
|
|
2003
|
+
type: 'problem',
|
|
2004
|
+
fixable: undefined,
|
|
2005
|
+
docs: {
|
|
2006
|
+
description: 'Disallow inline `@dereekb/firebase` constraint factory calls and ad-hoc constraint-array construction inside `@dbxModelFirebaseIndexDispatcher`-tagged factories. Dispatchers must delegate to other `@dbxModelFirebaseIndex`-tagged `*Query` factories.',
|
|
2007
|
+
recommended: true
|
|
2008
|
+
},
|
|
2009
|
+
messages: {
|
|
2010
|
+
dispatcherUsesInlineConstraint: '`{{name}}(...)` from `@dereekb/firebase` is not allowed inside a `@dbxModelFirebaseIndexDispatcher`-tagged factory. Dispatchers must call other `@dbxModelFirebaseIndex`-tagged query factories instead of building constraints directly.',
|
|
2011
|
+
dispatcherBuildsConstraintArray: '`@dbxModelFirebaseIndexDispatcher`-tagged factories must not construct their own constraint arrays. Return the result of other tagged `*Query` factories directly (or spread them into a return array).'
|
|
2012
|
+
},
|
|
2013
|
+
schema: [
|
|
2014
|
+
{
|
|
2015
|
+
type: 'object',
|
|
2016
|
+
additionalProperties: false,
|
|
2017
|
+
properties: {
|
|
2018
|
+
constraintNames: {
|
|
2019
|
+
type: 'array',
|
|
2020
|
+
items: {
|
|
2021
|
+
type: 'string'
|
|
2022
|
+
}
|
|
2023
|
+
},
|
|
2024
|
+
allowedImportSources: {
|
|
2025
|
+
type: 'array',
|
|
2026
|
+
items: {
|
|
2027
|
+
type: 'string'
|
|
2028
|
+
}
|
|
2029
|
+
},
|
|
2030
|
+
markerTag: {
|
|
2031
|
+
type: 'string'
|
|
2032
|
+
},
|
|
2033
|
+
dispatcherTag: {
|
|
2034
|
+
type: 'string'
|
|
2035
|
+
},
|
|
2036
|
+
presumedTaggedSuffix: {
|
|
2037
|
+
type: 'string'
|
|
2038
|
+
}
|
|
2039
|
+
}
|
|
2040
|
+
}
|
|
2041
|
+
]
|
|
2042
|
+
},
|
|
2043
|
+
create: function create(context) {
|
|
2044
|
+
var _context_options_, _options_constraintNames, _options_allowedImportSources, _options_markerTag, _options_dispatcherTag, _options_presumedTaggedSuffix;
|
|
2045
|
+
var options = (_context_options_ = context.options[0]) !== null && _context_options_ !== void 0 ? _context_options_ : {};
|
|
2046
|
+
var sourceCode = context.sourceCode;
|
|
2047
|
+
var constraintNames = new Set((_options_constraintNames = options.constraintNames) !== null && _options_constraintNames !== void 0 ? _options_constraintNames : DEFAULT_CONSTRAINT_FACTORY_NAMES);
|
|
2048
|
+
var allowedSources = new Set((_options_allowedImportSources = options.allowedImportSources) !== null && _options_allowedImportSources !== void 0 ? _options_allowedImportSources : [
|
|
2049
|
+
FIREBASE_MODULE
|
|
2050
|
+
]);
|
|
2051
|
+
var markerTag = (_options_markerTag = options.markerTag) !== null && _options_markerTag !== void 0 ? _options_markerTag : DBX_MODEL_FIREBASE_INDEX_MARKER;
|
|
2052
|
+
var dispatcherTagName = (_options_dispatcherTag = options.dispatcherTag) !== null && _options_dispatcherTag !== void 0 ? _options_dispatcherTag : DEFAULT_DISPATCHER_TAG;
|
|
2053
|
+
// presumedTaggedSuffix is reserved for future heuristics — the current detection only flags
|
|
2054
|
+
// disallowed patterns, so callee-name allow-listing is not consulted here. Read it once so the
|
|
2055
|
+
// option stays documented and validated against the schema.
|
|
2056
|
+
(_options_presumedTaggedSuffix = options.presumedTaggedSuffix) !== null && _options_presumedTaggedSuffix !== void 0 ? _options_presumedTaggedSuffix : DEFAULT_PRESUMED_TAGGED_SUFFIX;
|
|
2057
|
+
var registry = createImportRegistry();
|
|
2058
|
+
function jsdocFlagsDispatcher(anchor) {
|
|
2059
|
+
var jsdoc = leadingJsdocFor(sourceCode, anchor);
|
|
2060
|
+
var result = false;
|
|
2061
|
+
if (jsdoc) {
|
|
2062
|
+
var parsed = parseJsdocComment(jsdoc.value);
|
|
2063
|
+
var hasMarker = parsed.tags.some(function(t) {
|
|
2064
|
+
return t.tag === markerTag;
|
|
2065
|
+
});
|
|
2066
|
+
if (hasMarker) {
|
|
2067
|
+
var dispatcherTag = parsed.tags.find(function(t) {
|
|
2068
|
+
return t.tag === dispatcherTagName;
|
|
2069
|
+
});
|
|
2070
|
+
if (dispatcherTag) {
|
|
2071
|
+
var value = parseBooleanTagValue(dispatcherTag.description.trim());
|
|
2072
|
+
result = value === true;
|
|
2073
|
+
}
|
|
2074
|
+
}
|
|
2075
|
+
}
|
|
2076
|
+
return result;
|
|
2077
|
+
}
|
|
2078
|
+
function resolveConstraintCallName(node) {
|
|
2079
|
+
var _node_callee;
|
|
2080
|
+
var result = null;
|
|
2081
|
+
if (((_node_callee = node.callee) === null || _node_callee === void 0 ? void 0 : _node_callee.type) === 'Identifier') {
|
|
2082
|
+
var localName = node.callee.name;
|
|
2083
|
+
var source = registry.localToSource.get(localName);
|
|
2084
|
+
if (source && allowedSources.has(source) && isImportedFrom(registry, localName, source)) {
|
|
2085
|
+
var _registry_localToImported_get;
|
|
2086
|
+
var importedName = (_registry_localToImported_get = registry.localToImported.get(localName)) !== null && _registry_localToImported_get !== void 0 ? _registry_localToImported_get : localName;
|
|
2087
|
+
if (constraintNames.has(importedName)) {
|
|
2088
|
+
result = importedName;
|
|
2089
|
+
}
|
|
2090
|
+
}
|
|
2091
|
+
}
|
|
2092
|
+
return result;
|
|
2093
|
+
}
|
|
2094
|
+
function recordCallExpression(node, constraintCalls, pushReceivers) {
|
|
2095
|
+
var _node_callee, _node_callee_property, _node_callee_object;
|
|
2096
|
+
var constraintName = resolveConstraintCallName(node);
|
|
2097
|
+
if (constraintName) {
|
|
2098
|
+
constraintCalls.push({
|
|
2099
|
+
node: node,
|
|
2100
|
+
name: constraintName
|
|
2101
|
+
});
|
|
2102
|
+
}
|
|
2103
|
+
if (((_node_callee = node.callee) === null || _node_callee === void 0 ? void 0 : _node_callee.type) === 'MemberExpression' && ((_node_callee_property = node.callee.property) === null || _node_callee_property === void 0 ? void 0 : _node_callee_property.type) === 'Identifier' && node.callee.property.name === 'push' && ((_node_callee_object = node.callee.object) === null || _node_callee_object === void 0 ? void 0 : _node_callee_object.type) === 'Identifier') {
|
|
2104
|
+
pushReceivers.add(node.callee.object.name);
|
|
2105
|
+
}
|
|
2106
|
+
}
|
|
2107
|
+
function recordVariableDeclarator(node, emptyArrayDeclarators) {
|
|
2108
|
+
var _node_id, _node_init;
|
|
2109
|
+
if (((_node_id = node.id) === null || _node_id === void 0 ? void 0 : _node_id.type) === 'Identifier' && ((_node_init = node.init) === null || _node_init === void 0 ? void 0 : _node_init.type) === 'ArrayExpression' && Array.isArray(node.init.elements) && node.init.elements.length === 0) {
|
|
2110
|
+
emptyArrayDeclarators.push({
|
|
2111
|
+
node: node,
|
|
2112
|
+
name: node.id.name
|
|
2113
|
+
});
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
function scanBody(body) {
|
|
2117
|
+
var constraintCalls = [];
|
|
2118
|
+
var emptyArrayDeclarators = [];
|
|
2119
|
+
var pushReceivers = new Set();
|
|
2120
|
+
function visit(node) {
|
|
2121
|
+
if (!node || (typeof node === "undefined" ? "undefined" : _type_of(node)) !== 'object') return;
|
|
2122
|
+
if (node.type === 'CallExpression') {
|
|
2123
|
+
recordCallExpression(node, constraintCalls, pushReceivers);
|
|
2124
|
+
} else if (node.type === 'VariableDeclarator') {
|
|
2125
|
+
recordVariableDeclarator(node, emptyArrayDeclarators);
|
|
2126
|
+
}
|
|
2127
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2128
|
+
try {
|
|
2129
|
+
for(var _iterator = Object.keys(node)[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2130
|
+
var key = _step.value;
|
|
2131
|
+
if (key === 'parent') continue;
|
|
2132
|
+
var value = node[key];
|
|
2133
|
+
if (Array.isArray(value)) {
|
|
2134
|
+
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
2135
|
+
try {
|
|
2136
|
+
for(var _iterator1 = value[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
2137
|
+
var child = _step1.value;
|
|
2138
|
+
if (child && (typeof child === "undefined" ? "undefined" : _type_of(child)) === 'object' && typeof child.type === 'string') visit(child);
|
|
2139
|
+
}
|
|
2140
|
+
} catch (err) {
|
|
2141
|
+
_didIteratorError1 = true;
|
|
2142
|
+
_iteratorError1 = err;
|
|
2143
|
+
} finally{
|
|
2144
|
+
try {
|
|
2145
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
2146
|
+
_iterator1.return();
|
|
2147
|
+
}
|
|
2148
|
+
} finally{
|
|
2149
|
+
if (_didIteratorError1) {
|
|
2150
|
+
throw _iteratorError1;
|
|
2151
|
+
}
|
|
2152
|
+
}
|
|
2153
|
+
}
|
|
2154
|
+
} else if (value && (typeof value === "undefined" ? "undefined" : _type_of(value)) === 'object' && typeof value.type === 'string') {
|
|
2155
|
+
visit(value);
|
|
2156
|
+
}
|
|
2157
|
+
}
|
|
2158
|
+
} catch (err) {
|
|
2159
|
+
_didIteratorError = true;
|
|
2160
|
+
_iteratorError = err;
|
|
2161
|
+
} finally{
|
|
2162
|
+
try {
|
|
2163
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2164
|
+
_iterator.return();
|
|
2165
|
+
}
|
|
2166
|
+
} finally{
|
|
2167
|
+
if (_didIteratorError) {
|
|
2168
|
+
throw _iteratorError;
|
|
2169
|
+
}
|
|
2170
|
+
}
|
|
2171
|
+
}
|
|
2172
|
+
}
|
|
2173
|
+
visit(body);
|
|
2174
|
+
return {
|
|
2175
|
+
constraintCalls: constraintCalls,
|
|
2176
|
+
emptyArrayDeclarators: emptyArrayDeclarators,
|
|
2177
|
+
pushReceivers: pushReceivers
|
|
2178
|
+
};
|
|
2179
|
+
}
|
|
2180
|
+
function reportViolations(scan) {
|
|
2181
|
+
var _iteratorNormalCompletion = true, _didIteratorError = false, _iteratorError = undefined;
|
|
2182
|
+
try {
|
|
2183
|
+
for(var _iterator = scan.constraintCalls[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true){
|
|
2184
|
+
var call = _step.value;
|
|
2185
|
+
context.report({
|
|
2186
|
+
node: call.node,
|
|
2187
|
+
messageId: 'dispatcherUsesInlineConstraint',
|
|
2188
|
+
data: {
|
|
2189
|
+
name: call.name
|
|
2190
|
+
}
|
|
2191
|
+
});
|
|
2192
|
+
}
|
|
2193
|
+
} catch (err) {
|
|
2194
|
+
_didIteratorError = true;
|
|
2195
|
+
_iteratorError = err;
|
|
2196
|
+
} finally{
|
|
2197
|
+
try {
|
|
2198
|
+
if (!_iteratorNormalCompletion && _iterator.return != null) {
|
|
2199
|
+
_iterator.return();
|
|
2200
|
+
}
|
|
2201
|
+
} finally{
|
|
2202
|
+
if (_didIteratorError) {
|
|
2203
|
+
throw _iteratorError;
|
|
2204
|
+
}
|
|
2205
|
+
}
|
|
2206
|
+
}
|
|
2207
|
+
var _iteratorNormalCompletion1 = true, _didIteratorError1 = false, _iteratorError1 = undefined;
|
|
2208
|
+
try {
|
|
2209
|
+
for(var _iterator1 = scan.emptyArrayDeclarators[Symbol.iterator](), _step1; !(_iteratorNormalCompletion1 = (_step1 = _iterator1.next()).done); _iteratorNormalCompletion1 = true){
|
|
2210
|
+
var decl = _step1.value;
|
|
2211
|
+
if (scan.pushReceivers.has(decl.name)) {
|
|
2212
|
+
context.report({
|
|
2213
|
+
node: decl.node,
|
|
2214
|
+
messageId: 'dispatcherBuildsConstraintArray'
|
|
2215
|
+
});
|
|
2216
|
+
}
|
|
2217
|
+
}
|
|
2218
|
+
} catch (err) {
|
|
2219
|
+
_didIteratorError1 = true;
|
|
2220
|
+
_iteratorError1 = err;
|
|
2221
|
+
} finally{
|
|
2222
|
+
try {
|
|
2223
|
+
if (!_iteratorNormalCompletion1 && _iterator1.return != null) {
|
|
2224
|
+
_iterator1.return();
|
|
2225
|
+
}
|
|
2226
|
+
} finally{
|
|
2227
|
+
if (_didIteratorError1) {
|
|
2228
|
+
throw _iteratorError1;
|
|
2229
|
+
}
|
|
2230
|
+
}
|
|
2231
|
+
}
|
|
2232
|
+
}
|
|
2233
|
+
function check(node) {
|
|
2234
|
+
if (!node.body) return;
|
|
2235
|
+
var anchor = getFunctionJsdocAnchor(node);
|
|
2236
|
+
if (!anchor) return;
|
|
2237
|
+
if (!jsdocFlagsDispatcher(anchor)) return;
|
|
2238
|
+
var scan = scanBody(node.body);
|
|
2239
|
+
reportViolations(scan);
|
|
2240
|
+
}
|
|
2241
|
+
return {
|
|
2242
|
+
ImportDeclaration: function ImportDeclaration(node) {
|
|
2243
|
+
return trackImportDeclaration(registry, node);
|
|
2244
|
+
},
|
|
2245
|
+
FunctionDeclaration: function FunctionDeclaration(node) {
|
|
2246
|
+
return check(node);
|
|
2247
|
+
},
|
|
2248
|
+
FunctionExpression: function FunctionExpression(node) {
|
|
2249
|
+
return check(node);
|
|
2250
|
+
},
|
|
2251
|
+
ArrowFunctionExpression: function ArrowFunctionExpression(node) {
|
|
2252
|
+
return check(node);
|
|
2253
|
+
}
|
|
2254
|
+
};
|
|
2255
|
+
}
|
|
2256
|
+
};
|
|
2257
|
+
|
|
2258
|
+
/**
|
|
2259
|
+
* ESLint plugin for `@dereekb/firebase` rules.
|
|
2260
|
+
*
|
|
2261
|
+
* Register as a plugin in your flat ESLint config, then enable individual rules
|
|
2262
|
+
* under the chosen plugin prefix (e.g. 'dereekb-firebase/require-tagged-firestore-constraints').
|
|
2263
|
+
*/ var FIREBASE_ESLINT_PLUGIN = {
|
|
2264
|
+
rules: {
|
|
2265
|
+
'require-tagged-firestore-constraints': FIREBASE_REQUIRE_TAGGED_FIRESTORE_CONSTRAINTS_RULE,
|
|
2266
|
+
'require-dbx-model-firebase-index-query-suffix': FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_QUERY_SUFFIX_RULE,
|
|
2267
|
+
'require-dbx-model-firebase-index-companion-tags': FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_COMPANION_TAGS_RULE,
|
|
2268
|
+
'require-dbx-model-firebase-index-valid-dispatcher': FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_VALID_DISPATCHER_RULE
|
|
2269
|
+
}
|
|
2270
|
+
};
|
|
2271
|
+
/**
|
|
2272
|
+
* camelCase alias of {@link FIREBASE_ESLINT_PLUGIN} matching the conventional ESLint plugin export name.
|
|
2273
|
+
*
|
|
2274
|
+
* @dbxAllowConstantName
|
|
2275
|
+
*/ var firebaseESLintPlugin = FIREBASE_ESLINT_PLUGIN;
|
|
2276
|
+
|
|
2277
|
+
export { DBX_MODEL_FIREBASE_INDEX_MARKER, DEFAULT_CONSTRAINT_FACTORY_NAMES, DEFAULT_INDEX_AFFECTING_CONSTRAINT_NAMES, DEFAULT_PAGINATION_CONSTRAINT_NAMES, FIREBASE_ESLINT_PLUGIN, FIREBASE_MODULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_COMPANION_TAGS_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_QUERY_SUFFIX_RULE, FIREBASE_REQUIRE_DBX_MODEL_FIREBASE_INDEX_VALID_DISPATCHER_RULE, FIREBASE_REQUIRE_TAGGED_FIRESTORE_CONSTRAINTS_RULE, QUERY_SUFFIX, firebaseESLintPlugin };
|