eslint-plugin-rxjs-x 0.7.2 → 0.7.3

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/dist/index.d.mts CHANGED
@@ -124,7 +124,10 @@ declare const rxjsX: {
124
124
  'no-ignored-notifier': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
125
125
  'no-ignored-replay-buffer': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
126
126
  'no-ignored-subscribe': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
127
- 'no-ignored-subscription': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
127
+ 'no-ignored-subscription': TSESLint.RuleModule<"forbidden", readonly {
128
+ completers?: string[];
129
+ postCompleters?: string[];
130
+ }[], RxjsXRuleDocs, TSESLint.RuleListener>;
128
131
  'no-ignored-takewhile-value': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
129
132
  'no-implicit-any-catch': TSESLint.RuleModule<"explicitAny" | "implicitAny" | "narrowed" | "suggestExplicitUnknown", readonly {
130
133
  allowExplicitAny?: boolean;
package/dist/index.d.ts CHANGED
@@ -124,7 +124,10 @@ declare const rxjsX: {
124
124
  'no-ignored-notifier': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
125
125
  'no-ignored-replay-buffer': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
126
126
  'no-ignored-subscribe': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
127
- 'no-ignored-subscription': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
127
+ 'no-ignored-subscription': TSESLint.RuleModule<"forbidden", readonly {
128
+ completers?: string[];
129
+ postCompleters?: string[];
130
+ }[], RxjsXRuleDocs, TSESLint.RuleListener>;
128
131
  'no-ignored-takewhile-value': TSESLint.RuleModule<"forbidden", [], RxjsXRuleDocs, TSESLint.RuleListener>;
129
132
  'no-implicit-any-catch': TSESLint.RuleModule<"explicitAny" | "implicitAny" | "narrowed" | "suggestExplicitUnknown", readonly {
130
133
  allowExplicitAny?: boolean;
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { newObj[key] = obj[key]; } } } newObj.default = obj; return newObj; } } function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }// package.json
2
2
  var name = "eslint-plugin-rxjs-x";
3
- var version = "0.7.2";
3
+ var version = "0.7.3";
4
4
 
5
5
  // src/configs/recommended.ts
