eslint-plugin-rxjs-x 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -0
  3. package/dist/index.cjs +2936 -0
  4. package/dist/index.d.cts +121 -0
  5. package/dist/index.d.mts +121 -0
  6. package/dist/index.d.ts +121 -0
  7. package/dist/index.mjs +2916 -0
  8. package/docs/rules/ban-observables.md +22 -0
  9. package/docs/rules/ban-operators.md +21 -0
  10. package/docs/rules/finnish.md +54 -0
  11. package/docs/rules/just.md +11 -0
  12. package/docs/rules/macro.md +7 -0
  13. package/docs/rules/no-async-subscribe.md +27 -0
  14. package/docs/rules/no-compat.md +11 -0
  15. package/docs/rules/no-connectable.md +7 -0
  16. package/docs/rules/no-create.md +29 -0
  17. package/docs/rules/no-cyclic-action.md +62 -0
  18. package/docs/rules/no-explicit-generics.md +23 -0
  19. package/docs/rules/no-exposed-subjects.md +39 -0
  20. package/docs/rules/no-finnish.md +25 -0
  21. package/docs/rules/no-ignored-error.md +37 -0
  22. package/docs/rules/no-ignored-notifier.md +30 -0
  23. package/docs/rules/no-ignored-observable.md +23 -0
  24. package/docs/rules/no-ignored-replay-buffer.md +28 -0
  25. package/docs/rules/no-ignored-subscribe.md +28 -0
  26. package/docs/rules/no-ignored-subscription.md +33 -0
  27. package/docs/rules/no-ignored-takewhile-value.md +26 -0
  28. package/docs/rules/no-implicit-any-catch.md +78 -0
  29. package/docs/rules/no-index.md +21 -0
  30. package/docs/rules/no-internal.md +21 -0
  31. package/docs/rules/no-nested-subscribe.md +30 -0
  32. package/docs/rules/no-redundant-notify.md +32 -0
  33. package/docs/rules/no-sharereplay.md +22 -0
  34. package/docs/rules/no-subclass.md +7 -0
  35. package/docs/rules/no-subject-unsubscribe.md +11 -0
  36. package/docs/rules/no-subject-value.md +7 -0
  37. package/docs/rules/no-subscribe-handlers.md +39 -0
  38. package/docs/rules/no-tap.md +7 -0
  39. package/docs/rules/no-topromise.md +7 -0
  40. package/docs/rules/no-unbound-methods.md +47 -0
  41. package/docs/rules/no-unsafe-catch.md +50 -0
  42. package/docs/rules/no-unsafe-first.md +16 -0
  43. package/docs/rules/no-unsafe-subject-next.md +30 -0
  44. package/docs/rules/no-unsafe-switchmap.md +38 -0
  45. package/docs/rules/no-unsafe-takeuntil.md +46 -0
  46. package/docs/rules/prefer-observer.md +34 -0
  47. package/docs/rules/suffix-subjects.md +42 -0
  48. package/docs/rules/throw-error.md +49 -0
  49. package/package.json +82 -0
