Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

No nested pipe similar to no-nested-subscribe #139

Closed
wants to merge 5 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 60 additions & 0 deletions docs/rules/no-nested-pipe.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# Avoid nested `pipe` calls (`no-nested-pipe`)

This rule effects failures if `pipe` is called within a `pipe` handler.

## Rule details

Examples of **incorrect** code for this rule:

```ts
import { switchMap, map, of } from 'rxjs';

of('searchText1', 'searchText2')
.pipe(
switchMap(searchText => {
return callSearchAPI(searchText).pipe(
map(response => {
console.log(response);
return 'final ' + response;
// considering more lines here
})
);
})
)
.subscribe(value => console.log(value));

function callSearchAPI(searchText) {
return of('new' + searchText);
}


```

Examples of **correct** code for this rule:

```ts
import { switchMap, map, of } from 'rxjs';

of('searchText1', 'searchText2')
.pipe(
switchMap(searchText => {
return callSearchAPI(searchText);
})
)
.subscribe(value => console.log(value));

function callSearchAPI(searchText) {
return of('new' + searchText).pipe(
map(response => {
console.log(response);
return 'final ' + response;
// considering more lines here
})
);
}

```

## Options

This rule has no options.
59 changes: 59 additions & 0 deletions source/rules/no-nested-pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
/**
* @license Use of this source code is governed by an MIT-style license that
* can be found in the LICENSE file at https://github.com/cartant/eslint-plugin-rxjs
*/

import { TSESTree as es } from "@typescript-eslint/experimental-utils";
import { getParent, getTypeServices } from "eslint-etc";
import { ruleCreator } from "../utils";

const rule = ruleCreator({
defaultOptions: [],
meta: {
docs: {
description: "Forbids the calling of `pipe` within a `pipe` callback.",
recommended: "error",
},
fixable: undefined,
hasSuggestions: false,
messages: {
forbidden: "Nested pipe calls are forbidden.",
},
schema: [],
type: "problem",
},
name: "no-nested-pipe",
create: (context) => {
const { couldBeObservable, couldBeType } = getTypeServices(context);
const argumentsMap = new WeakMap<es.Node, void>();
return {
[`CallExpression > MemberExpression[property.name='pipe']`]: (
node: es.MemberExpression
) => {
if (
!couldBeObservable(node.object) &&
!couldBeType(node.object, "Pipeable")
) {
return;
}
const callExpression = getParent(node) as es.CallExpression;
let parent = getParent(callExpression);
while (parent) {
if (argumentsMap.has(parent)) {
context.report({
messageId: "forbidden",
node: node.property,
});
return;
}
parent = getParent(parent);
}
for (const arg of callExpression.arguments) {
argumentsMap.set(arg);
}
},
};
},
});

export = rule;
94 changes: 94 additions & 0 deletions tests/rules/no-nested-pipe.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
/**
* @license Use of this source code is governed by an MIT-style license that
* can be found in the LICENSE file at https://github.com/cartant/eslint-plugin-rxjs
*/

import { stripIndent } from "common-tags";
import { fromFixture } from "eslint-etc";
import rule = require("../../source/rules/no-nested-pipe");
import { ruleTester } from "../utils";

ruleTester({ types: true }).run("no-nested-pipe", rule, {
valid: [
stripIndent`
// not nested in pipe
import { Observable,of,switchMap } from "rxjs";
of(47).pipe(switchMap(value => {
console.log('new' ,value);
})).subscribe(value => {
console.log(value);
})
`,
stripIndent`
// not nested in pipe
import { Observable,switchMap } from "rxjs";
of(47).pipe(switchMap(value => {
return someFunction(value)
})).subscribe(value => {
console.log(value);
});
function someFunction(someParam: Observable<any>): Observable<any> {
return of(43).pipe(
switchMap(value => {value + someParam})
)
}
`,
stripIndent`
// not nested in pipe using function move to separate function
import { Observable,switchMap } from "rxjs";
of(47).pipe(switchMap(value => {
return someFunction1(value)
}),
switchMap(value => {
return someFunction2(value)
})
).subscribe(value => {
console.log(value);
});
function someFunction1(someParam: Observable<any>): Observable<any> {
return of(43).pipe(
switchMap(value => {value + someParam})
)
};
function someFunction2(someParam: Observable<any>): Observable<any> {
return of(43).pipe(
switchMap(value => {value + someParam})
)
}
`,
],
invalid: [
fromFixture(
stripIndent`
// nested in pipe
import { of,switchMap,tap } from "rxjs";
of("foo").pipe(
switchMap(value => {
return of("bar").pipe(tap(value => { console.log(value)})
~~~~ [forbidden]
)})
).subscribe(value => {
console.log(value);
});
`
),
fromFixture(
stripIndent`
// nested in pipe with parallel pipes
import { of,switchMap,tap } from "rxjs";
of("foo").pipe(
switchMap(value => {
return of("bar").pipe(tap(value => { console.log(value)})
~~~~ [forbidden]
)}),
switchMap(value => {
return of("bar").pipe(tap(value => { console.log(value)})
~~~~ [forbidden]
)})
).subscribe(value => {
console.log(value);
});
`
),
],
});