Skip to content
This repository was archived by the owner on Oct 3, 2024. It is now read-only.

Commit afd2c19

Browse files
Rule S3531: Generators should yield something (#236)
Co-authored-by: Tibor Blenessy <[email protected]>
1 parent d3130ac commit afd2c19

File tree

5 files changed

+216
-0
lines changed

5 files changed

+216
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ SonarJS rules for ESLint to detect bugs and suspicious patterns in your code.
88

99
Rules in this category aim to find places in code which have a high chance of being bugs, i.e. don't work as intended.
1010

11+
* Generators should "yield" something ([`generator-without-yield`])
1112
* All branches in a conditional structure should not have exactly the same implementation ([`no-all-duplicated-branches`])
1213
* Collection elements should not be replaced unconditionally ([`no-element-overwrite`])
1314
* Empty collections should not be accessed or iterated ([`no-empty-collection`])
@@ -44,6 +45,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
4445
* A "while" loop should be used instead of a "for" loop ([`prefer-while`]) (:wrench: *fixable*)
4546

4647
[`cognitive-complexity`]: ./docs/rules/cognitive-complexity.md
48+
[`generator-without-yield`]: ./docs/rules/generator-without-yield.md
4749
[`max-switch-cases`]: ./docs/rules/max-switch-cases.md
4850
[`no-all-duplicated-branches`]: ./docs/rules/no-all-duplicated-branches.md
4951
[`no-collapsible-if`]: ./docs/rules/no-collapsible-if.md
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# generator-without-yield
2+
3+
A generator without a `yield` statement is at best confusing, and at worst a bug in your code, since the iterator produced by your code will always be empty.
4+
5+
## Noncompliant Code Example
6+
7+
```javascript
8+
function* myGen(a, b) { // Noncompliant
9+
let answer = 0;
10+
answer += a * b;
11+
}
12+
```
13+
14+
## Compliant Solution
15+
16+
```javascript
17+
function* myGen(a, b) {
18+
let answer = 0;
19+
while (answer < 42) {
20+
answer += a * b;
21+
yield answer;
22+
}
23+
}
24+
```

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import { TSESLint } from '@typescript-eslint/experimental-utils';
2121

2222
const sonarjsRules: [string, TSESLint.Linter.RuleLevel][] = [
2323
['cognitive-complexity', 'error'],
24+
['generator-without-yield', 'error'],
2425
['max-switch-cases', 'error'],
2526
['no-all-duplicated-branches', 'error'],
2627
['no-collapsible-if', 'error'],
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018-2021 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
21+
import { TSESTree } from '@typescript-eslint/experimental-utils';
22+
import { getMainFunctionTokenLocation } from '../utils/locations';
23+
import { Rule } from '../utils/types';
24+
25+
const MESSAGE = 'Add a "yield" statement to this generator.';
26+
27+
const rule: Rule.RuleModule = {
28+
meta: {
29+
type: 'problem',
30+
},
31+
create(context: Rule.RuleContext) {
32+
const yieldStack: number[] = [];
33+
34+
function enterFunction() {
35+
yieldStack.push(0);
36+
}
37+
38+
function exitFunction(node: TSESTree.Node) {
39+
const functionNode = node as TSESTree.FunctionExpression | TSESTree.FunctionDeclaration;
40+
const countYield = yieldStack.pop();
41+
if (countYield === 0 && functionNode.body.body.length > 0) {
42+
context.report({
43+
message: MESSAGE,
44+
loc: getMainFunctionTokenLocation(functionNode, node.parent, context),
45+
});
46+
}
47+
}
48+
49+
return {
50+
':function[generator=true]': enterFunction,
51+
':function[generator=true]:exit': exitFunction,
52+
YieldExpression() {
53+
if (yieldStack.length > 0) {
54+
yieldStack[yieldStack.length - 1] += 1;
55+
}
56+
},
57+
};
58+
},
59+
};
60+
61+
export = rule;
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018-2021 SonarSource SA
4+
* mailto:info AT sonarsource DOT com
5+
*
6+
* This program is free software; you can redistribute it and/or
7+
* modify it under the terms of the GNU Lesser General Public
8+
* License as published by the Free Software Foundation; either
9+
* version 3 of the License, or (at your option) any later version.
10+
*
11+
* This program is distributed in the hope that it will be useful,
12+
* but WITHOUT ANY WARRANTY; without even the implied warranty of
13+
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14+
* Lesser General Public License for more details.
15+
*
16+
* You should have received a copy of the GNU Lesser General Public License
17+
* along with this program; if not, write to the Free Software Foundation,
18+
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19+
*/
20+
import { ruleTester } from '../rule-tester';
21+
import * as rule from '../../src/rules/generator-without-yield';
22+
23+
ruleTester.run('Generator without yield', rule, {
24+
valid: [
25+
{
26+
code: `
27+
var foo = function * () {
28+
}
29+
`,
30+
},
31+
{
32+
code: `
33+
var foo = function * () {
34+
let a = 3;
35+
yield a;
36+
}
37+
`,
38+
},
39+
{
40+
code: `
41+
function someFunction() {
42+
doSomething();
43+
}
44+
`,
45+
},
46+
],
47+
invalid: [
48+
{
49+
code: `
50+
function * foo() {
51+
// ^^^
52+
return 1;
53+
}
54+
`,
55+
errors: [
56+
{
57+
message: `Add a "yield" statement to this generator.`,
58+
line: 2,
59+
endLine: 2,
60+
column: 24,
61+
endColumn: 27,
62+
},
63+
],
64+
},
65+
{
66+
code: `
67+
var foo = function * () {
68+
// ^^^^^^^^
69+
doSomething();
70+
}
71+
`,
72+
errors: [
73+
{
74+
message: `Add a "yield" statement to this generator.`,
75+
line: 2,
76+
endLine: 2,
77+
column: 23,
78+
endColumn: 31,
79+
},
80+
],
81+
},
82+
{
83+
code: `
84+
var foo = function * bar () {
85+
doSomething();
86+
}
87+
`,
88+
errors: 1,
89+
},
90+
{
91+
code: `
92+
function * foo() { // Noncompliant
93+
// ^^^
94+
function * bar() { // OK
95+
yield 1;
96+
}
97+
}
98+
`,
99+
errors: [
100+
{
101+
message: `Add a "yield" statement to this generator.`,
102+
line: 2,
103+
endLine: 2,
104+
column: 24,
105+
endColumn: 27,
106+
},
107+
],
108+
},
109+
{
110+
code: `
111+
class A {
112+
*foo() {
113+
doSomething();
114+
}
115+
}
116+
`,
117+
errors: [
118+
{
119+
message: `Add a "yield" statement to this generator.`,
120+
line: 3,
121+
endLine: 3,
122+
column: 16,
123+
endColumn: 19,
124+
},
125+
],
126+
},
127+
],
128+
});

0 commit comments

Comments
 (0)