package/dist/index.mjs ADDED
@@ -0,0 +1,2916 @@
1
+ import { ESLintUtils, AST_NODE_TYPES } from '@typescript-eslint/utils';
2
+ import { stripIndent } from 'common-tags';
3
+ import * as tsutils from 'tsutils-etc';
4
+ import { isReferenceType, couldBeType, isUnionType, couldBeFunction, isAny, isUnknown } from 'tsutils-etc';
5
+ import * as ts from 'typescript';
6
+ import { DefinitionType } from '@typescript-eslint/scope-manager';
7
+ import * as tsutils$1 from 'tsutils';
8
+ import decamelize from 'decamelize';
9
+
10
+ const name = "eslint-plugin-rxjs-x";
11
+ const version = "0.0.1";
12
+
13
+ const createRecommendedConfig = (plugin) => ({
14
+ plugins: {
15
+ "rxjs-x": plugin
16
+ },
17
+ rules: {
18
+ "rxjs-x/no-async-subscribe": "error",
19
+ "rxjs-x/no-create": "error",
20
+ "rxjs-x/no-ignored-notifier": "error",
21
+ "rxjs-x/no-ignored-replay-buffer": "error",
22
+ "rxjs-x/no-ignored-takewhile-value": "error",
23
+ "rxjs-x/no-implicit-any-catch": "error",
24
+ "rxjs-x/no-index": "error",
25
+ "rxjs-x/no-internal": "error",
26
+ "rxjs-x/no-nested-subscribe": "error",
27
+ "rxjs-x/no-redundant-notify": "error",
28
+ "rxjs-x/no-sharereplay": ["error", { allowConfig: true }],
29
+ "rxjs-x/no-subject-unsubscribe": "error",
30
+ "rxjs-x/no-unbound-methods": "error",
31
+ "rxjs-x/no-unsafe-subject-next": "error",
32
+ "rxjs-x/no-unsafe-takeuntil": "error"
33
+ }
34
+ });
35
+
36
+ function createRegExpForWords(config) {
37
+ if (!config?.length) {
38
+ return void 0;
39
+ }
40
+ const flags = "i";
41
+ if (typeof config === "string") {
42
+ return new RegExp(config, flags);
43
+ }
44
+ const words = config;
45
+ const joined = words.map((word) => String.raw`(\b|_)${word}(\b|_)`).join("|");
46
+ return new RegExp(`(${joined})`, flags);
47
+ }
48
+ function escapeRegExp(text) {
49
+ return text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
50
+ }
51
+ const ruleCreator = ESLintUtils.RuleCreator(
52
+ (name) => `https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x/blob/main/docs/rules/${name}.md`
53
+ );
54
+
55
+ const defaultOptions$c = [];
56
+ const banObservablesRule = ruleCreator({
57
+ defaultOptions: defaultOptions$c,
58
+ meta: {
59
+ docs: {
60
+ description: "Forbids the use of banned observables."
61
+ },
62
+ messages: {
63
+ forbidden: "RxJS observable is banned: {{name}}{{explanation}}."
64
+ },
65
+ schema: [
66
+ {
67
+ type: "object",
68
+ description: stripIndent`
69
+ An object containing keys that are names of observable factory functions
70
+ and values that are either booleans or strings containing the explanation for the ban.`
71
+ }
72
+ ],
73
+ type: "problem"
74
+ },
75
+ name: "ban-observables",
76
+ create: (context) => {
77
+ const bans = [];
78
+ const [config] = context.options;
79
+ if (!config) {
80
+ return {};
81
+ }
82
+ Object.entries(config).forEach(([key, value]) => {
83
+ if (value !== false) {
84
+ bans.push({
85
+ explanation: typeof value === "string" ? value : "",
86
+ regExp: new RegExp(`^${key}$`)
87
+ });
88
+ }
89
+ });
90
+ function getFailure(name) {
91
+ for (let b = 0, length = bans.length; b < length; ++b) {
92
+ const ban = bans[b];
93
+ if (ban.regExp.test(name)) {
94
+ const explanation = ban.explanation ? `: ${ban.explanation}` : "";
95
+ return {
96
+ messageId: "forbidden",
97
+ data: { name, explanation }
98
+ };
99
+ }
100
+ }
101
+ return void 0;
102
+ }
103
+ return {
104
+ "ImportDeclaration[source.value='rxjs'] > ImportSpecifier": (node) => {
105
+ const identifier = node.imported;
106
+ const name = identifier.type === AST_NODE_TYPES.Identifier ? identifier.name : identifier.value;
107
+ const failure = getFailure(name);
108
+ if (failure) {
109
+ context.report({
110
+ ...failure,
111
+ node: identifier
112
+ });
113
+ }
114
+ }
115
+ };
116
+ }
117
+ });
118
+
119
+ const defaultOptions$b = [];
120
+ const banOperatorsRule = ruleCreator({
121
+ defaultOptions: defaultOptions$b,
122
+ meta: {
123
+ docs: {
124
+ description: "Forbids the use of banned operators."
125
+ },
126
+ messages: {
127
+ forbidden: "RxJS operator is banned: {{name}}{{explanation}}."
128
+ },
129
+ schema: [
130
+ {
131
+ type: "object",
132
+ description: stripIndent`
133
+ An object containing keys that are names of operators
134
+ and values that are either booleans or strings containing the explanation for the ban.`
135
+ }
136
+ ],
137
+ type: "problem"
138
+ },
139
+ name: "ban-operators",
140
+ create: (context) => {
141
+ const bans = [];
142
+ const [config] = context.options;
143
+ if (!config) {
144
+ return {};
145
+ }
146
+ Object.entries(config).forEach(([key, value]) => {
147
+ if (value !== false) {
148
+ bans.push({
149
+ explanation: typeof value === "string" ? value : "",
150
+ regExp: new RegExp(`^${key}$`)
151
+ });
152
+ }
153
+ });
154
+ function getFailure(name) {
155
+ for (let b = 0, length = bans.length; b < length; ++b) {
156
+ const ban = bans[b];
157
+ if (ban.regExp.test(name)) {
158
+ const explanation = ban.explanation ? `: ${ban.explanation}` : "";
159
+ return {
160
+ messageId: "forbidden",
161
+ data: { name, explanation }
162
+ };
163
+ }
164
+ }
165
+ return void 0;
166
+ }
167
+ return {
168
+ [String.raw`ImportDeclaration[source.value=/^rxjs\u002foperators$/] > ImportSpecifier`]: (node) => {
169
+ const identifier = node.imported;
170
+ const name = identifier.type === AST_NODE_TYPES.Identifier ? identifier.name : identifier.value;
171
+ const failure = getFailure(name);
172
+ if (failure) {
173
+ context.report({
174
+ ...failure,
175
+ node: identifier
176
+ });
177
+ }
178
+ }
179
+ };
180
+ }
181
+ });
182
+
183
+ function findParent(node, ...args) {
184
+ const [arg] = args;
185
+ const predicate = typeof arg === "function" ? arg : (type) => !args.includes(type) ? "continue" : "return";
186
+ let parent = node.parent;
187
+ while (parent) {
188
+ switch (predicate(parent.type)) {
189
+ case "break":
190
+ return void 0;
191
+ case "return":
192
+ return parent;
193
+ }
194
+ parent = parent.parent;
195
+ }
196
+ return void 0;
197
+ }
198
+
199
+ function getLoc(node) {
200
+ const sourceFile = node.getSourceFile();
201
+ const start = ts.getLineAndCharacterOfPosition(sourceFile, node.getStart());
202
+ const end = ts.getLineAndCharacterOfPosition(sourceFile, node.getEnd());
203
+ return {
204
+ start: {
205
+ line: start.line + 1,
206
+ column: start.character
207
+ },
208
+ end: {
209
+ line: end.line + 1,
210
+ column: end.character
211
+ }
212
+ };
213
+ }
214
+
215
+ function hasTypeAnnotation(node) {
216
+ return "typeAnnotation" in node && !!node.typeAnnotation;
217
+ }
218
+ function isArrayExpression(node) {
219
+ return node.type === AST_NODE_TYPES.ArrayExpression;
220
+ }
221
+ function isArrayPattern(node) {
222
+ return node.type === AST_NODE_TYPES.ArrayPattern;
223
+ }
224
+ function isArrowFunctionExpression(node) {
225
+ return node.type === AST_NODE_TYPES.ArrowFunctionExpression;
226
+ }
227
+ function isBlockStatement(node) {
228
+ return node.type === AST_NODE_TYPES.BlockStatement;
229
+ }
230
+ function isCallExpression(node) {
231
+ return node.type === AST_NODE_TYPES.CallExpression;
232
+ }
233
+ function isFunctionDeclaration(node) {
234
+ return node.type === AST_NODE_TYPES.FunctionDeclaration;
235
+ }
236
+ function isFunctionExpression(node) {
237
+ return node.type === AST_NODE_TYPES.FunctionExpression;
238
+ }
239
+ function isIdentifier(node) {
240
+ return node.type === AST_NODE_TYPES.Identifier;
241
+ }
242
+ function isLiteral(node) {
243
+ return node.type === AST_NODE_TYPES.Literal;
244
+ }
245
+ function isMemberExpression(node) {
246
+ return node.type === AST_NODE_TYPES.MemberExpression;
247
+ }
248
+ function isObjectExpression(node) {
249
+ return node.type === AST_NODE_TYPES.ObjectExpression;
250
+ }
251
+ function isObjectPattern(node) {
252
+ return node.type === AST_NODE_TYPES.ObjectPattern;
253
+ }
254
+ function isProgram(node) {
255
+ return node.type === AST_NODE_TYPES.Program;
256
+ }
257
+ function isProperty(node) {
258
+ return node.type === AST_NODE_TYPES.Property;
259
+ }
260
+
261
+ function getTypeServices(context) {
262
+ const services = ESLintUtils.getParserServices(context);
263
+ const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = services;
264
+ const typeChecker = program.getTypeChecker();
265
+ const couldBeType = (node, name, qualified) => {
266
+ const type = getType(node);
267
+ return tsutils.couldBeType(
268
+ type,
269
+ name,
270
+ qualified ? { ...qualified, typeChecker } : void 0
271
+ );
272
+ };
273
+ const couldReturnType = (node, name, qualified) => {
274
+ let tsTypeNode;
275
+ const tsNode = esTreeNodeToTSNodeMap.get(node);
276
+ if (ts.isArrowFunction(tsNode) || ts.isFunctionDeclaration(tsNode) || ts.isMethodDeclaration(tsNode) || ts.isFunctionExpression(tsNode)) {
277
+ tsTypeNode = tsNode.type ?? tsNode.body;
278
+ } else if (ts.isCallSignatureDeclaration(tsNode) || ts.isMethodSignature(tsNode)) {
279
+ tsTypeNode = tsNode.type;
280
+ }
281
+ return Boolean(
282
+ tsTypeNode && tsutils.couldBeType(
283
+ typeChecker.getTypeAtLocation(tsTypeNode),
284
+ name,
285
+ qualified ? { ...qualified, typeChecker } : void 0
286
+ )
287
+ );
288
+ };
289
+ const getType = (node) => {
290
+ return getTypeAtLocation(node);
291
+ };
292
+ return {
293
+ couldBeBehaviorSubject: (node) => couldBeType(node, "BehaviorSubject"),
294
+ couldBeError: (node) => couldBeType(node, "Error"),
295
+ couldBeFunction: (node) => {
296
+ if (isArrowFunctionExpression(node) || isFunctionDeclaration(node)) {
297
+ return true;
298
+ }
299
+ return tsutils.couldBeFunction(getType(node));
300
+ },
301
+ couldBeMonoTypeOperatorFunction: (node) => couldBeType(node, "MonoTypeOperatorFunction"),
302
+ couldBeObservable: (node) => couldBeType(node, "Observable"),
303
+ couldBeSubject: (node) => couldBeType(node, "Subject"),
304
+ couldBeSubscription: (node) => couldBeType(node, "Subscription"),
305
+ couldBeType,
306
+ couldReturnObservable: (node) => couldReturnType(node, "Observable"),
307
+ couldReturnType,
308
+ getType,
309
+ isAny: (node) => tsutils.isAny(getType(node)),
310
+ isReferenceType: (node) => tsutils.isReferenceType(getType(node)),
311
+ isUnknown: (node) => tsutils.isUnknown(getType(node)),
312
+ typeChecker
313
+ };
314
+ }
315
+
316
+ function isImport(scope, name, source) {
317
+ const variable = scope.variables.find((variable2) => variable2.name === name);
318
+ if (variable) {
319
+ return variable.defs.some(
320
+ (def) => def.type === DefinitionType.ImportBinding && def.parent.type === AST_NODE_TYPES.ImportDeclaration && (typeof source === "string" ? def.parent.source.value === source : source.test(def.parent.source.value))
321
+ );
322
+ }
323
+ return scope.upper ? isImport(scope.upper, name, source) : false;
324
+ }
325
+
326
+ const defaultOptions$a = [];
327
+ const finnishRule = ruleCreator({
328
+ defaultOptions: defaultOptions$a,
329
+ meta: {
330
+ docs: {
331
+ description: "Enforces the use of Finnish notation.",
332
+ requiresTypeChecking: true
333
+ },
334
+ messages: {
335
+ shouldBeFinnish: "Finnish notation should be used here.",
336
+ shouldNotBeFinnish: "Finnish notation should not be used here."
337
+ },
338
+ schema: [
339
+ {
340
+ properties: {
341
+ functions: { type: "boolean" },
342
+ methods: { type: "boolean" },
343
+ names: { type: "object" },
344
+ parameters: { type: "boolean" },
345
+ properties: { type: "boolean" },
346
+ strict: { type: "boolean" },
347
+ types: { type: "object" },
348
+ variables: { type: "boolean" }
349
+ },
350
+ type: "object"
351
+ }
352
+ ],
353
+ type: "problem"
354
+ },
355
+ name: "finnish",
356
+ create: (context) => {
357
+ const { esTreeNodeToTSNodeMap } = ESLintUtils.getParserServices(context);
358
+ const {
359
+ couldBeObservable,
360
+ couldBeType,
361
+ couldReturnObservable,
362
+ couldReturnType
363
+ } = getTypeServices(context);
364
+ const [config = {}] = context.options;
365
+ const { strict = false } = config;
366
+ const validate = {
367
+ functions: true,
368
+ methods: true,
369
+ parameters: true,
370
+ properties: true,
371
+ variables: true,
372
+ ...config
373
+ };
374
+ const names = [];
375
+ if (config.names) {
376
+ Object.entries(config.names).forEach(
377
+ ([key, validate2]) => {
378
+ names.push({ regExp: new RegExp(key), validate: validate2 });
379
+ }
380
+ );
381
+ } else {
382
+ names.push({
383
+ regExp: /^(canActivate|canActivateChild|canDeactivate|canLoad|intercept|resolve|validate)$/,
384
+ validate: false
385
+ });
386
+ }
387
+ const types = [];
388
+ if (config.types) {
389
+ Object.entries(config.types).forEach(
390
+ ([key, validate2]) => {
391
+ types.push({ regExp: new RegExp(key), validate: validate2 });
392
+ }
393
+ );
394
+ } else {
395
+ types.push({
396
+ regExp: /^EventEmitter$/,
397
+ validate: false
398
+ });
399
+ }
400
+ function checkNode(nameNode, typeNode) {
401
+ const tsNode = esTreeNodeToTSNodeMap.get(nameNode);
402
+ const text = tsNode.getText();
403
+ const hasFinnish = text.endsWith("$");
404
+ if (hasFinnish && !strict) {
405
+ return;
406
+ }
407
+ const shouldBeFinnish = hasFinnish ? () => {
408
+ } : () => {
409
+ context.report({
410
+ loc: getLoc(tsNode),
411
+ messageId: "shouldBeFinnish"
412
+ });
413
+ };
414
+ const shouldNotBeFinnish = hasFinnish ? () => {
415
+ context.report({
416
+ loc: getLoc(tsNode),
417
+ messageId: "shouldNotBeFinnish"
418
+ });
419
+ } : () => {
420
+ };
421
+ if (couldBeObservable(typeNode || nameNode) || couldReturnObservable(typeNode || nameNode)) {
422
+ for (const name of names) {
423
+ const { regExp, validate: validate2 } = name;
424
+ if (regExp.test(text) && !validate2) {
425
+ shouldNotBeFinnish();
426
+ return;
427
+ }
428
+ }
429
+ for (const type of types) {
430
+ const { regExp, validate: validate2 } = type;
431
+ if ((couldBeType(typeNode || nameNode, regExp) || couldReturnType(typeNode || nameNode, regExp)) && !validate2) {
432
+ shouldNotBeFinnish();
433
+ return;
434
+ }
435
+ }
436
+ shouldBeFinnish();
437
+ } else {
438
+ shouldNotBeFinnish();
439
+ }
440
+ }
441
+ return {
442
+ "ArrayPattern > Identifier": (node) => {
443
+ const found = findParent(
444
+ node,
445
+ "ArrowFunctionExpression",
446
+ "FunctionDeclaration",
447
+ "FunctionExpression",
448
+ "VariableDeclarator"
449
+ );
450
+ if (!found) {
451
+ return;
452
+ }
453
+ if (!validate.variables && found.type === AST_NODE_TYPES.VariableDeclarator) {
454
+ return;
455
+ }
456
+ if (!validate.parameters) {
457
+ return;
458
+ }
459
+ checkNode(node);
460
+ },
461
+ "ArrowFunctionExpression > Identifier": (node) => {
462
+ if (validate.parameters) {
463
+ const parent = node.parent;
464
+ if (node !== parent.body) {
465
+ checkNode(node);
466
+ }
467
+ }
468
+ },
469
+ "PropertyDefinition[computed=false]": (node) => {
470
+ if (validate.properties) {
471
+ checkNode(node.key);
472
+ }
473
+ },
474
+ "FunctionDeclaration > Identifier": (node) => {
475
+ const parent = node.parent;
476
+ if (node === parent.id) {
477
+ if (validate.functions) {
478
+ checkNode(node, parent);
479
+ }
480
+ } else {
481
+ if (validate.parameters) {
482
+ checkNode(node);
483
+ }
484
+ }
485
+ },
486
+ "FunctionExpression > Identifier": (node) => {
487
+ const parent = node.parent;
488
+ if (node === parent.id) {
489
+ if (validate.functions) {
490
+ checkNode(node, parent);
491
+ }
492
+ } else {
493
+ if (validate.parameters) {
494
+ checkNode(node);
495
+ }
496
+ }
497
+ },
498
+ "MethodDefinition[kind='get'][computed=false]": (node) => {
499
+ if (validate.properties) {
500
+ checkNode(node.key, node);
501
+ }
502
+ },
503
+ "MethodDefinition[kind='method'][computed=false]": (node) => {
504
+ if (validate.methods) {
505
+ checkNode(node.key, node);
506
+ }
507
+ },
508
+ "MethodDefinition[kind='set'][computed=false]": (node) => {
509
+ if (validate.properties) {
510
+ checkNode(node.key, node);
511
+ }
512
+ },
513
+ "ObjectExpression > Property[computed=false] > Identifier": (node) => {
514
+ if (validate.properties) {
515
+ const parent = node.parent;
516
+ if (node === parent.key) {
517
+ checkNode(node);
518
+ }
519
+ }
520
+ },
521
+ "ObjectPattern > Property > Identifier": (node) => {
522
+ const found = findParent(
523
+ node,
524
+ "ArrowFunctionExpression",
525
+ "FunctionDeclaration",
526
+ "FunctionExpression",
527
+ "VariableDeclarator"
528
+ );
529
+ if (!found) {
530
+ return;
531
+ }
532
+ if (!validate.variables && found.type === AST_NODE_TYPES.VariableDeclarator) {
533
+ return;
534
+ }
535
+ if (!validate.parameters) {
536
+ return;
537
+ }
538
+ const parent = node.parent;
539
+ if (node === parent.value) {
540
+ checkNode(node);
541
+ }
542
+ },
543
+ "TSCallSignatureDeclaration > Identifier": (node) => {
544
+ if (validate.parameters) {
545
+ checkNode(node);
546
+ }
547
+ },
548
+ "TSConstructSignatureDeclaration > Identifier": (node) => {
549
+ if (validate.parameters) {
550
+ checkNode(node);
551
+ }
552
+ },
553
+ "TSMethodSignature[computed=false]": (node) => {
554
+ if (validate.methods) {
555
+ checkNode(node.key, node);
556
+ }
557
+ if (validate.parameters) {
558
+ node.params.forEach((param) => {
559
+ checkNode(param);
560
+ });
561
+ }
562
+ },
563
+ "TSParameterProperty > Identifier": (node) => {
564
+ if (validate.parameters || validate.properties) {
565
+ checkNode(node);
566
+ }
567
+ },
568
+ "TSPropertySignature[computed=false]": (node) => {
569
+ if (validate.properties) {
570
+ checkNode(node.key);
571
+ }
572
+ },
573
+ "VariableDeclarator > Identifier": (node) => {
574
+ const parent = node.parent;
575
+ if (validate.variables && node === parent.id) {
576
+ checkNode(node);
577
+ }
578
+ }
579
+ };
580
+ }
581
+ });
582
+
583
+ const justRule = ruleCreator({
584
+ defaultOptions: [],
585
+ meta: {
586
+ docs: {
587
+ description: "Enforces the use of a `just` alias for `of`."
588
+ },
589
+ fixable: "code",
590
+ messages: {
591
+ forbidden: "Use just alias."
592
+ },
593
+ schema: [],
594
+ type: "problem"
595
+ },
596
+ name: "just",
597
+ create: (context) => {
598
+ return {
599
+ "ImportDeclaration[source.value='rxjs'] > ImportSpecifier[imported.name='of']": (node) => {
600
+ if (node.local.range[0] !== node.imported.range[0] && node.local.range[1] !== node.imported.range[1]) {
601
+ return;
602
+ }
603
+ context.report({
604
+ messageId: "forbidden",
605
+ node,
606
+ fix: (fixer) => fixer.replaceTextRange(node.range, "of as just")
607
+ });
608
+ const [ofImport] = context.sourceCode.getDeclaredVariables(node);
609
+ ofImport.references.forEach((ref) => {
610
+ context.report({
611
+ messageId: "forbidden",
612
+ node: ref.identifier,
613
+ fix: (fixer) => fixer.replaceTextRange(ref.identifier.range, "just")
614
+ });
615
+ });
616
+ }
617
+ };
618
+ }
619
+ });
620
+
621
+ const macroRule = ruleCreator({
622
+ defaultOptions: [],
623
+ meta: {
624
+ docs: {
625
+ description: "Enforces the use of the RxJS Tools Babel macro."
626
+ },
627
+ fixable: "code",
628
+ messages: {
629
+ macro: "Use the RxJS Tools Babel macro."
630
+ },
631
+ schema: [],
632
+ type: "problem"
633
+ },
634
+ name: "macro",
635
+ create: (context) => {
636
+ let hasFailure = false;
637
+ let hasMacroImport = false;
638
+ let program = void 0;
639
+ function fix(fixer) {
640
+ return fixer.insertTextBefore(
641
+ /* eslint-disable-next-line @typescript-eslint/no-non-null-assertion */
642
+ program,
643
+ `import "babel-plugin-rxjs-tools/macro";
644
+ `
645
+ );
646
+ }
647
+ return {
648
+ "CallExpression[callee.property.name=/^(pipe|subscribe)$/]": (node) => {
649
+ if (hasFailure || hasMacroImport) {
650
+ return;
651
+ }
652
+ hasFailure = true;
653
+ context.report({
654
+ fix,
655
+ messageId: "macro",
656
+ node: node.callee
657
+ });
658
+ },
659
+ "ImportDeclaration[source.value='babel-plugin-rxjs-tools/macro']": (_node) => {
660
+ hasMacroImport = true;
661
+ },
662
+ [String.raw`ImportDeclaration[source.value=/^rxjs(\u002f|$)/]`]: (node) => {
663
+ if (hasFailure || hasMacroImport) {
664
+ return;
665
+ }
666
+ hasFailure = true;
667
+ context.report({
668
+ fix,
669
+ messageId: "macro",
670
+ node
671
+ });
672
+ },
673
+ "Program": (node) => {
674
+ program = node;
675
+ }
676
+ };
677
+ }
678
+ });
679
+
680
+ const noAsyncSubscribeRule = ruleCreator({
681
+ defaultOptions: [],
682
+ meta: {
683
+ docs: {
684
+ description: "Forbids passing `async` functions to `subscribe`.",
685
+ recommended: true,
686
+ requiresTypeChecking: true
687
+ },
688
+ messages: {
689
+ forbidden: "Passing async functions to subscribe is forbidden."
690
+ },
691
+ schema: [],
692
+ type: "problem"
693
+ },
694
+ name: "no-async-subscribe",
695
+ create: (context) => {
696
+ const { couldBeObservable } = getTypeServices(context);
697
+ function checkNode(node) {
698
+ const parentNode = node.parent;
699
+ const callee = parentNode.callee;
700
+ if (couldBeObservable(callee.object)) {
701
+ const { loc } = node;
702
+ const asyncLoc = {
703
+ ...loc,
704
+ end: {
705
+ ...loc.start,
706
+ column: loc.start.column + 5
707
+ }
708
+ };
709
+ context.report({
710
+ messageId: "forbidden",
711
+ loc: asyncLoc
712
+ });
713
+ }
714
+ }
715
+ return {
716
+ "CallExpression[callee.property.name='subscribe'] > FunctionExpression[async=true]": checkNode,
717
+ "CallExpression[callee.property.name='subscribe'] > ArrowFunctionExpression[async=true]": checkNode
718
+ };
719
+ }
720
+ });
721
+
722
+ const noCompatRule = ruleCreator({
723
+ defaultOptions: [],
724
+ meta: {
725
+ docs: {
726
+ description: "Forbids importation from locations that depend upon `rxjs-compat`."
727
+ },
728
+ messages: {
729
+ forbidden: "'rxjs-compat'-dependent import locations are forbidden."
730
+ },
731
+ schema: [],
732
+ type: "problem"
733
+ },
734
+ name: "no-compat",
735
+ create: (context) => {
736
+ return {
737
+ [String.raw`ImportDeclaration Literal[value=/^rxjs\u002f/]:not(Literal[value=/^rxjs\u002f(ajax|fetch|operators|testing|webSocket)/])`]: (node) => {
738
+ context.report({
739
+ messageId: "forbidden",
740
+ node
741
+ });
742
+ }
743
+ };
744
+ }
745
+ });
746
+
747
+ const noConnectableRule = ruleCreator({
748
+ defaultOptions: [],
749
+ meta: {
750
+ docs: {
751
+ description: "Forbids operators that return connectable observables.",
752
+ requiresTypeChecking: true
753
+ },
754
+ messages: {
755
+ forbidden: "Connectable observables are forbidden."
756
+ },
757
+ schema: [],
758
+ type: "problem"
759
+ },
760
+ name: "no-connectable",
761
+ create: (context) => {
762
+ const { couldBeFunction } = getTypeServices(context);
763
+ return {
764
+ "CallExpression[callee.name='multicast']": (node) => {
765
+ if (node.arguments.length === 1) {
766
+ context.report({
767
+ messageId: "forbidden",
768
+ node: node.callee
769
+ });
770
+ }
771
+ },
772
+ "CallExpression[callee.name=/^(publish|publishBehavior|publishLast|publishReplay)$/]": (node) => {
773
+ if (!node.arguments.some((arg) => couldBeFunction(arg))) {
774
+ context.report({
775
+ messageId: "forbidden",
776
+ node: node.callee
777
+ });
778
+ }
779
+ }
780
+ };
781
+ }
782
+ });
783
+
784
+ const noCreateRule = ruleCreator({
785
+ defaultOptions: [],
786
+ meta: {
787
+ docs: {
788
+ description: "Forbids the calling of `Observable.create`.",
789
+ recommended: true,
790
+ requiresTypeChecking: true
791
+ },
792
+ messages: {
793
+ forbidden: "Observable.create is forbidden; use new Observable."
794
+ },
795
+ schema: [],
796
+ type: "problem"
797
+ },
798
+ name: "no-create",
799
+ create: (context) => {
800
+ const { couldBeObservable } = getTypeServices(context);
801
+ return {
802
+ "CallExpression > MemberExpression[object.name='Observable'] > Identifier[name='create']": (node) => {
803
+ const memberExpression = node.parent;
804
+ if (couldBeObservable(memberExpression.object)) {
805
+ context.report({
806
+ messageId: "forbidden",
807
+ node
808
+ });
809
+ }
810
+ }
811
+ };
812
+ }
813
+ });
814
+
815
+ const defaultObservable = String.raw`[Aa]ction(s|s\$|\$)$`;
816
+
817
+ function isTypeReference(type) {
818
+ return Boolean(type.target);
819
+ }
820
+ const defaultOptions$9 = [];
821
+ const noCyclicActionRule = ruleCreator({
822
+ defaultOptions: defaultOptions$9,
823
+ meta: {
824
+ docs: {
825
+ description: "Forbids effects and epics that re-emit filtered actions.",
826
+ requiresTypeChecking: true
827
+ },
828
+ messages: {
829
+ forbidden: "Effects and epics that re-emit filtered actions are forbidden."
830
+ },
831
+ schema: [
832
+ {
833
+ properties: {
834
+ observable: { type: "string" }
835
+ },
836
+ type: "object",
837
+ description: stripIndent`
838
+ An optional object with an optional \`observable\` property.
839
+ The property can be specified as a regular expression string and is used to identify the action observables from which effects and epics are composed.`
840
+ }
841
+ ],
842
+ type: "problem"
843
+ },
844
+ name: "no-cyclic-action",
845
+ create: (context) => {
846
+ const [config = {}] = context.options;
847
+ const { observable = defaultObservable } = config;
848
+ const observableRegExp = new RegExp(observable);
849
+ const { getType, typeChecker } = getTypeServices(context);
850
+ function checkNode(pipeCallExpression) {
851
+ const operatorCallExpression = pipeCallExpression.arguments.find(
852
+ (arg) => isCallExpression(arg) && isIdentifier(arg.callee) && arg.callee.name === "ofType"
853
+ );
854
+ if (!operatorCallExpression) {
855
+ return;
856
+ }
857
+ const operatorType = getType(operatorCallExpression);
858
+ const [signature] = typeChecker.getSignaturesOfType(
859
+ operatorType,
860
+ ts.SignatureKind.Call
861
+ );
862
+ if (!signature) {
863
+ return;
864
+ }
865
+ const operatorReturnType = typeChecker.getReturnTypeOfSignature(signature);
866
+ if (!isTypeReference(operatorReturnType)) {
867
+ return;
868
+ }
869
+ const [operatorElementType] = typeChecker.getTypeArguments(operatorReturnType);
870
+ if (!operatorElementType) {
871
+ return;
872
+ }
873
+ const pipeType = getType(pipeCallExpression);
874
+ if (!isTypeReference(pipeType)) {
875
+ return;
876
+ }
877
+ const [pipeElementType] = typeChecker.getTypeArguments(pipeType);
878
+ if (!pipeElementType) {
879
+ return;
880
+ }
881
+ const operatorActionTypes = getActionTypes(operatorElementType);
882
+ const pipeActionTypes = getActionTypes(pipeElementType);
883
+ for (const actionType of operatorActionTypes) {
884
+ if (pipeActionTypes.includes(actionType)) {
885
+ context.report({
886
+ messageId: "forbidden",
887
+ node: pipeCallExpression.callee
888
+ });
889
+ return;
890
+ }
891
+ }
892
+ }
893
+ function getActionTypes(type) {
894
+ if (type.isUnion()) {
895
+ const memberActionTypes = [];
896
+ for (const memberType of type.types) {
897
+ memberActionTypes.push(...getActionTypes(memberType));
898
+ }
899
+ return memberActionTypes;
900
+ }
901
+ const symbol = typeChecker.getPropertyOfType(type, "type");
902
+ if (!symbol?.valueDeclaration) {
903
+ return [];
904
+ }
905
+ const actionType = typeChecker.getTypeOfSymbolAtLocation(
906
+ symbol,
907
+ symbol.valueDeclaration
908
+ );
909
+ return [typeChecker.typeToString(actionType)];
910
+ }
911
+ return {
912
+ [`CallExpression[callee.property.name='pipe'][callee.object.name=${observableRegExp}]`]: checkNode,
913
+ [`CallExpression[callee.property.name='pipe'][callee.object.property.name=${observableRegExp}]`]: checkNode
914
+ };
915
+ }
916
+ });
917
+
918
+ const noExplicitGenericsRule = ruleCreator({
919
+ defaultOptions: [],
920
+ meta: {
921
+ docs: {
922
+ description: "Forbids explicit generic type arguments."
923
+ },
924
+ messages: {
925
+ forbidden: "Explicit generic type arguments are forbidden."
926
+ },
927
+ schema: [],
928
+ type: "problem"
929
+ },
930
+ name: "no-explicit-generics",
931
+ create: (context) => {
932
+ function report(node) {
933
+ context.report({
934
+ messageId: "forbidden",
935
+ node
936
+ });
937
+ }
938
+ function checkBehaviorSubjects(node) {
939
+ const parent = node.parent;
940
+ const {
941
+ arguments: [value]
942
+ } = parent;
943
+ if (isArrayExpression(value) || isObjectExpression(value)) {
944
+ return;
945
+ }
946
+ report(node);
947
+ }
948
+ function checkNotifications(node) {
949
+ const parent = node.parent;
950
+ const {
951
+ arguments: [, value]
952
+ } = parent;
953
+ if (isArrayExpression(value) || isObjectExpression(value)) {
954
+ return;
955
+ }
956
+ report(node);
957
+ }
958
+ return {
959
+ "CallExpression[callee.property.name='pipe'] > CallExpression[typeArguments.params.length > 0] > Identifier": report,
960
+ "NewExpression[typeArguments.params.length > 0] > Identifier[name='BehaviorSubject']": checkBehaviorSubjects,
961
+ "CallExpression[typeArguments.params.length > 0] > Identifier[name=/^(from|of)$/]": report,
962
+ "NewExpression[typeArguments.params.length > 0][arguments.0.value='N'] > Identifier[name='Notification']": checkNotifications
963
+ };
964
+ }
965
+ });
966
+
967
+ const defaultAllowedTypesRegExp = /^EventEmitter$/;
968
+ const defaultOptions$8 = [];
969
+ const noExposedSubjectsRule = ruleCreator({
970
+ defaultOptions: defaultOptions$8,
971
+ meta: {
972
+ docs: {
973
+ description: "Forbids exposed (i.e. non-private) subjects.",
974
+ requiresTypeChecking: true
975
+ },
976
+ messages: {
977
+ forbidden: "Subject '{{subject}}' must be private.",
978
+ forbiddenAllowProtected: "Subject '{{subject}}' must be private or protected."
979
+ },
980
+ schema: [
981
+ {
982
+ properties: {
983
+ allowProtected: { type: "boolean" }
984
+ },
985
+ type: "object"
986
+ }
987
+ ],
988
+ type: "problem"
989
+ },
990
+ name: "no-exposed-subjects",
991
+ create: (context) => {
992
+ const [config = {}] = context.options;
993
+ const { allowProtected = false } = config;
994
+ const { couldBeSubject, couldBeType } = getTypeServices(context);
995
+ const messageId = allowProtected ? "forbiddenAllowProtected" : "forbidden";
996
+ const accessibilityRexExp = allowProtected ? /^(private|protected)$/ : /^private$/;
997
+ function isSubject(node) {
998
+ return couldBeSubject(node) && !couldBeType(node, defaultAllowedTypesRegExp);
999
+ }
1000
+ return {
1001
+ [`PropertyDefinition[accessibility!=${accessibilityRexExp}]`]: (node) => {
1002
+ if (isSubject(node)) {
1003
+ const { key } = node;
1004
+ if (isIdentifier(key)) {
1005
+ context.report({
1006
+ messageId,
1007
+ node: key,
1008
+ data: {
1009
+ subject: key.name
1010
+ }
1011
+ });
1012
+ }
1013
+ }
1014
+ },
1015
+ [`MethodDefinition[kind='constructor'] > FunctionExpression > TSParameterProperty[accessibility!=${accessibilityRexExp}] > Identifier`]: (node) => {
1016
+ if (isSubject(node)) {
1017
+ const { loc } = node;
1018
+ context.report({
1019
+ messageId,
1020
+ loc: {
1021
+ ...loc,
1022
+ end: {
1023
+ ...loc.start,
1024
+ column: loc.start.column + node.name.length
1025
+ }
1026
+ },
1027
+ data: {
1028
+ subject: node.name
1029
+ }
1030
+ });
1031
+ }
1032
+ },
1033
+ [`MethodDefinition[accessibility!=${accessibilityRexExp}][kind=/^(get|set)$/]`]: (node) => {
1034
+ if (isSubject(node)) {
1035
+ const key = node.key;
1036
+ context.report({
1037
+ messageId,
1038
+ node: key,
1039
+ data: {
1040
+ subject: key.name
1041
+ }
1042
+ });
1043
+ }
1044
+ },
1045
+ [`MethodDefinition[accessibility!=${accessibilityRexExp}][kind='method']`]: (node) => {
1046
+ const functionExpression = node.value;
1047
+ const returnType = functionExpression.returnType;
1048
+ if (!returnType) {
1049
+ return;
1050
+ }
1051
+ const typeAnnotation = returnType.typeAnnotation;
1052
+ if (!typeAnnotation) {
1053
+ return;
1054
+ }
1055
+ if (isSubject(typeAnnotation)) {
1056
+ const key = node.key;
1057
+ context.report({
1058
+ messageId,
1059
+ node: key,
1060
+ data: {
1061
+ subject: key.name
1062
+ }
1063
+ });
1064
+ }
1065
+ }
1066
+ };
1067
+ }
1068
+ });
1069
+
1070
+ const noFinnishRule = ruleCreator({
1071
+ defaultOptions: [],
1072
+ meta: {
1073
+ docs: {
1074
+ description: "Forbids the use of Finnish notation.",
1075
+ requiresTypeChecking: true
1076
+ },
1077
+ messages: {
1078
+ forbidden: "Finnish notation is forbidden."
1079
+ },
1080
+ schema: [],
1081
+ type: "problem"
1082
+ },
1083
+ name: "no-finnish",
1084
+ create: (context) => {
1085
+ const { esTreeNodeToTSNodeMap } = ESLintUtils.getParserServices(context);
1086
+ const { couldBeObservable, couldReturnObservable } = getTypeServices(context);
1087
+ function checkNode(nameNode, typeNode) {
1088
+ if (couldBeObservable(typeNode || nameNode) || couldReturnObservable(typeNode || nameNode)) {
1089
+ const tsNode = esTreeNodeToTSNodeMap.get(nameNode);
1090
+ if (/[$]+$/.test(tsNode.getText())) {
1091
+ context.report({
1092
+ loc: getLoc(tsNode),
1093
+ messageId: "forbidden"
1094
+ });
1095
+ }
1096
+ }
1097
+ }
1098
+ return {
1099
+ "ArrayPattern > Identifier[name=/[$]+$/]": (node) => {
1100
+ checkNode(node);
1101
+ },
1102
+ "ArrowFunctionExpression > Identifier[name=/[$]+$/]": (node) => {
1103
+ const parent = node.parent;
1104
+ if (node !== parent.body) {
1105
+ checkNode(node);
1106
+ }
1107
+ },
1108
+ "PropertyDefinition[key.name=/[$]+$/] > Identifier": (node) => {
1109
+ checkNode(node, node.parent);
1110
+ },
1111
+ "FunctionDeclaration > Identifier[name=/[$]+$/]": (node) => {
1112
+ const parent = node.parent;
1113
+ if (node === parent.id) {
1114
+ checkNode(node, parent);
1115
+ } else {
1116
+ checkNode(node);
1117
+ }
1118
+ },
1119
+ "FunctionExpression > Identifier[name=/[$]+$/]": (node) => {
1120
+ const parent = node.parent;
1121
+ if (node === parent.id) {
1122
+ checkNode(node, parent);
1123
+ } else {
1124
+ checkNode(node);
1125
+ }
1126
+ },
1127
+ "MethodDefinition[key.name=/[$]+$/]": (node) => {
1128
+ checkNode(node.key, node);
1129
+ },
1130
+ "ObjectExpression > Property[computed=false][key.name=/[$]+$/]": (node) => {
1131
+ checkNode(node.key);
1132
+ },
1133
+ "ObjectPattern > Property[value.name=/[$]+$/]": (node) => {
1134
+ checkNode(node.value);
1135
+ },
1136
+ "TSCallSignatureDeclaration > Identifier[name=/[$]+$/]": (node) => {
1137
+ checkNode(node);
1138
+ },
1139
+ "TSConstructSignatureDeclaration > Identifier[name=/[$]+$/]": (node) => {
1140
+ checkNode(node);
1141
+ },
1142
+ "TSParameterProperty > Identifier[name=/[$]+$/]": (node) => {
1143
+ checkNode(node);
1144
+ },
1145
+ "TSPropertySignature > Identifier[name=/[$]+$/]": (node) => {
1146
+ checkNode(node, node.parent);
1147
+ },
1148
+ "TSMethodSignature > Identifier[name=/[$]+$/]": (node) => {
1149
+ const parent = node.parent;
1150
+ if (node === parent.key) {
1151
+ checkNode(node, parent);
1152
+ } else {
1153
+ checkNode(node);
1154
+ }
1155
+ },
1156
+ "VariableDeclarator[id.name=/[$]+$/]": (node) => {
1157
+ checkNode(node.id, node.init ?? node);
1158
+ }
1159
+ };
1160
+ }
1161
+ });
1162
+
1163
+ const noIgnoredErrorRule = ruleCreator({
1164
+ defaultOptions: [],
1165
+ meta: {
1166
+ docs: {
1167
+ description: "Forbids the calling of `subscribe` without specifying an error handler.",
1168
+ requiresTypeChecking: true
1169
+ },
1170
+ messages: {
1171
+ forbidden: "Calling subscribe without an error handler is forbidden."
1172
+ },
1173
+ schema: [],
1174
+ type: "problem"
1175
+ },
1176
+ name: "no-ignored-error",
1177
+ create: (context) => {
1178
+ const { couldBeObservable, couldBeFunction } = getTypeServices(context);
1179
+ return {
1180
+ "CallExpression[arguments.length > 0] > MemberExpression > Identifier[name='subscribe']": (node) => {
1181
+ const memberExpression = node.parent;
1182
+ const callExpression = memberExpression.parent;
1183
+ if (callExpression.arguments.length < 2 && couldBeObservable(memberExpression.object) && couldBeFunction(callExpression.arguments[0])) {
1184
+ context.report({
1185
+ messageId: "forbidden",
1186
+ node
1187
+ });
1188
+ }
1189
+ }
1190
+ };
1191
+ }
1192
+ });
1193
+
1194
+ const noIgnoredNotifierRule = ruleCreator({
1195
+ defaultOptions: [],
1196
+ meta: {
1197
+ docs: {
1198
+ description: "Forbids observables not composed from the `repeatWhen` or `retryWhen` notifier.",
1199
+ recommended: true,
1200
+ requiresTypeChecking: true
1201
+ },
1202
+ messages: {
1203
+ forbidden: "Ignoring the notifier is forbidden."
1204
+ },
1205
+ schema: [],
1206
+ type: "problem"
1207
+ },
1208
+ name: "no-ignored-notifier",
1209
+ create: (context) => {
1210
+ const { couldBeMonoTypeOperatorFunction } = getTypeServices(context);
1211
+ const entries = [];
1212
+ function getEntry() {
1213
+ const { length, [length - 1]: entry } = entries;
1214
+ return entry;
1215
+ }
1216
+ return {
1217
+ "CallExpression[callee.name=/^(repeatWhen|retryWhen)$/]": (node) => {
1218
+ if (couldBeMonoTypeOperatorFunction(node)) {
1219
+ const [arg] = node.arguments;
1220
+ if (isArrowFunctionExpression(arg) || isFunctionExpression(arg)) {
1221
+ const [param] = arg.params;
1222
+ if (param) {
1223
+ entries.push({
1224
+ node,
1225
+ param,
1226
+ sightings: 0
1227
+ });
1228
+ } else {
1229
+ context.report({
1230
+ messageId: "forbidden",
1231
+ node: node.callee
1232
+ });
1233
+ }
1234
+ }
1235
+ }
1236
+ },
1237
+ "CallExpression[callee.name=/^(repeatWhen|retryWhen)$/]:exit": (node) => {
1238
+ const entry = getEntry();
1239
+ if (!entry) {
1240
+ return;
1241
+ }
1242
+ if (entry.node === node) {
1243
+ if (entry.sightings < 2) {
1244
+ context.report({
1245
+ messageId: "forbidden",
1246
+ node: node.callee
1247
+ });
1248
+ }
1249
+ entries.pop();
1250
+ }
1251
+ },
1252
+ "Identifier": (node) => {
1253
+ const entry = getEntry();
1254
+ if (!entry) {
1255
+ return;
1256
+ }
1257
+ if (node.name === entry.param.name) {
1258
+ ++entry.sightings;
1259
+ }
1260
+ }
1261
+ };
1262
+ }
1263
+ });
1264
+
1265
+ const noIgnoredObservableRule = ruleCreator({
1266
+ defaultOptions: [],
1267
+ meta: {
1268
+ docs: {
1269
+ description: "Forbids the ignoring of observables returned by functions.",
1270
+ requiresTypeChecking: true
1271
+ },
1272
+ messages: {
1273
+ forbidden: "Ignoring a returned Observable is forbidden."
1274
+ },
1275
+ schema: [],
1276
+ type: "problem"
1277
+ },
1278
+ name: "no-ignored-observable",
1279
+ create: (context) => {
1280
+ const { couldBeObservable } = getTypeServices(context);
1281
+ return {
1282
+ "ExpressionStatement > CallExpression": (node) => {
1283
+ if (couldBeObservable(node)) {
1284
+ context.report({
1285
+ messageId: "forbidden",
1286
+ node
1287
+ });
1288
+ }
1289
+ }
1290
+ };
1291
+ }
1292
+ });
1293
+
1294
+ const noIgnoredReplayBufferRule = ruleCreator({
1295
+ defaultOptions: [],
1296
+ meta: {
1297
+ docs: {
1298
+ description: "Forbids using `ReplaySubject`, `publishReplay` or `shareReplay` without specifying the buffer size.",
1299
+ recommended: true
1300
+ },
1301
+ messages: {
1302
+ forbidden: "Ignoring the buffer size is forbidden."
1303
+ },
1304
+ schema: [],
1305
+ type: "problem"
1306
+ },
1307
+ name: "no-ignored-replay-buffer",
1308
+ create: (context) => {
1309
+ function checkNode(node, { arguments: args }) {
1310
+ if (!args || args.length === 0) {
1311
+ context.report({
1312
+ messageId: "forbidden",
1313
+ node
1314
+ });
1315
+ }
1316
+ }
1317
+ return {
1318
+ "NewExpression > Identifier[name='ReplaySubject']": (node) => {
1319
+ const newExpression = node.parent;
1320
+ checkNode(node, newExpression);
1321
+ },
1322
+ "NewExpression > MemberExpression > Identifier[name='ReplaySubject']": (node) => {
1323
+ const memberExpression = node.parent;
1324
+ const newExpression = memberExpression.parent;
1325
+ checkNode(node, newExpression);
1326
+ },
1327
+ "CallExpression > Identifier[name=/^(publishReplay|shareReplay)$/]": (node) => {
1328
+ const callExpression = node.parent;
1329
+ checkNode(node, callExpression);
1330
+ }
1331
+ };
1332
+ }
1333
+ });
1334
+
1335
+ const noIgnoredSubscribeRule = ruleCreator({
1336
+ defaultOptions: [],
1337
+ meta: {
1338
+ docs: {
1339
+ description: "Forbids the calling of `subscribe` without specifying arguments.",
1340
+ requiresTypeChecking: true
1341
+ },
1342
+ messages: {
1343
+ forbidden: "Calling subscribe without arguments is forbidden."
1344
+ },
1345
+ schema: [],
1346
+ type: "problem"
1347
+ },
1348
+ name: "no-ignored-subscribe",
1349
+ create: (context) => {
1350
+ const { couldBeObservable, couldBeType } = getTypeServices(context);
1351
+ return {
1352
+ "CallExpression[arguments.length = 0][callee.property.name='subscribe']": (node) => {
1353
+ const callee = node.callee;
1354
+ if (couldBeObservable(callee.object) || couldBeType(callee.object, "Subscribable")) {
1355
+ context.report({
1356
+ messageId: "forbidden",
1357
+ node: callee.property
1358
+ });
1359
+ }
1360
+ }
1361
+ };
1362
+ }
1363
+ });
1364
+
1365
+ const noIgnoredSubscriptionRule = ruleCreator({
1366
+ defaultOptions: [],
1367
+ meta: {
1368
+ docs: {
1369
+ description: "Forbids ignoring the subscription returned by `subscribe`.",
1370
+ requiresTypeChecking: true
1371
+ },
1372
+ messages: {
1373
+ forbidden: "Ignoring returned subscriptions is forbidden."
1374
+ },
1375
+ schema: [],
1376
+ type: "problem"
1377
+ },
1378
+ name: "no-ignored-subscription",
1379
+ create: (context) => {
1380
+ const { couldBeObservable, couldBeType } = getTypeServices(context);
1381
+ return {
1382
+ "ExpressionStatement > CallExpression > MemberExpression[property.name='subscribe']": (node) => {
1383
+ if (couldBeObservable(node.object)) {
1384
+ const callExpression = node.parent;
1385
+ if (callExpression.arguments.length === 1 && couldBeType(callExpression.arguments[0], "Subscriber")) {
1386
+ return;
1387
+ }
1388
+ context.report({
1389
+ messageId: "forbidden",
1390
+ node: node.property
1391
+ });
1392
+ }
1393
+ }
1394
+ };
1395
+ }
1396
+ });
1397
+
1398
+ const noIgnoredTakewhileValueRule = ruleCreator({
1399
+ defaultOptions: [],
1400
+ meta: {
1401
+ docs: {
1402
+ description: "Forbids ignoring the value within `takeWhile`.",
1403
+ recommended: true
1404
+ },
1405
+ messages: {
1406
+ forbidden: "Ignoring the value within takeWhile is forbidden."
1407
+ },
1408
+ schema: [],
1409
+ type: "problem"
1410
+ },
1411
+ name: "no-ignored-takewhile-value",
1412
+ create: (context) => {
1413
+ function checkNode(expression) {
1414
+ const scope = context.sourceCode.getScope(expression);
1415
+ if (!isImport(scope, "takeWhile", /^rxjs\/?/)) {
1416
+ return;
1417
+ }
1418
+ let ignored = true;
1419
+ const [param] = expression.params;
1420
+ if (param) {
1421
+ if (isIdentifier(param)) {
1422
+ const variable = scope.variables.find(
1423
+ ({ name }) => name === param.name
1424
+ );
1425
+ if (variable && variable.references.length > 0) {
1426
+ ignored = false;
1427
+ }
1428
+ } else if (isArrayPattern(param)) {
1429
+ ignored = false;
1430
+ } else if (isObjectPattern(param)) {
1431
+ ignored = false;
1432
+ }
1433
+ }
1434
+ if (ignored) {
1435
+ context.report({
1436
+ messageId: "forbidden",
1437
+ node: expression
1438
+ });
1439
+ }
1440
+ }
1441
+ return {
1442
+ "CallExpression[callee.name='takeWhile'] > ArrowFunctionExpression": (node) => {
1443
+ checkNode(node);
1444
+ },
1445
+ "CallExpression[callee.name='takeWhile'] > FunctionExpression": (node) => {
1446
+ checkNode(node);
1447
+ }
1448
+ };
1449
+ }
1450
+ });
1451
+
1452
+ function isParenthesised(sourceCode, node) {
1453
+ const before = sourceCode.getTokenBefore(node);
1454
+ const after = sourceCode.getTokenAfter(node);
1455
+ return before && after && before.value === "(" && before.range[1] <= node.range[0] && after.value === ")" && after.range[0] >= node.range[1];
1456
+ }
1457
+ const defaultOptions$7 = [];
1458
+ const noImplicitAnyCatchRule = ruleCreator({
1459
+ defaultOptions: defaultOptions$7,
1460
+ meta: {
1461
+ docs: {
1462
+ description: "Forbids implicit `any` error parameters in `catchError` operators.",
1463
+ recommended: true,
1464
+ requiresTypeChecking: true
1465
+ },
1466
+ fixable: "code",
1467
+ hasSuggestions: true,
1468
+ messages: {
1469
+ explicitAny: "Explicit `any` in `catchError`.",
1470
+ implicitAny: "Implicit `any` in `catchError`.",
1471
+ narrowed: "Error type must be `unknown` or `any`.",
1472
+ suggestExplicitUnknown: "Use `unknown` instead, this will force you to explicitly and safely assert the type is correct."
1473
+ },
1474
+ schema: [
1475
+ {
1476
+ additionalProperties: false,
1477
+ properties: {
1478
+ allowExplicitAny: {
1479
+ type: "boolean"
1480
+ }
1481
+ },
1482
+ type: "object"
1483
+ }
1484
+ ],
1485
+ type: "suggestion"
1486
+ },
1487
+ name: "no-implicit-any-catch",
1488
+ create: (context) => {
1489
+ const [config = {}] = context.options;
1490
+ const { allowExplicitAny = false } = config;
1491
+ const { couldBeObservable } = getTypeServices(context);
1492
+ const sourceCode = context.sourceCode;
1493
+ function checkCallback(callback) {
1494
+ if (isArrowFunctionExpression(callback) || isFunctionExpression(callback)) {
1495
+ const [param] = callback.params;
1496
+ if (!param) {
1497
+ return;
1498
+ }
1499
+ if (hasTypeAnnotation(param)) {
1500
+ const { typeAnnotation } = param;
1501
+ const {
1502
+ typeAnnotation: { type }
1503
+ } = typeAnnotation;
1504
+ if (type === AST_NODE_TYPES.TSAnyKeyword) {
1505
+ let fix = function(fixer) {
1506
+ return fixer.replaceText(typeAnnotation, ": unknown");
1507
+ };
1508
+ if (allowExplicitAny) {
1509
+ return;
1510
+ }
1511
+ context.report({
1512
+ fix,
1513
+ messageId: "explicitAny",
1514
+ node: param,
1515
+ suggest: [
1516
+ {
1517
+ messageId: "suggestExplicitUnknown",
1518
+ fix
1519
+ }
1520
+ ]
1521
+ });
1522
+ } else if (type !== AST_NODE_TYPES.TSUnknownKeyword) {
1523
+ let fix = function(fixer) {
1524
+ return fixer.replaceText(typeAnnotation, ": unknown");
1525
+ };
1526
+ context.report({
1527
+ messageId: "narrowed",
1528
+ node: param,
1529
+ suggest: [
1530
+ {
1531
+ messageId: "suggestExplicitUnknown",
1532
+ fix
1533
+ }
1534
+ ]
1535
+ });
1536
+ }
1537
+ } else {
1538
+ let fix = function(fixer) {
1539
+ if (isParenthesised(sourceCode, param)) {
1540
+ return fixer.insertTextAfter(param, ": unknown");
1541
+ }
1542
+ return [
1543
+ fixer.insertTextBefore(param, "("),
1544
+ fixer.insertTextAfter(param, ": unknown)")
1545
+ ];
1546
+ };
1547
+ context.report({
1548
+ fix,
1549
+ messageId: "implicitAny",
1550
+ node: param,
1551
+ suggest: [
1552
+ {
1553
+ messageId: "suggestExplicitUnknown",
1554
+ fix
1555
+ }
1556
+ ]
1557
+ });
1558
+ }
1559
+ }
1560
+ }
1561
+ return {
1562
+ "CallExpression[callee.name='catchError']": (node) => {
1563
+ const [callback] = node.arguments;
1564
+ if (!callback) {
1565
+ return;
1566
+ }
1567
+ checkCallback(callback);
1568
+ },
1569
+ "CallExpression[callee.property.name='subscribe'],CallExpression[callee.name='tap']": (node) => {
1570
+ const { callee } = node;
1571
+ if (isMemberExpression(callee) && !couldBeObservable(callee.object)) {
1572
+ return;
1573
+ }
1574
+ const [observer, callback] = node.arguments;
1575
+ if (callback) {
1576
+ checkCallback(callback);
1577
+ } else if (observer && isObjectExpression(observer)) {
1578
+ const errorProperty = observer.properties.find(
1579
+ (property) => isProperty(property) && isIdentifier(property.key) && property.key.name === "error"
1580
+ );
1581
+ if (errorProperty) {
1582
+ checkCallback(errorProperty.value);
1583
+ }
1584
+ }
1585
+ }
1586
+ };
1587
+ }
1588
+ });
1589
+
1590
+ const noIndexRule = ruleCreator({
1591
+ defaultOptions: [],
1592
+ meta: {
1593
+ docs: {
1594
+ description: "Forbids the importation from index modules.",
1595
+ recommended: true
1596
+ },
1597
+ messages: {
1598
+ forbidden: "RxJS imports from index modules are forbidden."
1599
+ },
1600
+ schema: [],
1601
+ type: "problem"
1602
+ },
1603
+ name: "no-index",
1604
+ create: (context) => {
1605
+ return {
1606
+ [String.raw`ImportDeclaration Literal[value=/^rxjs(?:\u002f\w+)?\u002findex/]`]: (node) => {
1607
+ context.report({
1608
+ messageId: "forbidden",
1609
+ node
1610
+ });
1611
+ }
1612
+ };
1613
+ }
1614
+ });
1615
+
1616
+ const noInternalRule = ruleCreator({
1617
+ defaultOptions: [],
1618
+ meta: {
1619
+ docs: {
1620
+ description: "Forbids the importation of internals.",
1621
+ recommended: true
1622
+ },
1623
+ fixable: "code",
1624
+ hasSuggestions: true,
1625
+ messages: {
1626
+ forbidden: "RxJS imports from internal are forbidden.",
1627
+ suggest: "Import from a non-internal location."
1628
+ },
1629
+ schema: [],
1630
+ type: "problem"
1631
+ },
1632
+ name: "no-internal",
1633
+ create: (context) => {
1634
+ function getReplacement(location) {
1635
+ const match = /^\s*('|")/.exec(location);
1636
+ if (!match) {
1637
+ return void 0;
1638
+ }
1639
+ const [, quote] = match;
1640
+ if (/^['"]rxjs\/internal\/ajax/.test(location)) {
1641
+ return `${quote}rxjs/ajax${quote}`;
1642
+ }
1643
+ if (/^['"]rxjs\/internal\/observable\/dom\/fetch/.test(location)) {
1644
+ return `${quote}rxjs/fetch${quote}`;
1645
+ }
1646
+ if (/^['"]rxjs\/internal\/observable\/dom\/webSocket/i.test(location)) {
1647
+ return `${quote}rxjs/webSocket${quote}`;
1648
+ }
1649
+ if (/^['"]rxjs\/internal\/observable/.test(location)) {
1650
+ return `${quote}rxjs${quote}`;
1651
+ }
1652
+ if (/^['"]rxjs\/internal\/operators/.test(location)) {
1653
+ return `${quote}rxjs/operators${quote}`;
1654
+ }
1655
+ if (/^['"]rxjs\/internal\/scheduled/.test(location)) {
1656
+ return `${quote}rxjs${quote}`;
1657
+ }
1658
+ if (/^['"]rxjs\/internal\/scheduler/.test(location)) {
1659
+ return `${quote}rxjs${quote}`;
1660
+ }
1661
+ if (/^['"]rxjs\/internal\/testing/.test(location)) {
1662
+ return `${quote}rxjs/testing${quote}`;
1663
+ }
1664
+ return void 0;
1665
+ }
1666
+ return {
1667
+ [String.raw`ImportDeclaration Literal[value=/^rxjs\u002finternal/]`]: (node) => {
1668
+ const replacement = getReplacement(node.raw);
1669
+ if (replacement) {
1670
+ let fix = function(fixer) {
1671
+ return fixer.replaceText(node, replacement);
1672
+ };
1673
+ context.report({
1674
+ fix,
1675
+ messageId: "forbidden",
1676
+ node,
1677
+ suggest: [{ fix, messageId: "suggest" }]
1678
+ });
1679
+ } else {
1680
+ context.report({
1681
+ messageId: "forbidden",
1682
+ node
1683
+ });
1684
+ }
1685
+ }
1686
+ };
1687
+ }
1688
+ });
1689
+
1690
+ const noNestedSubscribeRule = ruleCreator({
1691
+ defaultOptions: [],
1692
+ meta: {
1693
+ docs: {
1694
+ description: "Forbids the calling of `subscribe` within a `subscribe` callback.",
1695
+ recommended: true,
1696
+ requiresTypeChecking: true
1697
+ },
1698
+ messages: {
1699
+ forbidden: "Nested subscribe calls are forbidden."
1700
+ },
1701
+ schema: [],
1702
+ type: "problem"
1703
+ },
1704
+ name: "no-nested-subscribe",
1705
+ create: (context) => {
1706
+ const { couldBeObservable, couldBeType } = getTypeServices(context);
1707
+ const argumentsMap = /* @__PURE__ */ new WeakMap();
1708
+ return {
1709
+ [`CallExpression > MemberExpression[property.name='subscribe']`]: (node) => {
1710
+ if (!couldBeObservable(node.object) && !couldBeType(node.object, "Subscribable")) {
1711
+ return;
1712
+ }
1713
+ const callExpression = node.parent;
1714
+ let parent = callExpression.parent;
1715
+ while (parent) {
1716
+ if (argumentsMap.has(parent)) {
1717
+ context.report({
1718
+ messageId: "forbidden",
1719
+ node: node.property
1720
+ });
1721
+ return;
1722
+ }
1723
+ parent = parent.parent;
1724
+ }
1725
+ for (const arg of callExpression.arguments) {
1726
+ argumentsMap.set(arg);
1727
+ }
1728
+ }
1729
+ };
1730
+ }
1731
+ });
1732
+
1733
+ const noRedundantNotifyRule = ruleCreator({
1734
+ defaultOptions: [],
1735
+ meta: {
1736
+ docs: {
1737
+ description: "Forbids redundant notifications from completed or errored observables.",
1738
+ recommended: true,
1739
+ requiresTypeChecking: true
1740
+ },
1741
+ messages: {
1742
+ forbidden: "Redundant notifications are forbidden."
1743
+ },
1744
+ schema: [],
1745
+ type: "problem"
1746
+ },
1747
+ name: "no-redundant-notify",
1748
+ create: (context) => {
1749
+ const sourceCode = context.sourceCode;
1750
+ const { couldBeType } = getTypeServices(context);
1751
+ return {
1752
+ "ExpressionStatement[expression.callee.property.name=/^(complete|error)$/] + ExpressionStatement[expression.callee.property.name=/^(next|complete|error)$/]": (node) => {
1753
+ const parent = node.parent;
1754
+ if (!parent) {
1755
+ return;
1756
+ }
1757
+ if (!isBlockStatement(parent) && !isProgram(parent)) {
1758
+ return;
1759
+ }
1760
+ const { body } = parent;
1761
+ const index = body.indexOf(node);
1762
+ const sibling = body[index - 1];
1763
+ if (getExpressionText(sibling, sourceCode) !== getExpressionText(node, sourceCode)) {
1764
+ return;
1765
+ }
1766
+ if (!isExpressionObserver(sibling, couldBeType) || !isExpressionObserver(node, couldBeType)) {
1767
+ return;
1768
+ }
1769
+ const { expression } = node;
1770
+ if (isCallExpression(expression)) {
1771
+ const { callee } = expression;
1772
+ if (isMemberExpression(callee)) {
1773
+ const { property } = callee;
1774
+ if (isIdentifier(property)) {
1775
+ context.report({
1776
+ messageId: "forbidden",
1777
+ node: property
1778
+ });
1779
+ }
1780
+ }
1781
+ }
1782
+ }
1783
+ };
1784
+ }
1785
+ });
1786
+ function getExpressionText(expressionStatement, sourceCode) {
1787
+ if (!isCallExpression(expressionStatement.expression)) {
1788
+ return void 0;
1789
+ }
1790
+ const callExpression = expressionStatement.expression;
1791
+ if (!isMemberExpression(callExpression.callee)) {
1792
+ return void 0;
1793
+ }
1794
+ const { object } = callExpression.callee;
1795
+ return sourceCode.getText(object);
1796
+ }
1797
+ function isExpressionObserver(expressionStatement, couldBeType) {
1798
+ if (!isCallExpression(expressionStatement.expression)) {
1799
+ return false;
1800
+ }
1801
+ const callExpression = expressionStatement.expression;
1802
+ if (!isMemberExpression(callExpression.callee)) {
1803
+ return false;
1804
+ }
1805
+ const { object } = callExpression.callee;
1806
+ return couldBeType(object, /^(Subject|Subscriber)$/);
1807
+ }
1808
+
1809
+ const defaultOptions$6 = [];
1810
+ const noSharereplayRule = ruleCreator({
1811
+ defaultOptions: defaultOptions$6,
1812
+ meta: {
1813
+ docs: {
1814
+ description: "Forbids using the `shareReplay` operator.",
1815
+ recommended: true
1816
+ },
1817
+ messages: {
1818
+ forbidden: "shareReplay is forbidden.",
1819
+ forbiddenWithoutConfig: "shareReplay is forbidden unless a config argument is passed."
1820
+ },
1821
+ schema: [
1822
+ {
1823
+ properties: {
1824
+ allowConfig: { type: "boolean" }
1825
+ },
1826
+ type: "object"
1827
+ }
1828
+ ],
1829
+ type: "problem"
1830
+ },
1831
+ name: "no-sharereplay",
1832
+ create: (context) => {
1833
+ const [config = {}] = context.options;
1834
+ const { allowConfig = true } = config;
1835
+ return {
1836
+ "CallExpression[callee.name='shareReplay']": (node) => {
1837
+ let report = true;
1838
+ if (allowConfig) {
1839
+ report = node.arguments.length !== 1 || node.arguments[0].type !== AST_NODE_TYPES.ObjectExpression;
1840
+ }
1841
+ if (report) {
1842
+ context.report({
1843
+ messageId: allowConfig ? "forbiddenWithoutConfig" : "forbidden",
1844
+ node: node.callee
1845
+ });
1846
+ }
1847
+ }
1848
+ };
1849
+ }
1850
+ });
1851
+
1852
+ const noSubclassRule = ruleCreator({
1853
+ defaultOptions: [],
1854
+ meta: {
1855
+ docs: {
1856
+ description: "Forbids subclassing RxJS classes.",
1857
+ requiresTypeChecking: true
1858
+ },
1859
+ messages: {
1860
+ forbidden: "Subclassing RxJS classes is forbidden."
1861
+ },
1862
+ schema: [],
1863
+ type: "problem"
1864
+ },
1865
+ name: "no-subclass",
1866
+ create: (context) => {
1867
+ const { couldBeType } = getTypeServices(context);
1868
+ const queryNames = [
1869
+ "AsyncSubject",
1870
+ "BehaviorSubject",
1871
+ "Observable",
1872
+ "ReplaySubject",
1873
+ "Scheduler",
1874
+ "Subject",
1875
+ "Subscriber"
1876
+ ];
1877
+ return {
1878
+ [`ClassDeclaration[superClass.name=/^(${queryNames.join(
1879
+ "|"
1880
+ )})$/] > Identifier.superClass`]: (node) => {
1881
+ if (queryNames.some(
1882
+ (name) => couldBeType(node, name, { name: /[/\\]rxjs[/\\]/ })
1883
+ )) {
1884
+ context.report({
1885
+ messageId: "forbidden",
1886
+ node
1887
+ });
1888
+ }
1889
+ }
1890
+ };
1891
+ }
1892
+ });
1893
+
1894
+ const noSubjectUnsubscribeRule = ruleCreator({
1895
+ defaultOptions: [],
1896
+ meta: {
1897
+ docs: {
1898
+ description: "Forbids calling the `unsubscribe` method of a subject instance.",
1899
+ recommended: true,
1900
+ requiresTypeChecking: true
1901
+ },
1902
+ messages: {
1903
+ forbidden: "Calling unsubscribe on a subject is forbidden."
1904
+ },
1905
+ schema: [],
1906
+ type: "problem"
1907
+ },
1908
+ name: "no-subject-unsubscribe",
1909
+ create: (context) => {
1910
+ const { couldBeSubject, couldBeSubscription } = getTypeServices(context);
1911
+ return {
1912
+ "MemberExpression[property.name='unsubscribe']": (node) => {
1913
+ if (couldBeSubject(node.object)) {
1914
+ context.report({
1915
+ messageId: "forbidden",
1916
+ node: node.property
1917
+ });
1918
+ }
1919
+ },
1920
+ "CallExpression[callee.property.name='add'][arguments.length > 0]": (node) => {
1921
+ const memberExpression = node.callee;
1922
+ if (couldBeSubscription(memberExpression.object)) {
1923
+ const [arg] = node.arguments;
1924
+ if (couldBeSubject(arg)) {
1925
+ context.report({
1926
+ messageId: "forbidden",
1927
+ node: arg
1928
+ });
1929
+ }
1930
+ }
1931
+ }
1932
+ };
1933
+ }
1934
+ });
1935
+
1936
+ const noSubjectValueRule = ruleCreator({
1937
+ defaultOptions: [],
1938
+ meta: {
1939
+ docs: {
1940
+ description: "Forbids accessing the `value` property of a `BehaviorSubject` instance.",
1941
+ requiresTypeChecking: true
1942
+ },
1943
+ messages: {
1944
+ forbidden: "Accessing the value property of a BehaviorSubject is forbidden."
1945
+ },
1946
+ schema: [],
1947
+ type: "problem"
1948
+ },
1949
+ name: "no-subject-value",
1950
+ create: (context) => {
1951
+ const { couldBeBehaviorSubject } = getTypeServices(context);
1952
+ return {
1953
+ "Identifier[name=/^(value|getValue)$/]": (node) => {
1954
+ const parent = node.parent;
1955
+ if (!parent || !("object" in parent)) {
1956
+ return;
1957
+ }
1958
+ if (couldBeBehaviorSubject(parent.object)) {
1959
+ context.report({
1960
+ messageId: "forbidden",
1961
+ node
1962
+ });
1963
+ }
1964
+ }
1965
+ };
1966
+ }
1967
+ });
1968
+
1969
+ const noSubscribeHandlersRule = ruleCreator({
1970
+ defaultOptions: [],
1971
+ meta: {
1972
+ docs: {
1973
+ description: "Forbids the passing of handlers to `subscribe`.",
1974
+ requiresTypeChecking: true
1975
+ },
1976
+ messages: {
1977
+ forbidden: "Passing handlers to subscribe is forbidden."
1978
+ },
1979
+ schema: [],
1980
+ type: "problem"
1981
+ },
1982
+ name: "no-subscribe-handlers",
1983
+ create: (context) => {
1984
+ const { couldBeObservable, couldBeType } = getTypeServices(context);
1985
+ return {
1986
+ "CallExpression[arguments.length > 0][callee.property.name='subscribe']": (node) => {
1987
+ const callee = node.callee;
1988
+ if (couldBeObservable(callee.object) || couldBeType(callee.object, "Subscribable")) {
1989
+ context.report({
1990
+ messageId: "forbidden",
1991
+ node: callee.property
1992
+ });
1993
+ }
1994
+ }
1995
+ };
1996
+ }
1997
+ });
1998
+
1999
+ const noTapRule = ruleCreator({
2000
+ defaultOptions: [],
2001
+ meta: {
2002
+ deprecated: true,
2003
+ docs: {
2004
+ description: "Forbids the use of the `tap` operator."
2005
+ },
2006
+ messages: {
2007
+ forbidden: "The tap operator is forbidden."
2008
+ },
2009
+ replacedBy: ["ban-operators"],
2010
+ schema: [],
2011
+ type: "problem"
2012
+ },
2013
+ name: "no-tap",
2014
+ create: (context) => {
2015
+ return {
2016
+ [String.raw`ImportDeclaration[source.value=/^rxjs(\u002foperators)?$/] > ImportSpecifier[imported.name='tap']`]: (node) => {
2017
+ const { loc } = node;
2018
+ context.report({
2019
+ messageId: "forbidden",
2020
+ loc: {
2021
+ ...loc,
2022
+ end: {
2023
+ ...loc.start,
2024
+ column: loc.start.column + 3
2025
+ }
2026
+ }
2027
+ });
2028
+ }
2029
+ };
2030
+ }
2031
+ });
2032
+
2033
+ const noTopromiseRule = ruleCreator({
2034
+ defaultOptions: [],
2035
+ meta: {
2036
+ docs: {
2037
+ description: "Forbids the use of the `toPromise` method.",
2038
+ requiresTypeChecking: true
2039
+ },
2040
+ messages: {
2041
+ forbidden: "The toPromise method is forbidden."
2042
+ },
2043
+ schema: [],
2044
+ type: "problem"
2045
+ },
2046
+ name: "no-topromise",
2047
+ create: (context) => {
2048
+ const { couldBeObservable } = getTypeServices(context);
2049
+ return {
2050
+ [`MemberExpression[property.name="toPromise"]`]: (node) => {
2051
+ if (couldBeObservable(node.object)) {
2052
+ context.report({
2053
+ messageId: "forbidden",
2054
+ node: node.property
2055
+ });
2056
+ }
2057
+ }
2058
+ };
2059
+ }
2060
+ });
2061
+
2062
+ const noUnboundMethodsRule = ruleCreator({
2063
+ defaultOptions: [],
2064
+ meta: {
2065
+ docs: {
2066
+ description: "Forbids the passing of unbound methods.",
2067
+ recommended: true,
2068
+ requiresTypeChecking: true
2069
+ },
2070
+ messages: {
2071
+ forbidden: "Unbound methods are forbidden."
2072
+ },
2073
+ schema: [],
2074
+ type: "problem"
2075
+ },
2076
+ name: "no-unbound-methods",
2077
+ create: (context) => {
2078
+ const { couldBeObservable, couldBeSubscription, getType } = getTypeServices(context);
2079
+ const nodeMap = /* @__PURE__ */ new WeakMap();
2080
+ function mapArguments(node) {
2081
+ node.arguments.filter(isMemberExpression).forEach((arg) => {
2082
+ const argType = getType(arg);
2083
+ if (argType.getCallSignatures().length > 0) {
2084
+ nodeMap.set(arg);
2085
+ }
2086
+ });
2087
+ }
2088
+ function isObservableOrSubscription(node, action) {
2089
+ if (!isMemberExpression(node.callee)) {
2090
+ return;
2091
+ }
2092
+ if (couldBeObservable(node.callee.object) || couldBeSubscription(node.callee.object)) {
2093
+ action(node);
2094
+ }
2095
+ }
2096
+ return {
2097
+ "CallExpression[callee.property.name='pipe']": (node) => {
2098
+ isObservableOrSubscription(node, ({ arguments: args }) => {
2099
+ args.filter(isCallExpression).forEach(mapArguments);
2100
+ });
2101
+ },
2102
+ "CallExpression[callee.property.name=/^(add|subscribe)$/]": (node) => {
2103
+ isObservableOrSubscription(node, mapArguments);
2104
+ },
2105
+ "NewExpression[callee.name='Subscription']": mapArguments,
2106
+ "ThisExpression": (node) => {
2107
+ let parent = node.parent;
2108
+ while (parent) {
2109
+ if (nodeMap.has(parent)) {
2110
+ context.report({
2111
+ messageId: "forbidden",
2112
+ node: parent
2113
+ });
2114
+ return;
2115
+ }
2116
+ parent = parent.parent;
2117
+ }
2118
+ }
2119
+ };
2120
+ }
2121
+ });
2122
+
2123
+ const defaultOptions$5 = [];
2124
+ const noUnsafeCatchRule = ruleCreator({
2125
+ defaultOptions: defaultOptions$5,
2126
+ meta: {
2127
+ docs: {
2128
+ description: "Forbids unsafe `catchError` usage in effects and epics.",
2129
+ requiresTypeChecking: true
2130
+ },
2131
+ messages: {
2132
+ forbidden: "Unsafe catchError usage in effects and epics are forbidden."
2133
+ },
2134
+ schema: [
2135
+ {
2136
+ properties: {
2137
+ observable: { type: "string" }
2138
+ },
2139
+ type: "object",
2140
+ description: stripIndent`
2141
+ An optional object with an optional \`observable\` property.
2142
+ The property can be specified as a regular expression string and is used to identify the action observables from which effects and epics are composed.`
2143
+ }
2144
+ ],
2145
+ type: "problem"
2146
+ },
2147
+ name: "no-unsafe-catch",
2148
+ create: (context) => {
2149
+ const invalidOperatorsRegExp = /^(catchError)$/;
2150
+ const [config = {}] = context.options;
2151
+ const { observable = defaultObservable } = config;
2152
+ const observableRegExp = new RegExp(observable);
2153
+ const { couldBeObservable } = getTypeServices(context);
2154
+ function isUnsafe([arg]) {
2155
+ if (arg && (isFunctionDeclaration(arg) || isArrowFunctionExpression(arg))) {
2156
+ return arg.params.length < 2;
2157
+ }
2158
+ return false;
2159
+ }
2160
+ function checkNode(node) {
2161
+ if (!node.arguments || !couldBeObservable(node)) {
2162
+ return;
2163
+ }
2164
+ node.arguments.forEach((arg) => {
2165
+ if (isCallExpression(arg) && isIdentifier(arg.callee)) {
2166
+ if (invalidOperatorsRegExp.test(arg.callee.name) && isUnsafe(arg.arguments)) {
2167
+ context.report({
2168
+ messageId: "forbidden",
2169
+ node: arg.callee
2170
+ });
2171
+ }
2172
+ }
2173
+ });
2174
+ }
2175
+ return {
2176
+ [`CallExpression[callee.property.name='pipe'][callee.object.name=${observableRegExp}]`]: checkNode,
2177
+ [`CallExpression[callee.property.name='pipe'][callee.object.property.name=${observableRegExp}]`]: checkNode
2178
+ };
2179
+ }
2180
+ });
2181
+
2182
+ const defaultOptions$4 = [];
2183
+ const noUnsafeFirstRule = ruleCreator({
2184
+ defaultOptions: defaultOptions$4,
2185
+ meta: {
2186
+ docs: {
2187
+ description: "Forbids unsafe `first`/`take` usage in effects and epics.",
2188
+ requiresTypeChecking: true
2189
+ },
2190
+ messages: {
2191
+ forbidden: "Unsafe first and take usage in effects and epics are forbidden."
2192
+ },
2193
+ schema: [
2194
+ {
2195
+ properties: {
2196
+ observable: { type: "string" }
2197
+ },
2198
+ type: "object",
2199
+ description: stripIndent`
2200
+ An optional object with an optional \`observable\` property.
2201
+ The property can be specified as a regular expression string and is used to identify the action observables from which effects and epics are composed.`
2202
+ }
2203
+ ],
2204
+ type: "problem"
2205
+ },
2206
+ name: "no-unsafe-first",
2207
+ create: (context) => {
2208
+ const invalidOperatorsRegExp = /^(take|first)$/;
2209
+ const [config = {}] = context.options;
2210
+ const { observable = defaultObservable } = config;
2211
+ const observableRegExp = new RegExp(observable);
2212
+ const { couldBeObservable } = getTypeServices(context);
2213
+ const nodes = [];
2214
+ function checkNode(node) {
2215
+ if (!node.arguments || !couldBeObservable(node)) {
2216
+ return;
2217
+ }
2218
+ node.arguments.forEach((arg) => {
2219
+ if (isCallExpression(arg) && isIdentifier(arg.callee)) {
2220
+ if (invalidOperatorsRegExp.test(arg.callee.name)) {
2221
+ context.report({
2222
+ messageId: "forbidden",
2223
+ node: arg.callee
2224
+ });
2225
+ }
2226
+ }
2227
+ });
2228
+ }
2229
+ return {
2230
+ [`CallExpression[callee.property.name='pipe'][callee.object.name=${observableRegExp}]`]: (node) => {
2231
+ if (nodes.push(node) === 1) {
2232
+ checkNode(node);
2233
+ }
2234
+ },
2235
+ [`CallExpression[callee.property.name='pipe'][callee.object.name=${observableRegExp}]:exit`]: () => {
2236
+ nodes.pop();
2237
+ },
2238
+ [`CallExpression[callee.property.name='pipe'][callee.object.property.name=${observableRegExp}]`]: (node) => {
2239
+ if (nodes.push(node) === 1) {
2240
+ checkNode(node);
2241
+ }
2242
+ },
2243
+ [`CallExpression[callee.property.name='pipe'][callee.object.property.name=${observableRegExp}]:exit`]: () => {
2244
+ nodes.pop();
2245
+ }
2246
+ };
2247
+ }
2248
+ });
2249
+
2250
+ const noUnsafeSubjectNext = ruleCreator({
2251
+ defaultOptions: [],
2252
+ meta: {
2253
+ docs: {
2254
+ description: "Forbids unsafe optional `next` calls.",
2255
+ recommended: true,
2256
+ requiresTypeChecking: true
2257
+ },
2258
+ messages: {
2259
+ forbidden: "Unsafe optional next calls are forbidden."
2260
+ },
2261
+ schema: [],
2262
+ type: "problem"
2263
+ },
2264
+ name: "no-unsafe-subject-next",
2265
+ create: (context) => {
2266
+ const { getType, typeChecker } = getTypeServices(context);
2267
+ return {
2268
+ [`CallExpression[callee.property.name='next']`]: (node) => {
2269
+ if (node.arguments.length === 0 && isMemberExpression(node.callee)) {
2270
+ const type = getType(node.callee.object);
2271
+ if (isReferenceType(type) && couldBeType(type, "Subject")) {
2272
+ const [typeArg] = typeChecker.getTypeArguments(type);
2273
+ if (tsutils$1.isTypeFlagSet(typeArg, ts.TypeFlags.Any)) {
2274
+ return;
2275
+ }
2276
+ if (tsutils$1.isTypeFlagSet(typeArg, ts.TypeFlags.Unknown)) {
2277
+ return;
2278
+ }
2279
+ if (tsutils$1.isTypeFlagSet(typeArg, ts.TypeFlags.Void)) {
2280
+ return;
2281
+ }
2282
+ if (isUnionType(typeArg) && typeArg.types.some(
2283
+ (t) => tsutils$1.isTypeFlagSet(t, ts.TypeFlags.Void)
2284
+ )) {
2285
+ return;
2286
+ }
2287
+ context.report({
2288
+ messageId: "forbidden",
2289
+ node: node.callee.property
2290
+ });
2291
+ }
2292
+ }
2293
+ }
2294
+ };
2295
+ }
2296
+ });
2297
+
2298
+ const defaultOptions$3 = [];
2299
+ const noUnsafeSwitchmapRule = ruleCreator({
2300
+ defaultOptions: defaultOptions$3,
2301
+ meta: {
2302
+ docs: {
2303
+ description: "Forbids unsafe `switchMap` usage in effects and epics.",
2304
+ requiresTypeChecking: true
2305
+ },
2306
+ messages: {
2307
+ forbidden: "Unsafe switchMap usage in effects and epics is forbidden."
2308
+ },
2309
+ schema: [
2310
+ {
2311
+ properties: {
2312
+ allow: {
2313
+ oneOf: [
2314
+ { type: "string" },
2315
+ { type: "array", items: { type: "string" } }
2316
+ ]
2317
+ },
2318
+ disallow: {
2319
+ oneOf: [
2320
+ { type: "string" },
2321
+ { type: "array", items: { type: "string" } }
2322
+ ]
2323
+ },
2324
+ observable: {
2325
+ oneOf: [
2326
+ { type: "string" },
2327
+ { type: "array", items: { type: "string" } }
2328
+ ]
2329
+ }
2330
+ },
2331
+ type: "object",
2332
+ description: stripIndent`
2333
+ An optional object with optional \`allow\`, \`disallow\` and \`observable\` properties.
2334
+ The properties can be specified as regular expression strings or as arrays of words.
2335
+ The \`allow\` or \`disallow\` properties are mutually exclusive. Whether or not
2336
+ \`switchMap\` is allowed will depend upon the matching of action types with \`allow\` or \`disallow\`.
2337
+ The \`observable\` property is used to identify the action observables from which effects and epics are composed.
2338
+ `
2339
+ }
2340
+ ],
2341
+ type: "problem"
2342
+ },
2343
+ name: "no-unsafe-switchmap",
2344
+ create: (context) => {
2345
+ const defaultDisallow = [
2346
+ "add",
2347
+ "create",
2348
+ "delete",
2349
+ "post",
2350
+ "put",
2351
+ "remove",
2352
+ "set",
2353
+ "update"
2354
+ ];
2355
+ let allowRegExp;
2356
+ let disallowRegExp;
2357
+ let observableRegExp;
2358
+ const [config = {}] = context.options;
2359
+ if (config.allow || config.disallow) {
2360
+ allowRegExp = createRegExpForWords(config.allow ?? []);
2361
+ disallowRegExp = createRegExpForWords(config.disallow ?? []);
2362
+ observableRegExp = new RegExp(config.observable ?? defaultObservable);
2363
+ } else {
2364
+ allowRegExp = void 0;
2365
+ disallowRegExp = createRegExpForWords(defaultDisallow);
2366
+ observableRegExp = new RegExp(defaultObservable);
2367
+ }
2368
+ const { couldBeObservable } = getTypeServices(context);
2369
+ function shouldDisallow(args) {
2370
+ const names = args.map((arg) => {
2371
+ if (isLiteral(arg) && typeof arg.value === "string") {
2372
+ return arg.value;
2373
+ }
2374
+ if (isIdentifier(arg)) {
2375
+ return arg.name;
2376
+ }
2377
+ if (isMemberExpression(arg) && isIdentifier(arg.property)) {
2378
+ return arg.property.name;
2379
+ }
2380
+ return "";
2381
+ }).map((name) => decamelize(name));
2382
+ if (allowRegExp) {
2383
+ return !names.every((name) => allowRegExp?.test(name));
2384
+ }
2385
+ if (disallowRegExp) {
2386
+ return names.some((name) => disallowRegExp?.test(name));
2387
+ }
2388
+ return false;
2389
+ }
2390
+ function checkNode(node) {
2391
+ if (!node.arguments || !couldBeObservable(node)) {
2392
+ return;
2393
+ }
2394
+ const hasUnsafeOfType = node.arguments.some((arg) => {
2395
+ if (isCallExpression(arg) && isIdentifier(arg.callee) && arg.callee.name === "ofType") {
2396
+ return shouldDisallow(arg.arguments);
2397
+ }
2398
+ return false;
2399
+ });
2400
+ if (!hasUnsafeOfType) {
2401
+ return;
2402
+ }
2403
+ node.arguments.forEach((arg) => {
2404
+ if (isCallExpression(arg) && isIdentifier(arg.callee) && arg.callee.name === "switchMap") {
2405
+ context.report({
2406
+ messageId: "forbidden",
2407
+ node: arg.callee
2408
+ });
2409
+ }
2410
+ });
2411
+ }
2412
+ return {
2413
+ [`CallExpression[callee.property.name='pipe'][callee.object.name=${observableRegExp}]`]: checkNode,
2414
+ [`CallExpression[callee.property.name='pipe'][callee.object.property.name=${observableRegExp}]`]: checkNode
2415
+ };
2416
+ }
2417
+ });
2418
+
2419
+ const defaultOptions$2 = [];
2420
+ const noUnsafeTakeuntilRule = ruleCreator({
2421
+ defaultOptions: defaultOptions$2,
2422
+ meta: {
2423
+ docs: {
2424
+ description: "Forbids the application of operators after `takeUntil`.",
2425
+ recommended: true,
2426
+ requiresTypeChecking: true
2427
+ },
2428
+ messages: {
2429
+ forbidden: "Applying operators after takeUntil is forbidden."
2430
+ },
2431
+ schema: [
2432
+ {
2433
+ properties: {
2434
+ alias: { type: "array", items: { type: "string" } },
2435
+ allow: { type: "array", items: { type: "string" } }
2436
+ },
2437
+ type: "object",
2438
+ description: stripIndent`
2439
+ An optional object with optional \`alias\` and \`allow\` properties.
2440
+ The \`alias\` property is an array containing the names of operators that aliases for \`takeUntil\`.
2441
+ The \`allow\` property is an array containing the names of the operators that are allowed to follow \`takeUntil\`.`
2442
+ }
2443
+ ],
2444
+ type: "problem"
2445
+ },
2446
+ name: "no-unsafe-takeuntil",
2447
+ create: (context) => {
2448
+ let checkedOperatorsRegExp = /^takeUntil$/;
2449
+ const allowedOperators = [
2450
+ "count",
2451
+ "defaultIfEmpty",
2452
+ "endWith",
2453
+ "every",
2454
+ "finalize",
2455
+ "finally",
2456
+ "isEmpty",
2457
+ "last",
2458
+ "max",
2459
+ "min",
2460
+ "publish",
2461
+ "publishBehavior",
2462
+ "publishLast",
2463
+ "publishReplay",
2464
+ "reduce",
2465
+ "share",
2466
+ "shareReplay",
2467
+ "skipLast",
2468
+ "takeLast",
2469
+ "throwIfEmpty",
2470
+ "toArray"
2471
+ ];
2472
+ const [config = {}] = context.options;
2473
+ const { alias, allow = allowedOperators } = config;
2474
+ if (alias) {
2475
+ checkedOperatorsRegExp = new RegExp(
2476
+ `^(${alias.concat("takeUntil").join("|")})$`
2477
+ );
2478
+ }
2479
+ const { couldBeObservable } = getTypeServices(context);
2480
+ function checkNode(node) {
2481
+ const pipeCallExpression = node.parent;
2482
+ if (!pipeCallExpression.arguments || !couldBeObservable(pipeCallExpression)) {
2483
+ return;
2484
+ }
2485
+ pipeCallExpression.arguments.reduceRight((state, arg) => {
2486
+ if (state === "taken") {
2487
+ return state;
2488
+ }
2489
+ if (!isCallExpression(arg)) {
2490
+ return "disallowed";
2491
+ }
2492
+ let operatorName;
2493
+ if (isIdentifier(arg.callee)) {
2494
+ operatorName = arg.callee.name;
2495
+ } else if (isMemberExpression(arg.callee) && isIdentifier(arg.callee.property)) {
2496
+ operatorName = arg.callee.property.name;
2497
+ } else {
2498
+ return "disallowed";
2499
+ }
2500
+ if (checkedOperatorsRegExp.test(operatorName)) {
2501
+ if (state === "disallowed") {
2502
+ context.report({
2503
+ messageId: "forbidden",
2504
+ node: arg.callee
2505
+ });
2506
+ }
2507
+ return "taken";
2508
+ }
2509
+ if (!allow.includes(operatorName)) {
2510
+ return "disallowed";
2511
+ }
2512
+ return state;
2513
+ }, "allowed");
2514
+ }
2515
+ return {
2516
+ [`CallExpression[callee.property.name='pipe'] > CallExpression[callee.name=${checkedOperatorsRegExp}]`]: checkNode,
2517
+ [`CallExpression[callee.property.name='pipe'] > CallExpression[callee.property.name=${checkedOperatorsRegExp}]`]: checkNode
2518
+ };
2519
+ }
2520
+ });
2521
+
2522
+ const defaultOptions$1 = [];
2523
+ const preferObserverRule = ruleCreator({
2524
+ defaultOptions: defaultOptions$1,
2525
+ meta: {
2526
+ docs: {
2527
+ description: "Forbids the passing separate handlers to `subscribe` and `tap`.",
2528
+ requiresTypeChecking: true
2529
+ },
2530
+ fixable: "code",
2531
+ hasSuggestions: true,
2532
+ messages: {
2533
+ forbidden: "Passing separate handlers is forbidden; pass an observer instead."
2534
+ },
2535
+ schema: [
2536
+ {
2537
+ properties: {
2538
+ allowNext: { type: "boolean" }
2539
+ },
2540
+ type: "object"
2541
+ }
2542
+ ],
2543
+ type: "problem"
2544
+ },
2545
+ name: "prefer-observer",
2546
+ create: (context) => {
2547
+ const { couldBeFunction, couldBeObservable } = getTypeServices(context);
2548
+ const [config = {}] = context.options;
2549
+ const { allowNext = true } = config;
2550
+ function checkArgs(callExpression, reportNode) {
2551
+ const { arguments: args, callee } = callExpression;
2552
+ if (isMemberExpression(callee) && !couldBeObservable(callee.object)) {
2553
+ return;
2554
+ }
2555
+ function* fix(fixer) {
2556
+ const sourceCode = context.sourceCode;
2557
+ const [nextArg, errorArg, completeArg] = args;
2558
+ const nextArgText = nextArg ? sourceCode.getText(nextArg) : "";
2559
+ const errorArgText = errorArg ? sourceCode.getText(errorArg) : "";
2560
+ const completeArgText = completeArg ? sourceCode.getText(completeArg) : "";
2561
+ let observer = "{";
2562
+ if (nextArgText && nextArgText !== "undefined" && nextArgText !== "null") {
2563
+ observer += ` next: ${nextArgText}${isValidArgText(errorArgText) || isValidArgText(completeArgText) ? "," : ""}`;
2564
+ }
2565
+ if (errorArgText && errorArgText !== "undefined" && errorArgText !== "null") {
2566
+ observer += ` error: ${errorArgText}${isValidArgText(completeArgText) ? "," : ""}`;
2567
+ }
2568
+ if (completeArgText && completeArgText !== "undefined" && completeArgText !== "null") {
2569
+ observer += ` complete: ${completeArgText}`;
2570
+ }
2571
+ observer += " }";
2572
+ yield fixer.replaceText(callExpression.arguments[0], observer);
2573
+ const [, start] = callExpression.arguments[0].range;
2574
+ const [, end] = callExpression.arguments[callExpression.arguments.length - 1].range;
2575
+ yield fixer.removeRange([start, end]);
2576
+ }
2577
+ if (args.length > 1) {
2578
+ context.report({
2579
+ messageId: "forbidden",
2580
+ node: reportNode,
2581
+ fix,
2582
+ suggest: [
2583
+ {
2584
+ messageId: "forbidden",
2585
+ fix
2586
+ }
2587
+ ]
2588
+ });
2589
+ } else if (args.length === 1 && !allowNext) {
2590
+ const [arg] = args;
2591
+ if (isArrowFunctionExpression(arg) || isFunctionExpression(arg) || couldBeFunction(arg)) {
2592
+ context.report({
2593
+ messageId: "forbidden",
2594
+ node: reportNode,
2595
+ fix,
2596
+ suggest: [
2597
+ {
2598
+ messageId: "forbidden",
2599
+ fix
2600
+ }
2601
+ ]
2602
+ });
2603
+ }
2604
+ }
2605
+ }
2606
+ return {
2607
+ "CallExpression[callee.property.name='pipe'] > CallExpression[callee.name='tap']": (node) => {
2608
+ checkArgs(node, node.callee);
2609
+ },
2610
+ "CallExpression[callee.property.name='subscribe']": (node) => {
2611
+ checkArgs(node, node.callee.property);
2612
+ }
2613
+ };
2614
+ }
2615
+ });
2616
+ function isValidArgText(argText) {
2617
+ return argText && argText !== "undefined" && argText !== "null";
2618
+ }
2619
+
2620
+ const defaultOptions = [];
2621
+ const suffixSubjectsRule = ruleCreator({
2622
+ defaultOptions,
2623
+ meta: {
2624
+ docs: {
2625
+ description: "Enforces the use of a suffix in subject identifiers.",
2626
+ requiresTypeChecking: true
2627
+ },
2628
+ messages: {
2629
+ forbidden: `Subject identifiers must end with "{{suffix}}".`
2630
+ },
2631
+ schema: [
2632
+ {
2633
+ properties: {
2634
+ parameters: { type: "boolean" },
2635
+ properties: { type: "boolean" },
2636
+ suffix: { type: "string" },
2637
+ types: { type: "object" },
2638
+ variables: { type: "boolean" }
2639
+ },
2640
+ type: "object"
2641
+ }
2642
+ ],
2643
+ type: "problem"
2644
+ },
2645
+ name: "suffix-subjects",
2646
+ create: (context) => {
2647
+ const { esTreeNodeToTSNodeMap } = ESLintUtils.getParserServices(context);
2648
+ const { couldBeType } = getTypeServices(context);
2649
+ const [config = {}] = context.options;
2650
+ const validate = {
2651
+ parameters: true,
2652
+ properties: true,
2653
+ variables: true,
2654
+ ...config
2655
+ };
2656
+ const types = [];
2657
+ if (config.types) {
2658
+ Object.entries(config.types).forEach(
2659
+ ([key, validate2]) => {
2660
+ types.push({ regExp: new RegExp(key), validate: validate2 });
2661
+ }
2662
+ );
2663
+ } else {
2664
+ types.push({
2665
+ regExp: /^EventEmitter$/,
2666
+ validate: false
2667
+ });
2668
+ }
2669
+ const { suffix = "Subject" } = config;
2670
+ const suffixRegex = new RegExp(
2671
+ String.raw`${escapeRegExp(suffix)}\$?$`,
2672
+ "i"
2673
+ );
2674
+ function checkNode(nameNode, typeNode) {
2675
+ const tsNode = esTreeNodeToTSNodeMap.get(nameNode);
2676
+ const text = tsNode.getText();
2677
+ if (!suffixRegex.test(text) && couldBeType(typeNode || nameNode, "Subject")) {
2678
+ for (const type of types) {
2679
+ const { regExp, validate: validate2 } = type;
2680
+ if (couldBeType(typeNode || nameNode, regExp) && !validate2) {
2681
+ return;
2682
+ }
2683
+ }
2684
+ context.report({
2685
+ data: { suffix },
2686
+ loc: getLoc(tsNode),
2687
+ messageId: "forbidden"
2688
+ });
2689
+ }
2690
+ }
2691
+ return {
2692
+ "ArrayPattern > Identifier": (node) => {
2693
+ const found = findParent(
2694
+ node,
2695
+ "ArrowFunctionExpression",
2696
+ "FunctionDeclaration",
2697
+ "FunctionExpression",
2698
+ "VariableDeclarator"
2699
+ );
2700
+ if (!found) {
2701
+ return;
2702
+ }
2703
+ if (!validate.variables && found.type === AST_NODE_TYPES.VariableDeclarator) {
2704
+ return;
2705
+ }
2706
+ if (!validate.parameters) {
2707
+ return;
2708
+ }
2709
+ checkNode(node);
2710
+ },
2711
+ "ArrowFunctionExpression > Identifier": (node) => {
2712
+ if (validate.parameters) {
2713
+ const parent = node.parent;
2714
+ if (node !== parent.body) {
2715
+ checkNode(node);
2716
+ }
2717
+ }
2718
+ },
2719
+ "PropertyDefinition[computed=false]": (node) => {
2720
+ const anyNode = node;
2721
+ if (validate.properties) {
2722
+ checkNode(anyNode.key);
2723
+ }
2724
+ },
2725
+ "FunctionDeclaration > Identifier": (node) => {
2726
+ if (validate.parameters) {
2727
+ const parent = node.parent;
2728
+ if (node !== parent.id) {
2729
+ checkNode(node);
2730
+ }
2731
+ }
2732
+ },
2733
+ "FunctionExpression > Identifier": (node) => {
2734
+ if (validate.parameters) {
2735
+ const parent = node.parent;
2736
+ if (node !== parent.id) {
2737
+ checkNode(node);
2738
+ }
2739
+ }
2740
+ },
2741
+ "MethodDefinition[kind='get'][computed=false]": (node) => {
2742
+ if (validate.properties) {
2743
+ checkNode(node.key, node);
2744
+ }
2745
+ },
2746
+ "MethodDefinition[kind='set'][computed=false]": (node) => {
2747
+ if (validate.properties) {
2748
+ checkNode(node.key, node);
2749
+ }
2750
+ },
2751
+ "ObjectExpression > Property[computed=false] > Identifier": (node) => {
2752
+ if (validate.properties) {
2753
+ const parent = node.parent;
2754
+ if (node === parent.key) {
2755
+ checkNode(node);
2756
+ }
2757
+ }
2758
+ },
2759
+ "ObjectPattern > Property > Identifier": (node) => {
2760
+ const found = findParent(
2761
+ node,
2762
+ "ArrowFunctionExpression",
2763
+ "FunctionDeclaration",
2764
+ "FunctionExpression",
2765
+ "VariableDeclarator"
2766
+ );
2767
+ if (!found) {
2768
+ return;
2769
+ }
2770
+ if (!validate.variables && found.type === AST_NODE_TYPES.VariableDeclarator) {
2771
+ return;
2772
+ }
2773
+ if (!validate.parameters) {
2774
+ return;
2775
+ }
2776
+ const parent = node.parent;
2777
+ if (node === parent.value) {
2778
+ checkNode(node);
2779
+ }
2780
+ },
2781
+ "TSCallSignatureDeclaration > Identifier": (node) => {
2782
+ if (validate.parameters) {
2783
+ checkNode(node);
2784
+ }
2785
+ },
2786
+ "TSConstructSignatureDeclaration > Identifier": (node) => {
2787
+ if (validate.parameters) {
2788
+ checkNode(node);
2789
+ }
2790
+ },
2791
+ "TSMethodSignature > Identifier": (node) => {
2792
+ if (validate.parameters) {
2793
+ checkNode(node);
2794
+ }
2795
+ },
2796
+ "TSParameterProperty > Identifier": (node) => {
2797
+ if (validate.parameters || validate.properties) {
2798
+ checkNode(node);
2799
+ }
2800
+ },
2801
+ "TSPropertySignature[computed=false]": (node) => {
2802
+ if (validate.properties) {
2803
+ checkNode(node.key);
2804
+ }
2805
+ },
2806
+ "VariableDeclarator > Identifier": (node) => {
2807
+ const parent = node.parent;
2808
+ if (validate.variables && node === parent.id) {
2809
+ checkNode(node);
2810
+ }
2811
+ }
2812
+ };
2813
+ }
2814
+ });
2815
+
2816
+ const throwErrorRule = ruleCreator({
2817
+ defaultOptions: [],
2818
+ meta: {
2819
+ docs: {
2820
+ description: "Enforces the passing of `Error` values to error notifications.",
2821
+ requiresTypeChecking: true
2822
+ },
2823
+ messages: {
2824
+ forbidden: "Passing non-Error values are forbidden."
2825
+ },
2826
+ schema: [],
2827
+ type: "problem"
2828
+ },
2829
+ name: "throw-error",
2830
+ create: (context) => {
2831
+ const { esTreeNodeToTSNodeMap, program } = ESLintUtils.getParserServices(context);
2832
+ const { couldBeObservable, getType } = getTypeServices(context);
2833
+ function checkNode(node) {
2834
+ let type = getType(node);
2835
+ if (couldBeFunction(type)) {
2836
+ const tsNode = esTreeNodeToTSNodeMap.get(node);
2837
+ const annotation = tsNode.type;
2838
+ const body = tsNode.body;
2839
+ type = program.getTypeChecker().getTypeAtLocation(annotation ?? body);
2840
+ }
2841
+ if (!isAny(type) && !isUnknown(type) && !couldBeType(type, /^(Error|DOMException)$/)) {
2842
+ context.report({
2843
+ messageId: "forbidden",
2844
+ node
2845
+ });
2846
+ }
2847
+ }
2848
+ return {
2849
+ "ThrowStatement > *": checkNode,
2850
+ "CallExpression[callee.name='throwError']": (node) => {
2851
+ if (couldBeObservable(node)) {
2852
+ const [arg] = node.arguments;
2853
+ if (arg) {
2854
+ checkNode(arg);
2855
+ }
2856
+ }
2857
+ }
2858
+ };
2859
+ }
2860
+ });
2861
+
2862
+ const plugin = {
2863
+ meta: { name, version },
2864
+ rules: {
2865
+ "ban-observables": banObservablesRule,
2866
+ "ban-operators": banOperatorsRule,
2867
+ "finnish": finnishRule,
2868
+ "just": justRule,
2869
+ "macro": macroRule,
2870
+ "no-async-subscribe": noAsyncSubscribeRule,
2871
+ "no-compat": noCompatRule,
2872
+ "no-connectable": noConnectableRule,
2873
+ "no-create": noCreateRule,
2874
+ "no-cyclic-action": noCyclicActionRule,
2875
+ "no-explicit-generics": noExplicitGenericsRule,
2876
+ "no-exposed-subjects": noExposedSubjectsRule,
2877
+ "no-finnish": noFinnishRule,
2878
+ "no-ignored-error": noIgnoredErrorRule,
2879
+ "no-ignored-notifier": noIgnoredNotifierRule,
2880
+ "no-ignored-observable": noIgnoredObservableRule,
2881
+ "no-ignored-replay-buffer": noIgnoredReplayBufferRule,
2882
+ "no-ignored-subscribe": noIgnoredSubscribeRule,
2883
+ "no-ignored-subscription": noIgnoredSubscriptionRule,
2884
+ "no-ignored-takewhile-value": noIgnoredTakewhileValueRule,
2885
+ "no-implicit-any-catch": noImplicitAnyCatchRule,
2886
+ "no-index": noIndexRule,
2887
+ "no-internal": noInternalRule,
2888
+ "no-nested-subscribe": noNestedSubscribeRule,
2889
+ "no-redundant-notify": noRedundantNotifyRule,
2890
+ "no-sharereplay": noSharereplayRule,
2891
+ "no-subclass": noSubclassRule,
2892
+ "no-subject-unsubscribe": noSubjectUnsubscribeRule,
2893
+ "no-subject-value": noSubjectValueRule,
2894
+ "no-subscribe-handlers": noSubscribeHandlersRule,
2895
+ "no-tap": noTapRule,
2896
+ "no-topromise": noTopromiseRule,
2897
+ "no-unbound-methods": noUnboundMethodsRule,
2898
+ "no-unsafe-catch": noUnsafeCatchRule,
2899
+ "no-unsafe-first": noUnsafeFirstRule,
2900
+ "no-unsafe-subject-next": noUnsafeSubjectNext,
2901
+ "no-unsafe-switchmap": noUnsafeSwitchmapRule,
2902
+ "no-unsafe-takeuntil": noUnsafeTakeuntilRule,
2903
+ "prefer-observer": preferObserverRule,
2904
+ "suffix-subjects": suffixSubjectsRule,
2905
+ "throw-error": throwErrorRule
2906
+ }
2907
+ };
2908
+ const configs = {
2909
+ recommended: createRecommendedConfig(plugin)
2910
+ };
2911
+ const index = {
2912
+ ...plugin,
2913
+ configs
2914
+ };
2915
+
2916
+ export { index as default };