6
6
  var createRecommendedConfig = (plugin2) => ({
@@ -77,8 +77,7 @@ var createStrictConfig = (plugin2) => ({
77
77
  var _utils = require('@typescript-eslint/utils');
78
78
  var _commontags = require('common-tags');
79
79
 
80
- // src/utils.ts
81
-
80
+ // src/utils/create-regexp-for-words.ts
82
81
  function createRegExpForWords(config) {
83
82
  if (!(config == null ? void 0 : config.length)) {
84
83
  return void 0;
@@ -91,81 +90,11 @@ function createRegExpForWords(config) {
91
90
  const joined = words.map((word) => String.raw`(\b|_)${word}(\b|_)`).join("|");
92
91
  return new RegExp(`(${joined})`, flags);
93
92
  }
93
+
94
+ // src/utils/escape-regexp.ts
94
95
  function escapeRegExp(text) {
95
96
  return text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
96
97
  }
97
- var REPO_URL = "https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x";
98
- var ruleCreator = _utils.ESLintUtils.RuleCreator(
99
- (name2) => `${REPO_URL}/blob/v${version}/docs/rules/${name2}.md`
100
- );
101
-
102
- // src/rules/ban-observables.ts
103
- var defaultOptions = [];
104
- var banObservablesRule = ruleCreator({
105
- defaultOptions,
106
- meta: {
107
- docs: {
108
- description: "Disallow banned observable creators."
109
- },
110
- messages: {
111
- forbidden: "RxJS observable is banned: {{name}}{{explanation}}."
112
- },
113
- schema: [
114
- {
115
- type: "object",
116
- description: _commontags.stripIndent`
117
- An object containing keys that are names of observable factory functions
118
- and values that are either booleans or strings containing the explanation for the ban.`
119
- }
120
- ],
121
- type: "problem"
122
- },
123
- name: "ban-observables",
124
- create: (context) => {
125
- const bans = [];
126
- const [config] = context.options;
127
- if (!config) {
128
- return {};
129
- }
130
- Object.entries(config).forEach(([key, value]) => {
131
- if (value !== false) {
132
- bans.push({
133
- explanation: typeof value === "string" ? value : "",
134
- regExp: new RegExp(`^${key}$`)
135
- });
136
- }
137
- });
138
- function getFailure(name2) {
139
- for (let b = 0, length = bans.length; b < length; ++b) {
140
- const ban = bans[b];
141
- if (ban.regExp.test(name2)) {
142
- const explanation = ban.explanation ? `: ${ban.explanation}` : "";
143
- return {
144
- messageId: "forbidden",
145
- data: { name: name2, explanation }
146
- };
147
- }
148
- }
149
- return void 0;
150
- }
151
- return {
152
- "ImportDeclaration[source.value='rxjs'] > ImportSpecifier": (node) => {
153
- const identifier = node.imported;
154
- const name2 = identifier.type === _utils.AST_NODE_TYPES.Identifier ? identifier.name : identifier.value;
155
- const failure = getFailure(name2);
156
- if (failure) {
157
- context.report({
158
- ...failure,
159
- node: identifier
160
- });
161
- }
162
- }
163
- };
164
- }
165
- });
166
-
167
- // src/rules/ban-operators.ts
168
-
169
98
 
170
99
  // src/etc/could-be-function.ts
171
100
  var _typescript = require('typescript'); var _typescript2 = _interopRequireDefault(_typescript);
@@ -426,7 +355,114 @@ function isImport(scope, name2, source) {
426
355
  return scope.upper ? isImport(scope.upper, name2, source) : false;
427
356
  }
428
357
 
358
+ // src/utils/find-is-last-operator-order-valid.ts
359
+ function findIsLastOperatorOrderValid(pipeCallExpression, operatorsRegExp, allow) {
360
+ let isOrderValid = true;
361
+ let operatorNode;
362
+ for (let i = pipeCallExpression.arguments.length - 1; i >= 0; i--) {
363
+ const arg = pipeCallExpression.arguments[i];
364
+ if (operatorNode) {
365
+ break;
366
+ }
367
+ if (!isCallExpression(arg)) {
368
+ isOrderValid = false;
369
+ continue;
370
+ }
371
+ let operatorName;
372
+ if (isIdentifier(arg.callee)) {
373
+ operatorName = arg.callee.name;
374
+ } else if (isMemberExpression(arg.callee) && isIdentifier(arg.callee.property)) {
375
+ operatorName = arg.callee.property.name;
376
+ } else {
377
+ isOrderValid = false;
378
+ continue;
379
+ }
380
+ if (operatorsRegExp.test(operatorName)) {
381
+ operatorNode = arg.callee;
382
+ break;
383
+ }
384
+ if (!allow.includes(operatorName)) {
385
+ isOrderValid = false;
386
+ continue;
387
+ }
388
+ }
389
+ return { isOrderValid, operatorNode };
390
+ }
391
+
392
+ // src/utils/rule-creator.ts
393
+
394
+ var REPO_URL = "https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x";
395
+ var ruleCreator = _utils.ESLintUtils.RuleCreator(
396
+ (name2) => `${REPO_URL}/blob/v${version}/docs/rules/${name2}.md`
397
+ );
398
+
399
+ // src/rules/ban-observables.ts
400
+ var defaultOptions = [];
401
+ var banObservablesRule = ruleCreator({
402
+ defaultOptions,
403
+ meta: {
404
+ docs: {
405
+ description: "Disallow banned observable creators."
406
+ },
407
+ messages: {
408
+ forbidden: "RxJS observable is banned: {{name}}{{explanation}}."
409
+ },
410
+ schema: [
411
+ {
412
+ type: "object",
413
+ description: _commontags.stripIndent`
414
+ An object containing keys that are names of observable factory functions
415
+ and values that are either booleans or strings containing the explanation for the ban.`
416
+ }
417
+ ],
418
+ type: "problem"
419
+ },
420
+ name: "ban-observables",
421
+ create: (context) => {
422
+ const bans = [];
423
+ const [config] = context.options;
424
+ if (!config) {
425
+ return {};
426
+ }
427
+ Object.entries(config).forEach(([key, value]) => {
428
+ if (value !== false) {
429
+ bans.push({
430
+ explanation: typeof value === "string" ? value : "",
431
+ regExp: new RegExp(`^${key}$`)
432
+ });
433
+ }
434
+ });
435
+ function getFailure(name2) {
436
+ for (let b = 0, length = bans.length; b < length; ++b) {
437
+ const ban = bans[b];
438
+ if (ban.regExp.test(name2)) {
439
+ const explanation = ban.explanation ? `: ${ban.explanation}` : "";
440
+ return {
441
+ messageId: "forbidden",
442
+ data: { name: name2, explanation }
443
+ };
444
+ }
445
+ }
446
+ return void 0;
447
+ }
448
+ return {
449
+ "ImportDeclaration[source.value='rxjs'] > ImportSpecifier": (node) => {
450
+ const identifier = node.imported;
451
+ const name2 = identifier.type === _utils.AST_NODE_TYPES.Identifier ? identifier.name : identifier.value;
452
+ const failure = getFailure(name2);
453
+ if (failure) {
454
+ context.report({
455
+ ...failure,
456
+ node: identifier
457
+ });
458
+ }
459
+ }
460
+ };
461
+ }
462
+ });
463
+
429
464
  // src/rules/ban-operators.ts
465
+
430
466
  var defaultOptions2 = [];
431
467
  var banOperatorsRule = ruleCreator({
432
468
  defaultOptions: defaultOptions2,
@@ -995,6 +1031,29 @@ var noCreateRule = ruleCreator({
995
1031
 
996
1032
  // src/constants.ts
997
1033
  var defaultObservable = String.raw`[Aa]ction(s|s\$|\$)$`;
1034
+ var DEFAULT_VALID_POST_COMPLETION_OPERATORS = [
1035
+ "count",
1036
+ "defaultIfEmpty",
1037
+ "endWith",
1038
+ "every",
1039
+ "finalize",
1040
+ "finally",
1041
+ "isEmpty",
1042
+ "last",
1043
+ "max",
1044
+ "min",
1045
+ "publish",
1046
+ "publishBehavior",
1047
+ "publishLast",
1048
+ "publishReplay",
1049
+ "reduce",
1050
+ "share",
1051
+ "shareReplay",
1052
+ "skipLast",
1053
+ "takeLast",
1054
+ "throwIfEmpty",
1055
+ "toArray"
1056
+ ];
998
1057
 
999
1058
  // src/rules/no-cyclic-action.ts
1000
1059
  function isTypeReference2(type) {
@@ -1765,8 +1824,10 @@ var noIgnoredSubscribeRule = ruleCreator({
1765
1824
  });
1766
1825
 
1767
1826
  // src/rules/no-ignored-subscription.ts
1827
+
1828
+ var defaultOptions7 = [];
1768
1829
  var noIgnoredSubscriptionRule = ruleCreator({
1769
- defaultOptions: [],
1830
+ defaultOptions: defaultOptions7,
1770
1831
  meta: {
1771
1832
  docs: {
1772
1833
  description: "Disallow ignoring the subscription returned by `subscribe`.",
@@ -1775,12 +1836,44 @@ var noIgnoredSubscriptionRule = ruleCreator({
1775
1836
  messages: {
1776
1837
  forbidden: "Ignoring returned subscriptions is forbidden."
1777
1838
  },
1778
- schema: [],
1839
+ schema: [
1840
+ {
1841
+ properties: {
1842
+ completers: { type: "array", items: { type: "string" }, description: "An array of operator names that will complete the observable and silence this rule.", default: ["takeUntil", "takeWhile", "take", "first", "last", "takeUntilDestroyed"] },
1843
+ postCompleters: { type: "array", items: { type: "string" }, description: "An array of operator names that are allowed to follow the completion operators.", default: DEFAULT_VALID_POST_COMPLETION_OPERATORS }
1844
+ },
1845
+ type: "object",
1846
+ description: _commontags.stripIndent`
1847
+ An object with optional \`completers\` and \`postCompleters\` properties.
1848
+ The \`completers\` property is an array containing the names of operators that will complete the observable and silence this rule.
1849
+ The \`postCompleters\` property is an array containing the names of the operators that are allowed to follow the completion operators.
1850
+ `
1851
+ }
1852
+ ],
1779
1853
  type: "problem"
1780
1854
  },
1781
1855
  name: "no-ignored-subscription",
1782
1856
  create: (context) => {
1857
+ const [config = {}] = context.options;
1858
+ const {
1859
+ completers = ["takeUntil", "takeWhile", "take", "first", "last", "takeUntilDestroyed"],
1860
+ postCompleters = DEFAULT_VALID_POST_COMPLETION_OPERATORS
1861
+ } = config;
1862
+ const checkedOperatorsRegExp = new RegExp(
1863
+ `^(${completers.join("|")})$`
1864
+ );
1783
1865
  const { couldBeObservable, couldBeType: couldBeType2 } = getTypeServices(context);
1866
+ function hasAllowedOperator(node) {
1867
+ if (isCallExpression(node) && isMemberExpression(node.callee) && isIdentifier(node.callee.property) && node.callee.property.name === "pipe") {
1868
+ const { isOrderValid, operatorNode } = findIsLastOperatorOrderValid(
1869
+ node,
1870
+ checkedOperatorsRegExp,
1871
+ postCompleters
1872
+ );
1873
+ return isOrderValid && !!operatorNode;
1874
+ }
1875
+ return false;
1876
+ }
1784
1877
  return {
1785
1878
  "ExpressionStatement > CallExpression > MemberExpression[property.name='subscribe']": (node) => {
1786
1879
  if (couldBeObservable(node.object)) {
@@ -1788,6 +1881,9 @@ var noIgnoredSubscriptionRule = ruleCreator({
1788
1881
  if (callExpression.arguments.length === 1 && couldBeType2(callExpression.arguments[0], "Subscriber")) {
1789
1882
  return;
1790
1883
  }
1884
+ if (hasAllowedOperator(node.object)) {
1885
+ return;
1886
+ }
1791
1887
  context.report({
1792
1888
  messageId: "forbidden",
1793
1889
  node: node.property
@@ -1862,9 +1958,9 @@ function isParenthesised(sourceCode, node) {
1862
1958
  const after = sourceCode.getTokenAfter(node);
1863
1959
  return before && after && before.value === "(" && before.range[1] <= node.range[0] && after.value === ")" && after.range[0] >= node.range[1];
1864
1960
  }
1865
- var defaultOptions7 = [];
1961
+ var defaultOptions8 = [];
1866
1962
  var noImplicitAnyCatchRule = ruleCreator({
1867
- defaultOptions: defaultOptions7,
1963
+ defaultOptions: defaultOptions8,
1868
1964
  meta: {
1869
1965
  docs: {
1870
1966
  description: "Disallow implicit `any` error parameters in `catchError` operators.",
@@ -2138,9 +2234,9 @@ function parseChecksVoidReturn(checksVoidReturn) {
2138
2234
  };
2139
2235
  }
2140
2236
  }
2141
- var defaultOptions8 = [];
2237
+ var defaultOptions9 = [];
2142
2238
  var noMisusedObservablesRule = ruleCreator({
2143
- defaultOptions: defaultOptions8,
2239
+ defaultOptions: defaultOptions9,
2144
2240
  meta: {
2145
2241
  docs: {
2146
2242
  description: "Disallow Observables in places not designed to handle them.",
@@ -2628,9 +2724,9 @@ function isExpressionObserver(expressionStatement, couldBeType2) {
2628
2724
 
2629
2725
  // src/rules/no-sharereplay.ts
2630
2726
 
2631
- var defaultOptions9 = [];
2727
+ var defaultOptions10 = [];
2632
2728
  var noSharereplayRule = ruleCreator({
2633
- defaultOptions: defaultOptions9,
2729
+ defaultOptions: defaultOptions10,
2634
2730
  meta: {
2635
2731
  docs: {
2636
2732
  description: "Disallow unsafe `shareReplay` usage.",
@@ -3070,9 +3166,9 @@ var noUnboundMethodsRule = ruleCreator({
3070
3166
 
3071
3167
  // src/rules/no-unsafe-catch.ts
3072
3168
 
3073
- var defaultOptions10 = [];
3169
+ var defaultOptions11 = [];
3074
3170
  var noUnsafeCatchRule = ruleCreator({
3075
- defaultOptions: defaultOptions10,
3171
+ defaultOptions: defaultOptions11,
3076
3172
  meta: {
3077
3173
  docs: {
3078
3174
  description: "Disallow unsafe `catchError` usage in effects and epics.",
@@ -3131,9 +3227,9 @@ var noUnsafeCatchRule = ruleCreator({
3131
3227
 
3132
3228
  // src/rules/no-unsafe-first.ts
3133
3229
 
3134
- var defaultOptions11 = [];
3230
+ var defaultOptions12 = [];
3135
3231
  var noUnsafeFirstRule = ruleCreator({
3136
- defaultOptions: defaultOptions11,
3232
+ defaultOptions: defaultOptions12,
3137
3233
  meta: {
3138
3234
  docs: {
3139
3235
  description: "Disallow unsafe `first`/`take` usage in effects and epics.",
@@ -3255,9 +3351,9 @@ var noUnsafeSubjectNext = ruleCreator({
3255
3351
  // src/rules/no-unsafe-switchmap.ts
3256
3352
 
3257
3353
  var _decamelize = require('decamelize'); var _decamelize2 = _interopRequireDefault(_decamelize);
3258
- var defaultOptions12 = [];
3354
+ var defaultOptions13 = [];
3259
3355
  var noUnsafeSwitchmapRule = ruleCreator({
3260
- defaultOptions: defaultOptions12,
3356
+ defaultOptions: defaultOptions13,
3261
3357
  meta: {
3262
3358
  docs: {
3263
3359
  description: "Disallow unsafe `switchMap` usage in effects and epics.",
@@ -3380,32 +3476,9 @@ var noUnsafeSwitchmapRule = ruleCreator({
3380
3476
 
3381
3477
  // src/rules/no-unsafe-takeuntil.ts
3382
3478
 
3383
- var defaultOptions13 = [];
3384
- var allowedOperators = [
3385
- "count",
3386
- "defaultIfEmpty",
3387
- "endWith",
3388
- "every",
3389
- "finalize",
3390
- "finally",
3391
- "isEmpty",
3392
- "last",
3393
- "max",
3394
- "min",
3395
- "publish",
3396
- "publishBehavior",
3397
- "publishLast",
3398
- "publishReplay",
3399
- "reduce",
3400
- "share",
3401
- "shareReplay",
3402
- "skipLast",
3403
- "takeLast",
3404
- "throwIfEmpty",
3405
- "toArray"
3406
- ];
3479
+ var defaultOptions14 = [];
3407
3480
  var noUnsafeTakeuntilRule = ruleCreator({
3408
- defaultOptions: defaultOptions13,
3481
+ defaultOptions: defaultOptions14,
3409
3482
  meta: {
3410
3483
  docs: {
3411
3484
  description: "Disallow applying operators after `takeUntil`.",
@@ -3419,7 +3492,7 @@ var noUnsafeTakeuntilRule = ruleCreator({
3419
3492
  {
3420
3493
  properties: {
3421
3494
  alias: { type: "array", items: { type: "string" }, description: "An array of operator names that should be treated similarly to `takeUntil`." },
3422
- allow: { type: "array", items: { type: "string" }, description: "An array of operator names that are allowed to follow `takeUntil`.", default: allowedOperators }
3495
+ allow: { type: "array", items: { type: "string" }, description: "An array of operator names that are allowed to follow `takeUntil`.", default: DEFAULT_VALID_POST_COMPLETION_OPERATORS }
3423
3496
  },
3424
3497
  type: "object",
3425
3498
  description: _commontags.stripIndent`
@@ -3434,7 +3507,7 @@ var noUnsafeTakeuntilRule = ruleCreator({
3434
3507
  create: (context) => {
3435
3508
  let checkedOperatorsRegExp = /^takeUntil$/;
3436
3509
  const [config = {}] = context.options;
3437
- const { alias, allow = allowedOperators } = config;
3510
+ const { alias, allow = DEFAULT_VALID_POST_COMPLETION_OPERATORS } = config;
3438
3511
  if (alias) {
3439
3512
  checkedOperatorsRegExp = new RegExp(
3440
3513
  `^(${alias.concat("takeUntil").join("|")})$`
@@ -3446,35 +3519,17 @@ var noUnsafeTakeuntilRule = ruleCreator({
3446
3519
  if (!pipeCallExpression.arguments || !couldBeObservable(pipeCallExpression)) {
3447
3520
  return;
3448
3521
  }
3449
- pipeCallExpression.arguments.reduceRight((state, arg) => {
3450
- if (state === "taken") {
3451
- return state;
3452
- }
3453
- if (!isCallExpression(arg)) {
3454
- return "disallowed";
3455
- }
3456
- let operatorName;
3457
- if (isIdentifier(arg.callee)) {
3458
- operatorName = arg.callee.name;
3459
- } else if (isMemberExpression(arg.callee) && isIdentifier(arg.callee.property)) {
3460
- operatorName = arg.callee.property.name;
3461
- } else {
3462
- return "disallowed";
3463
- }
3464
- if (checkedOperatorsRegExp.test(operatorName)) {
3465
- if (state === "disallowed") {
3466
- context.report({
3467
- messageId: "forbidden",
3468
- node: arg.callee
3469
- });
3470
- }
3471
- return "taken";
3472
- }
3473
- if (!allow.includes(operatorName)) {
3474
- return "disallowed";
3475
- }
3476
- return state;
3477
- }, "allowed");
3522
+ const { isOrderValid, operatorNode } = findIsLastOperatorOrderValid(
3523
+ pipeCallExpression,
3524
+ checkedOperatorsRegExp,
3525
+ allow
3526
+ );
3527
+ if (!isOrderValid && operatorNode) {
3528
+ context.report({
3529
+ messageId: "forbidden",
3530
+ node: operatorNode
3531
+ });
3532
+ }
3478
3533
  }
3479
3534
  return {
3480
3535
  [`CallExpression[callee.property.name='pipe'] > CallExpression[callee.name=${checkedOperatorsRegExp}]`]: checkNode,
@@ -3484,9 +3539,9 @@ var noUnsafeTakeuntilRule = ruleCreator({
3484
3539
  });
3485
3540
 
3486
3541
  // src/rules/prefer-observer.ts
3487
- var defaultOptions14 = [];
3542
+ var defaultOptions15 = [];
3488
3543
  var preferObserverRule = ruleCreator({
3489
- defaultOptions: defaultOptions14,
3544
+ defaultOptions: defaultOptions15,
3490
3545
  meta: {
3491
3546
  docs: {
3492
3547
  description: "Disallow passing separate handlers to `subscribe` and `tap`.",
@@ -3722,9 +3777,9 @@ var preferRootOperatorsRule = ruleCreator({
3722
3777
 
3723
3778
  // src/rules/suffix-subjects.ts
3724
3779
 
3725
- var defaultOptions15 = [];
3780
+ var defaultOptions16 = [];
3726
3781
  var suffixSubjectsRule = ruleCreator({
3727
- defaultOptions: defaultOptions15,
3782
+ defaultOptions: defaultOptions16,
3728
3783
  meta: {
3729
3784
  docs: {
3730
3785
  description: "Enforce the use of a suffix in subject identifiers.",
@@ -3921,9 +3976,9 @@ var suffixSubjectsRule = ruleCreator({
3921
3976
  // src/rules/throw-error.ts
3922
3977
 
3923
3978
 
3924
- var defaultOptions16 = [];
3979
+ var defaultOptions17 = [];
3925
3980
  var throwErrorRule = ruleCreator({
3926
- defaultOptions: defaultOptions16,
3981
+ defaultOptions: defaultOptions17,
3927
3982
  meta: {
3928
3983
  docs: {
3929
3984
  description: "Enforce passing only `Error` values to `throwError`.",
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  // package.json
2
2
  var name = "eslint-plugin-rxjs-x";
3
- var version = "0.7.2";
3
+ var version = "0.7.3";
4
4
 
5
5
  // src/configs/recommended.ts
6
6
  var createRecommendedConfig = (plugin2) => ({
@@ -74,11 +74,10 @@ var createStrictConfig = (plugin2) => ({
74
74
  });
75
75
 
76
76
  // src/rules/ban-observables.ts
77
- import { AST_NODE_TYPES } from "@typescript-eslint/utils";
77
+ import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
78
78
  import { stripIndent } from "common-tags";
79
79
 
80
- // src/utils.ts
81
- import { ESLintUtils } from "@typescript-eslint/utils";
80
+ // src/utils/create-regexp-for-words.ts
82
81
  function createRegExpForWords(config) {
83
82
  if (!(config == null ? void 0 : config.length)) {
84
83
  return void 0;
@@ -91,81 +90,11 @@ function createRegExpForWords(config) {
91
90
  const joined = words.map((word) => String.raw`(\b|_)${word}(\b|_)`).join("|");
92
91
  return new RegExp(`(${joined})`, flags);
93
92
  }
93
+
94
+ // src/utils/escape-regexp.ts
94
95
  function escapeRegExp(text) {
95
96
  return text.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&");
96
97
  }
97
- var REPO_URL = "https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x";
98
- var ruleCreator = ESLintUtils.RuleCreator(
99
- (name2) => `${REPO_URL}/blob/v${version}/docs/rules/${name2}.md`
100
- );
101
-
102
- // src/rules/ban-observables.ts
103
- var defaultOptions = [];
104
- var banObservablesRule = ruleCreator({
105
- defaultOptions,
106
- meta: {
107
- docs: {
108
- description: "Disallow banned observable creators."
109
- },
110
- messages: {
111
- forbidden: "RxJS observable is banned: {{name}}{{explanation}}."
112
- },
113
- schema: [
114
- {
115
- type: "object",
116
- description: stripIndent`
117
- An object containing keys that are names of observable factory functions
118
- and values that are either booleans or strings containing the explanation for the ban.`
119
- }
120
- ],
121
- type: "problem"
122
- },
123
- name: "ban-observables",
124
- create: (context) => {
125
- const bans = [];
126
- const [config] = context.options;
127
- if (!config) {
128
- return {};
129
- }
130
- Object.entries(config).forEach(([key, value]) => {
131
- if (value !== false) {
132
- bans.push({
133
- explanation: typeof value === "string" ? value : "",
134
- regExp: new RegExp(`^${key}$`)
135
- });
136
- }
137
- });
138
- function getFailure(name2) {
139
- for (let b = 0, length = bans.length; b < length; ++b) {
140
- const ban = bans[b];
141
- if (ban.regExp.test(name2)) {
142
- const explanation = ban.explanation ? `: ${ban.explanation}` : "";
143
- return {
144
- messageId: "forbidden",
145
- data: { name: name2, explanation }
146
- };
147
- }
148
- }
149
- return void 0;
150
- }
151
- return {
152
- "ImportDeclaration[source.value='rxjs'] > ImportSpecifier": (node) => {
153
- const identifier = node.imported;
154
- const name2 = identifier.type === AST_NODE_TYPES.Identifier ? identifier.name : identifier.value;
155
- const failure = getFailure(name2);
156
- if (failure) {
157
- context.report({
158
- ...failure,
159
- node: identifier
160
- });
161
- }
162
- }
163
- };
164
- }
165
- });
166
-
167
- // src/rules/ban-operators.ts
168
- import { stripIndent as stripIndent2 } from "common-tags";
169
98
 
170
99
  // src/etc/could-be-function.ts
171
100
  import ts2 from "typescript";
@@ -281,84 +210,84 @@ function getLoc(node) {
281
210
  }
282
211
 
283
212
  // src/etc/get-type-services.ts
284
- import { ESLintUtils as ESLintUtils2 } from "@typescript-eslint/utils";
213
+ import { ESLintUtils } from "@typescript-eslint/utils";
285
214
  import ts4 from "typescript";
286
215
 
287
216
  // src/etc/is.ts
288
- import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils";
217
+ import { AST_NODE_TYPES } from "@typescript-eslint/utils";
289
218
  function hasTypeAnnotation(node) {
290
219
  return "typeAnnotation" in node && !!node.typeAnnotation;
291
220
  }
292
221
  function isArrayExpression(node) {
293
- return node.type === AST_NODE_TYPES2.ArrayExpression;
222
+ return node.type === AST_NODE_TYPES.ArrayExpression;
294
223
  }
295
224
  function isArrayPattern(node) {
296
- return node.type === AST_NODE_TYPES2.ArrayPattern;
225
+ return node.type === AST_NODE_TYPES.ArrayPattern;
297
226
  }
298
227
  function isArrowFunctionExpression(node) {
299
- return node.type === AST_NODE_TYPES2.ArrowFunctionExpression;
228
+ return node.type === AST_NODE_TYPES.ArrowFunctionExpression;
300
229
  }
301
230
  function isBlockStatement(node) {
302
- return node.type === AST_NODE_TYPES2.BlockStatement;
231
+ return node.type === AST_NODE_TYPES.BlockStatement;
303
232
  }
304
233
  function isCallExpression(node) {
305
- return node.type === AST_NODE_TYPES2.CallExpression;
234
+ return node.type === AST_NODE_TYPES.CallExpression;
306
235
  }
307
236
  function isChainExpression(node) {
308
- return node.type === AST_NODE_TYPES2.ChainExpression;
237
+ return node.type === AST_NODE_TYPES.ChainExpression;
309
238
  }
310
239
  function isFunctionDeclaration(node) {
311
- return node.type === AST_NODE_TYPES2.FunctionDeclaration;
240
+ return node.type === AST_NODE_TYPES.FunctionDeclaration;
312
241
  }
313
242
  function isFunctionExpression(node) {
314
- return node.type === AST_NODE_TYPES2.FunctionExpression;
243
+ return node.type === AST_NODE_TYPES.FunctionExpression;
315
244
  }
316
245
  function isIdentifier(node) {
317
- return node.type === AST_NODE_TYPES2.Identifier;
246
+ return node.type === AST_NODE_TYPES.Identifier;
318
247
  }
319
248
  function isImportDeclaration(node) {
320
- return node.type === AST_NODE_TYPES2.ImportDeclaration;
249
+ return node.type === AST_NODE_TYPES.ImportDeclaration;
321
250
  }
322
251
  function isImportNamespaceSpecifier(node) {
323
- return node.type === AST_NODE_TYPES2.ImportNamespaceSpecifier;
252
+ return node.type === AST_NODE_TYPES.ImportNamespaceSpecifier;
324
253
  }
325
254
  function isImportSpecifier(node) {
326
- return node.type === AST_NODE_TYPES2.ImportSpecifier;
255
+ return node.type === AST_NODE_TYPES.ImportSpecifier;
327
256
  }
328
257
  function isJSXExpressionContainer(node) {
329
- return node.type === AST_NODE_TYPES2.JSXExpressionContainer;
258
+ return node.type === AST_NODE_TYPES.JSXExpressionContainer;
330
259
  }
331
260
  function isLiteral(node) {
332
- return node.type === AST_NODE_TYPES2.Literal;
261
+ return node.type === AST_NODE_TYPES.Literal;
333
262
  }
334
263
  function isMemberExpression(node) {
335
- return node.type === AST_NODE_TYPES2.MemberExpression;
264
+ return node.type === AST_NODE_TYPES.MemberExpression;
336
265
  }
337
266
  function isMethodDefinition(node) {
338
- return node.type === AST_NODE_TYPES2.MethodDefinition;
267
+ return node.type === AST_NODE_TYPES.MethodDefinition;
339
268
  }
340
269
  function isObjectExpression(node) {
341
- return node.type === AST_NODE_TYPES2.ObjectExpression;
270
+ return node.type === AST_NODE_TYPES.ObjectExpression;
342
271
  }
343
272
  function isObjectPattern(node) {
344
- return node.type === AST_NODE_TYPES2.ObjectPattern;
273
+ return node.type === AST_NODE_TYPES.ObjectPattern;
345
274
  }
346
275
  function isProgram(node) {
347
- return node.type === AST_NODE_TYPES2.Program;
276
+ return node.type === AST_NODE_TYPES.Program;
348
277
  }
349
278
  function isProperty(node) {
350
- return node.type === AST_NODE_TYPES2.Property;
279
+ return node.type === AST_NODE_TYPES.Property;
351
280
  }
352
281
  function isPropertyDefinition(node) {
353
- return node.type === AST_NODE_TYPES2.PropertyDefinition;
282
+ return node.type === AST_NODE_TYPES.PropertyDefinition;
354
283
  }
355
284
  function isUnaryExpression(node) {
356
- return node.type === AST_NODE_TYPES2.UnaryExpression;
285
+ return node.type === AST_NODE_TYPES.UnaryExpression;
357
286
  }
358
287
 
359
288
  // src/etc/get-type-services.ts
360
289
  function getTypeServices(context) {
361
- const services = ESLintUtils2.getParserServices(context);
290
+ const services = ESLintUtils.getParserServices(context);
362
291
  const { esTreeNodeToTSNodeMap, program, getTypeAtLocation } = services;
363
292
  const typeChecker = program.getTypeChecker();
364
293
  const couldBeType2 = (node, name2, qualified) => {
@@ -415,18 +344,125 @@ function getTypeServices(context) {
415
344
 
416
345
  // src/etc/is-import.ts
417
346
  import { DefinitionType } from "@typescript-eslint/scope-manager";
418
- import { AST_NODE_TYPES as AST_NODE_TYPES3 } from "@typescript-eslint/utils";
347
+ import { AST_NODE_TYPES as AST_NODE_TYPES2 } from "@typescript-eslint/utils";
419
348
  function isImport(scope, name2, source) {
420
349
  const variable = scope.variables.find((variable2) => variable2.name === name2);
421
350
  if (variable) {
422
351
  return variable.defs.some(
423
- (def) => def.type === DefinitionType.ImportBinding && def.parent.type === AST_NODE_TYPES3.ImportDeclaration && (typeof source === "string" ? def.parent.source.value === source : source.test(def.parent.source.value))
352
+ (def) => def.type === DefinitionType.ImportBinding && def.parent.type === AST_NODE_TYPES2.ImportDeclaration && (typeof source === "string" ? def.parent.source.value === source : source.test(def.parent.source.value))
424
353
  );
425
354
  }
426
355
  return scope.upper ? isImport(scope.upper, name2, source) : false;
427
356
  }
428
357
 
358
+ // src/utils/find-is-last-operator-order-valid.ts
359
+ function findIsLastOperatorOrderValid(pipeCallExpression, operatorsRegExp, allow) {
360
+ let isOrderValid = true;
361
+ let operatorNode;
362
+ for (let i = pipeCallExpression.arguments.length - 1; i >= 0; i--) {
363
+ const arg = pipeCallExpression.arguments[i];
364
+ if (operatorNode) {
365
+ break;
366
+ }
367
+ if (!isCallExpression(arg)) {
368
+ isOrderValid = false;
369
+ continue;
370
+ }
371
+ let operatorName;
372
+ if (isIdentifier(arg.callee)) {
373
+ operatorName = arg.callee.name;
374
+ } else if (isMemberExpression(arg.callee) && isIdentifier(arg.callee.property)) {
375
+ operatorName = arg.callee.property.name;
376
+ } else {
377
+ isOrderValid = false;
378
+ continue;
379
+ }
380
+ if (operatorsRegExp.test(operatorName)) {
381
+ operatorNode = arg.callee;
382
+ break;
383
+ }
384
+ if (!allow.includes(operatorName)) {
385
+ isOrderValid = false;
386
+ continue;
387
+ }
388
+ }
389
+ return { isOrderValid, operatorNode };
390
+ }
391
+
392
+ // src/utils/rule-creator.ts
393
+ import { ESLintUtils as ESLintUtils2 } from "@typescript-eslint/utils";
394
+ var REPO_URL = "https://github.com/JasonWeinzierl/eslint-plugin-rxjs-x";
395
+ var ruleCreator = ESLintUtils2.RuleCreator(
396
+ (name2) => `${REPO_URL}/blob/v${version}/docs/rules/${name2}.md`
397
+ );
398
+
399
+ // src/rules/ban-observables.ts
400
+ var defaultOptions = [];
401
+ var banObservablesRule = ruleCreator({
402
+ defaultOptions,
403
+ meta: {
404
+ docs: {
405
+ description: "Disallow banned observable creators."
406
+ },
407
+ messages: {
408
+ forbidden: "RxJS observable is banned: {{name}}{{explanation}}."
409
+ },
410
+ schema: [
411
+ {
412
+ type: "object",
413
+ description: stripIndent`
414
+ An object containing keys that are names of observable factory functions
415
+ and values that are either booleans or strings containing the explanation for the ban.`
416
+ }
417
+ ],
418
+ type: "problem"
419
+ },
420
+ name: "ban-observables",
421
+ create: (context) => {
422
+ const bans = [];
423
+ const [config] = context.options;
424
+ if (!config) {
425
+ return {};
426
+ }
427
+ Object.entries(config).forEach(([key, value]) => {
428
+ if (value !== false) {
429
+ bans.push({
430
+ explanation: typeof value === "string" ? value : "",
431
+ regExp: new RegExp(`^${key}$`)
432
+ });
433
+ }
434
+ });
435
+ function getFailure(name2) {
436
+ for (let b = 0, length = bans.length; b < length; ++b) {
437
+ const ban = bans[b];
438
+ if (ban.regExp.test(name2)) {
439
+ const explanation = ban.explanation ? `: ${ban.explanation}` : "";
440
+ return {
441
+ messageId: "forbidden",
442
+ data: { name: name2, explanation }
443
+ };
444
+ }
445
+ }
446
+ return void 0;
447
+ }
448
+ return {
449
+ "ImportDeclaration[source.value='rxjs'] > ImportSpecifier": (node) => {
450
+ const identifier = node.imported;
451
+ const name2 = identifier.type === AST_NODE_TYPES3.Identifier ? identifier.name : identifier.value;
452
+ const failure = getFailure(name2);
453
+ if (failure) {
454
+ context.report({
455
+ ...failure,
456
+ node: identifier
457
+ });
458
+ }
459
+ }
460
+ };
461
+ }
462
+ });
463
+
429
464
  // src/rules/ban-operators.ts
465
+ import { stripIndent as stripIndent2 } from "common-tags";
430
466
  var defaultOptions2 = [];
431
467
  var banOperatorsRule = ruleCreator({
432
468
  defaultOptions: defaultOptions2,
@@ -995,6 +1031,29 @@ import ts5 from "typescript";
995
1031
 
996
1032
  // src/constants.ts
997
1033
  var defaultObservable = String.raw`[Aa]ction(s|s\$|\$)$`;
1034
+ var DEFAULT_VALID_POST_COMPLETION_OPERATORS = [
1035
+ "count",
1036
+ "defaultIfEmpty",
1037
+ "endWith",
1038
+ "every",
1039
+ "finalize",
1040
+ "finally",
1041
+ "isEmpty",
1042
+ "last",
1043
+ "max",
1044
+ "min",
1045
+ "publish",
1046
+ "publishBehavior",
1047
+ "publishLast",
1048
+ "publishReplay",
1049
+ "reduce",
1050
+ "share",
1051
+ "shareReplay",
1052
+ "skipLast",
1053
+ "takeLast",
1054
+ "throwIfEmpty",
1055
+ "toArray"
1056
+ ];
998
1057
 
999
1058
  // src/rules/no-cyclic-action.ts
1000
1059
  function isTypeReference2(type) {
@@ -1765,8 +1824,10 @@ var noIgnoredSubscribeRule = ruleCreator({
1765
1824
  });
1766
1825
 
1767
1826
  // src/rules/no-ignored-subscription.ts
1827
+ import { stripIndent as stripIndent4 } from "common-tags";
1828
+ var defaultOptions7 = [];
1768
1829
  var noIgnoredSubscriptionRule = ruleCreator({
1769
- defaultOptions: [],
1830
+ defaultOptions: defaultOptions7,
1770
1831
  meta: {
1771
1832
  docs: {
1772
1833
  description: "Disallow ignoring the subscription returned by `subscribe`.",
@@ -1775,12 +1836,44 @@ var noIgnoredSubscriptionRule = ruleCreator({
1775
1836
  messages: {
1776
1837
  forbidden: "Ignoring returned subscriptions is forbidden."
1777
1838
  },
1778
- schema: [],
1839
+ schema: [
1840
+ {
1841
+ properties: {
1842
+ completers: { type: "array", items: { type: "string" }, description: "An array of operator names that will complete the observable and silence this rule.", default: ["takeUntil", "takeWhile", "take", "first", "last", "takeUntilDestroyed"] },
1843
+ postCompleters: { type: "array", items: { type: "string" }, description: "An array of operator names that are allowed to follow the completion operators.", default: DEFAULT_VALID_POST_COMPLETION_OPERATORS }
1844
+ },
1845
+ type: "object",
1846
+ description: stripIndent4`
1847
+ An object with optional \`completers\` and \`postCompleters\` properties.
1848
+ The \`completers\` property is an array containing the names of operators that will complete the observable and silence this rule.
1849
+ The \`postCompleters\` property is an array containing the names of the operators that are allowed to follow the completion operators.
1850
+ `
1851
+ }
1852
+ ],
1779
1853
  type: "problem"
1780
1854
  },
1781
1855
  name: "no-ignored-subscription",
1782
1856
  create: (context) => {
1857
+ const [config = {}] = context.options;
1858
+ const {
1859
+ completers = ["takeUntil", "takeWhile", "take", "first", "last", "takeUntilDestroyed"],
1860
+ postCompleters = DEFAULT_VALID_POST_COMPLETION_OPERATORS
1861
+ } = config;
1862
+ const checkedOperatorsRegExp = new RegExp(
1863
+ `^(${completers.join("|")})$`
1864
+ );
1783
1865
  const { couldBeObservable, couldBeType: couldBeType2 } = getTypeServices(context);
1866
+ function hasAllowedOperator(node) {
1867
+ if (isCallExpression(node) && isMemberExpression(node.callee) && isIdentifier(node.callee.property) && node.callee.property.name === "pipe") {
1868
+ const { isOrderValid, operatorNode } = findIsLastOperatorOrderValid(
1869
+ node,
1870
+ checkedOperatorsRegExp,
1871
+ postCompleters
1872
+ );
1873
+ return isOrderValid && !!operatorNode;
1874
+ }
1875
+ return false;
1876
+ }
1784
1877
  return {
1785
1878
  "ExpressionStatement > CallExpression > MemberExpression[property.name='subscribe']": (node) => {
1786
1879
  if (couldBeObservable(node.object)) {
@@ -1788,6 +1881,9 @@ var noIgnoredSubscriptionRule = ruleCreator({
1788
1881
  if (callExpression.arguments.length === 1 && couldBeType2(callExpression.arguments[0], "Subscriber")) {
1789
1882
  return;
1790
1883
  }
1884
+ if (hasAllowedOperator(node.object)) {
1885
+ return;
1886
+ }
1791
1887
  context.report({
1792
1888
  messageId: "forbidden",
1793
1889
  node: node.property
@@ -1862,9 +1958,9 @@ function isParenthesised(sourceCode, node) {
1862
1958
  const after = sourceCode.getTokenAfter(node);
1863
1959
  return before && after && before.value === "(" && before.range[1] <= node.range[0] && after.value === ")" && after.range[0] >= node.range[1];
1864
1960
  }
1865
- var defaultOptions7 = [];
1961
+ var defaultOptions8 = [];
1866
1962
  var noImplicitAnyCatchRule = ruleCreator({
1867
- defaultOptions: defaultOptions7,
1963
+ defaultOptions: defaultOptions8,
1868
1964
  meta: {
1869
1965
  docs: {
1870
1966
  description: "Disallow implicit `any` error parameters in `catchError` operators.",
@@ -2138,9 +2234,9 @@ function parseChecksVoidReturn(checksVoidReturn) {
2138
2234
  };
2139
2235
  }
2140
2236
  }
2141
- var defaultOptions8 = [];
2237
+ var defaultOptions9 = [];
2142
2238
  var noMisusedObservablesRule = ruleCreator({
2143
- defaultOptions: defaultOptions8,
2239
+ defaultOptions: defaultOptions9,
2144
2240
  meta: {
2145
2241
  docs: {
2146
2242
  description: "Disallow Observables in places not designed to handle them.",
@@ -2628,9 +2724,9 @@ function isExpressionObserver(expressionStatement, couldBeType2) {
2628
2724
 
2629
2725
  // src/rules/no-sharereplay.ts
2630
2726
  import { AST_NODE_TYPES as AST_NODE_TYPES7 } from "@typescript-eslint/utils";
2631
- var defaultOptions9 = [];
2727
+ var defaultOptions10 = [];
2632
2728
  var noSharereplayRule = ruleCreator({
2633
- defaultOptions: defaultOptions9,
2729
+ defaultOptions: defaultOptions10,
2634
2730
  meta: {
2635
2731
  docs: {
2636
2732
  description: "Disallow unsafe `shareReplay` usage.",
@@ -3069,10 +3165,10 @@ var noUnboundMethodsRule = ruleCreator({
3069
3165
  });
3070
3166
 
3071
3167
  // src/rules/no-unsafe-catch.ts
3072
- import { stripIndent as stripIndent4 } from "common-tags";
3073
- var defaultOptions10 = [];
3168
+ import { stripIndent as stripIndent5 } from "common-tags";
3169
+ var defaultOptions11 = [];
3074
3170
  var noUnsafeCatchRule = ruleCreator({
3075
- defaultOptions: defaultOptions10,
3171
+ defaultOptions: defaultOptions11,
3076
3172
  meta: {
3077
3173
  docs: {
3078
3174
  description: "Disallow unsafe `catchError` usage in effects and epics.",
@@ -3087,7 +3183,7 @@ var noUnsafeCatchRule = ruleCreator({
3087
3183
  observable: { type: "string", description: "A RegExp that matches an effect or epic's actions observable.", default: defaultObservable }
3088
3184
  },
3089
3185
  type: "object",
3090
- description: stripIndent4`
3186
+ description: stripIndent5`
3091
3187
  An optional object with an optional \`observable\` property.
3092
3188
  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.`
3093
3189
  }
@@ -3130,10 +3226,10 @@ var noUnsafeCatchRule = ruleCreator({
3130
3226
  });
3131
3227
 
3132
3228
  // src/rules/no-unsafe-first.ts
3133
- import { stripIndent as stripIndent5 } from "common-tags";
3134
- var defaultOptions11 = [];
3229
+ import { stripIndent as stripIndent6 } from "common-tags";
3230
+ var defaultOptions12 = [];
3135
3231
  var noUnsafeFirstRule = ruleCreator({
3136
- defaultOptions: defaultOptions11,
3232
+ defaultOptions: defaultOptions12,
3137
3233
  meta: {
3138
3234
  docs: {
3139
3235
  description: "Disallow unsafe `first`/`take` usage in effects and epics.",
@@ -3148,7 +3244,7 @@ var noUnsafeFirstRule = ruleCreator({
3148
3244
  observable: { type: "string", description: "A RegExp that matches an effect or epic's actions observable.", default: defaultObservable }
3149
3245
  },
3150
3246
  type: "object",
3151
- description: stripIndent5`
3247
+ description: stripIndent6`
3152
3248
  An optional object with an optional \`observable\` property.
3153
3249
  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.`
3154
3250
  }
@@ -3253,11 +3349,11 @@ var noUnsafeSubjectNext = ruleCreator({
3253
3349
  });
3254
3350
 
3255
3351
  // src/rules/no-unsafe-switchmap.ts
3256
- import { stripIndent as stripIndent6 } from "common-tags";
3352
+ import { stripIndent as stripIndent7 } from "common-tags";
3257
3353
  import decamelize from "decamelize";
3258
- var defaultOptions12 = [];
3354
+ var defaultOptions13 = [];
3259
3355
  var noUnsafeSwitchmapRule = ruleCreator({
3260
- defaultOptions: defaultOptions12,
3356
+ defaultOptions: defaultOptions13,
3261
3357
  meta: {
3262
3358
  docs: {
3263
3359
  description: "Disallow unsafe `switchMap` usage in effects and epics.",
@@ -3290,7 +3386,7 @@ var noUnsafeSwitchmapRule = ruleCreator({
3290
3386
  }
3291
3387
  },
3292
3388
  type: "object",
3293
- description: stripIndent6`
3389
+ description: stripIndent7`
3294
3390
  An optional object with optional \`allow\`, \`disallow\` and \`observable\` properties.
3295
3391
  The properties can be specified as regular expression strings or as arrays of words.
3296
3392
  The \`allow\` or \`disallow\` properties are mutually exclusive. Whether or not
@@ -3379,33 +3475,10 @@ var noUnsafeSwitchmapRule = ruleCreator({
3379
3475
  });
3380
3476
 
3381
3477
  // src/rules/no-unsafe-takeuntil.ts
3382
- import { stripIndent as stripIndent7 } from "common-tags";
3383
- var defaultOptions13 = [];
3384
- var allowedOperators = [
3385
- "count",
3386
- "defaultIfEmpty",
3387
- "endWith",
3388
- "every",
3389
- "finalize",
3390
- "finally",
3391
- "isEmpty",
3392
- "last",
3393
- "max",
3394
- "min",
3395
- "publish",
3396
- "publishBehavior",
3397
- "publishLast",
3398
- "publishReplay",
3399
- "reduce",
3400
- "share",
3401
- "shareReplay",
3402
- "skipLast",
3403
- "takeLast",
3404
- "throwIfEmpty",
3405
- "toArray"
3406
- ];
3478
+ import { stripIndent as stripIndent8 } from "common-tags";
3479
+ var defaultOptions14 = [];
3407
3480
  var noUnsafeTakeuntilRule = ruleCreator({
3408
- defaultOptions: defaultOptions13,
3481
+ defaultOptions: defaultOptions14,
3409
3482
  meta: {
3410
3483
  docs: {
3411
3484
  description: "Disallow applying operators after `takeUntil`.",
@@ -3419,10 +3492,10 @@ var noUnsafeTakeuntilRule = ruleCreator({
3419
3492
  {
3420
3493
  properties: {
3421
3494
  alias: { type: "array", items: { type: "string" }, description: "An array of operator names that should be treated similarly to `takeUntil`." },
3422
- allow: { type: "array", items: { type: "string" }, description: "An array of operator names that are allowed to follow `takeUntil`.", default: allowedOperators }
3495
+ allow: { type: "array", items: { type: "string" }, description: "An array of operator names that are allowed to follow `takeUntil`.", default: DEFAULT_VALID_POST_COMPLETION_OPERATORS }
3423
3496
  },
3424
3497
  type: "object",
3425
- description: stripIndent7`
3498
+ description: stripIndent8`
3426
3499
  An optional object with optional \`alias\` and \`allow\` properties.
3427
3500
  The \`alias\` property is an array containing the names of operators that aliases for \`takeUntil\`.
3428
3501
  The \`allow\` property is an array containing the names of the operators that are allowed to follow \`takeUntil\`.`
@@ -3434,7 +3507,7 @@ var noUnsafeTakeuntilRule = ruleCreator({
3434
3507
  create: (context) => {
3435
3508
  let checkedOperatorsRegExp = /^takeUntil$/;
3436
3509
  const [config = {}] = context.options;
3437
- const { alias, allow = allowedOperators } = config;
3510
+ const { alias, allow = DEFAULT_VALID_POST_COMPLETION_OPERATORS } = config;
3438
3511
  if (alias) {
3439
3512
  checkedOperatorsRegExp = new RegExp(
3440
3513
  `^(${alias.concat("takeUntil").join("|")})$`
@@ -3446,35 +3519,17 @@ var noUnsafeTakeuntilRule = ruleCreator({
3446
3519
  if (!pipeCallExpression.arguments || !couldBeObservable(pipeCallExpression)) {
3447
3520
  return;
3448
3521
  }
3449
- pipeCallExpression.arguments.reduceRight((state, arg) => {
3450
- if (state === "taken") {
3451
- return state;
3452
- }
3453
- if (!isCallExpression(arg)) {
3454
- return "disallowed";
3455
- }
3456
- let operatorName;
3457
- if (isIdentifier(arg.callee)) {
3458
- operatorName = arg.callee.name;
3459
- } else if (isMemberExpression(arg.callee) && isIdentifier(arg.callee.property)) {
3460
- operatorName = arg.callee.property.name;
3461
- } else {
3462
- return "disallowed";
3463
- }
3464
- if (checkedOperatorsRegExp.test(operatorName)) {
3465
- if (state === "disallowed") {
3466
- context.report({
3467
- messageId: "forbidden",
3468
- node: arg.callee
3469
- });
3470
- }
3471
- return "taken";
3472
- }
3473
- if (!allow.includes(operatorName)) {
3474
- return "disallowed";
3475
- }
3476
- return state;
3477
- }, "allowed");
3522
+ const { isOrderValid, operatorNode } = findIsLastOperatorOrderValid(
3523
+ pipeCallExpression,
3524
+ checkedOperatorsRegExp,
3525
+ allow
3526
+ );
3527
+ if (!isOrderValid && operatorNode) {
3528
+ context.report({
3529
+ messageId: "forbidden",
3530
+ node: operatorNode
3531
+ });
3532
+ }
3478
3533
  }
3479
3534
  return {
3480
3535
  [`CallExpression[callee.property.name='pipe'] > CallExpression[callee.name=${checkedOperatorsRegExp}]`]: checkNode,
@@ -3484,9 +3539,9 @@ var noUnsafeTakeuntilRule = ruleCreator({
3484
3539
  });
3485
3540
 
3486
3541
  // src/rules/prefer-observer.ts
3487
- var defaultOptions14 = [];
3542
+ var defaultOptions15 = [];
3488
3543
  var preferObserverRule = ruleCreator({
3489
- defaultOptions: defaultOptions14,
3544
+ defaultOptions: defaultOptions15,
3490
3545
  meta: {
3491
3546
  docs: {
3492
3547
  description: "Disallow passing separate handlers to `subscribe` and `tap`.",
@@ -3722,9 +3777,9 @@ var preferRootOperatorsRule = ruleCreator({
3722
3777
 
3723
3778
  // src/rules/suffix-subjects.ts
3724
3779
  import { AST_NODE_TYPES as AST_NODE_TYPES9, ESLintUtils as ESLintUtils11 } from "@typescript-eslint/utils";
3725
- var defaultOptions15 = [];
3780
+ var defaultOptions16 = [];
3726
3781
  var suffixSubjectsRule = ruleCreator({
3727
- defaultOptions: defaultOptions15,
3782
+ defaultOptions: defaultOptions16,
3728
3783
  meta: {
3729
3784
  docs: {
3730
3785
  description: "Enforce the use of a suffix in subject identifiers.",
@@ -3921,9 +3976,9 @@ var suffixSubjectsRule = ruleCreator({
3921
3976
  // src/rules/throw-error.ts
3922
3977
  import { ESLintUtils as ESLintUtils12 } from "@typescript-eslint/utils";
3923
3978
  import * as tsutils4 from "ts-api-utils";
3924
- var defaultOptions16 = [];
3979
+ var defaultOptions17 = [];
3925
3980
  var throwErrorRule = ruleCreator({
3926
- defaultOptions: defaultOptions16,
3981
+ defaultOptions: defaultOptions17,
3927
3982
  meta: {
3928
3983
  docs: {
3929
3984
  description: "Enforce passing only `Error` values to `throwError`.",
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "eslint-plugin-rxjs-x",
3
3
  "type": "commonjs",
4
- "version": "0.7.2",
4
+ "version": "0.7.3",
5
5
  "packageManager": "yarn@4.9.1+sha512.f95ce356460e05be48d66401c1ae64ef84d163dd689964962c6888a9810865e39097a5e9de748876c2e0bf89b232d583c33982773e9903ae7a76257270986538",
6
6
  "description": "ESLint v9+ plugin for RxJS",
7
7
  "author": "Jason Weinzierl <weinzierljason@gmail.com>",
@@ -72,28 +72,28 @@
72
72
  }
73
73
  },
74
74
  "devDependencies": {
75
- "@eslint/js": "^9.24.0",
76
- "@stylistic/eslint-plugin": "^4.2.0",
75
+ "@eslint/js": "^9.27.0",
76
+ "@stylistic/eslint-plugin": "^4.4.0",
77
77
  "@types/common-tags": "^1.8.4",
78
78
  "@types/node": "~18.18.0",
79
- "@typescript-eslint/rule-tester": "^8.30.1",
79
+ "@typescript-eslint/rule-tester": "^8.32.1",
80
80
  "@typescript/vfs": "^1.6.1",
81
- "@vitest/coverage-v8": "^3.1.1",
82
- "@vitest/eslint-plugin": "^1.1.42",
83
- "bumpp": "^10.1.0",
84
- "eslint": "^9.24.0",
81
+ "@vitest/coverage-v8": "^3.1.4",
82
+ "@vitest/eslint-plugin": "^1.2.1",
83
+ "bumpp": "^10.1.1",
84
+ "eslint": "^9.27.0",
85
85
  "eslint-config-flat-gitignore": "^2.1.0",
86
86
  "eslint-doc-generator": "^2.1.2",
87
- "eslint-import-resolver-typescript": "^4.3.2",
87
+ "eslint-import-resolver-typescript": "^4.4.0",
88
88
  "eslint-plugin-eslint-plugin": "^6.4.0",
89
- "eslint-plugin-import-x": "^4.10.5",
90
- "eslint-plugin-n": "^17.17.0",
91
- "markdownlint-cli2": "^0.17.2",
89
+ "eslint-plugin-import-x": "^4.13.1",
90
+ "eslint-plugin-n": "^17.18.0",
91
+ "markdownlint-cli2": "^0.18.1",
92
92
  "rxjs": "^7.8.2",
93
- "tsup": "^8.4.0",
93
+ "tsup": "^8.5.0",
94
94
  "typescript": "~5.8.3",
95
- "typescript-eslint": "^8.30.1",
96
- "vitest": "^3.1.1"
95
+ "typescript-eslint": "^8.32.1",
96
+ "vitest": "^3.1.4"
97
97
  },
98
98
  "engines": {
99
99
  "node": "^18.18.0 || ^20.9.0 || >= 21.1.0"