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