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

Commit cd2257a

Browse files
add rule no-collapsible-if (#110)
1 parent df14ad9 commit cd2257a

File tree

6 files changed

+346
-0
lines changed

6 files changed

+346
-0
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
2424

2525
* Cognitive Complexity of functions should not be too high ([`cognitive-complexity`])
2626
* "switch" statements should not have too many "case" clauses ([`max-switch-cases`])
27+
* Collapsible "if" statements should be merged ([`no-collapsible-if`])
2728
* String literals should not be duplicated ([`no-duplicate-string`])
2829
* Two branches in a conditional structure should not have exactly the same implementation ([`no-duplicated-branches`])
2930
* Functions should not have identical implementations ([`no-identical-functions`])
@@ -39,6 +40,7 @@ Code Smells, or maintainability issues, are raised for places of code which migh
3940
[`cognitive-complexity`]: ./docs/rules/cognitive-complexity.md
4041
[`max-switch-cases`]: ./docs/rules/max-switch-cases.md
4142
[`no-all-duplicated-branches`]: ./docs/rules/no-all-duplicated-branches.md
43+
[`no-collapsible-if`]: ./docs/rules/no-collapsible-if.md
4244
[`no-duplicate-string`]: ./docs/rules/no-duplicate-string.md
4345
[`no-duplicated-branches`]: ./docs/rules/no-duplicated-branches.md
4446
[`no-element-overwrite`]: ./docs/rules/no-element-overwrite.md

docs/rules/no-collapsible-if.md

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# no-collapsible-if
2+
3+
Merging collapsible if statements increases the code's readability.
4+
5+
## Noncompliant Code Example
6+
7+
```javascript
8+
if (x != undefined) {
9+
if (y === 2) {
10+
// ...
11+
}
12+
}
13+
```
14+
15+
## Compliant Solution
16+
17+
```javascript
18+
if (x != undefined && y === 2) {
19+
// ...
20+
}
21+
```

ruling/snapshots/no-collapsible-if

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
src/angular.js/src/ng/location.js: 933
2+
src/angular.js/src/ng/parse.js: 1597,1614
3+
src/angular.js/src/ngRoute/route.js: 661,662
4+
src/brackets/src/command/KeyBindingManager.js: 685,707
5+
src/brackets/src/command/Menus.js: 675,1015
6+
src/brackets/src/document/Document.js: 166,183,302
7+
src/brackets/src/document/DocumentCommandHandlers.js: 877
8+
src/brackets/src/document/DocumentManager.js: 128
9+
src/brackets/src/editor/CodeHintManager.js: 592
10+
src/brackets/src/editor/Editor.js: 876,2435
11+
src/brackets/src/editor/ImageViewer.js: 332
12+
src/brackets/src/extensions/default/CSSCodeHints/main.js: 98,166
13+
src/brackets/src/extensions/default/DebugCommands/main.js: 495
14+
src/brackets/src/extensions/default/HTMLCodeHints/main.js: 91,135
15+
src/brackets/src/extensions/default/InlineColorEditor/ColorEditor.js: 454,686,687
16+
src/brackets/src/extensions/default/InlineColorEditor/InlineColorEditor.js: 267
17+
src/brackets/src/extensions/default/InlineTimingFunctionEditor/BezierCurveEditor.js: 168
18+
src/brackets/src/extensions/default/InlineTimingFunctionEditor/StepEditor.js: 180
19+
src/brackets/src/extensions/default/JavaScriptCodeHints/main.js: 486
20+
src/brackets/src/extensions/default/JavaScriptCodeHints/thirdparty/requirejs/require.js: 12,16
21+
src/brackets/src/extensions/default/JavaScriptQuickEdit/unittests.js: 152
22+
src/brackets/src/extensions/default/NavigationAndHistory/NavigationProvider.js: 341
23+
src/brackets/src/extensions/default/QuickView/main.js: 604
24+
src/brackets/src/extensions/default/StaticServer/node/node_modules/connect/node_modules/multiparty/node_modules/readable-stream/zlib.js: 232,239,246,253,260,270
25+
src/brackets/src/extensions/default/UrlCodeHints/main.js: 115,345
26+
src/brackets/src/filesystem/FileIndex.js: 122
27+
src/brackets/src/JSUtils/ScopeManager.js: 541
28+
src/brackets/src/language/CSSUtils.js: 1098,1393,1654
29+
src/brackets/src/language/HTMLDOMDiff.js: 406,484
30+
src/brackets/src/language/HTMLSimpleDOM.js: 460
31+
src/brackets/src/language/HTMLUtils.js: 137
32+
src/brackets/src/language/JSUtils.js: 137,149,159
33+
src/brackets/src/language/XMLUtils.js: 78,90,91,103,241
34+
src/brackets/src/LiveDevelopment/LiveDevMultiBrowser.js: 549,742
35+
src/brackets/src/LiveDevelopment/MultiBrowserImpl/language/HTMLInstrumentation.js: 149
36+
src/brackets/src/LiveDevelopment/MultiBrowserImpl/language/HTMLSimpleDOM.js: 422
37+
src/brackets/src/project/FileSyncManager.js: 128
38+
src/brackets/src/search/FindBar.js: 366,367,368
39+
src/brackets/src/search/FindInFiles.js: 383,788
40+
src/brackets/src/utils/DragAndDrop.js: 110
41+
src/brackets/src/utils/DropdownEventHandler.js: 309
42+
src/brackets/src/utils/StringMatch.js: 351
43+
src/brackets/src/view/MainViewManager.js: 468
44+
src/brackets/src/view/Pane.js: 948
45+
src/brackets/src/widgets/Dialogs.js: 193
46+
src/Chart.js/src/core/core.controller.js: 918
47+
src/Chart.js/src/core/core.helpers.js: 584
48+
src/Chart.js/src/core/core.ticks.js: 39
49+
src/Chart.js/src/scales/scale.linearbase.js: 41,131
50+
src/create-react-app/packages/react-dev-utils/webpackHotDevClient.js: 90
51+
src/create-react-app/packages/react-scripts/scripts/eject.js: 154
52+
src/express/lib/response.js: 200
53+
src/freeCodeCamp/public/js/lib/loop-protect/loop-protect.js: 241
54+
src/freeCodeCamp/server/boot/authentication.js: 129
55+
src/Ghost/core/server/api/users.js: 138
56+
src/Ghost/core/server/api/utils.js: 349
57+
src/Ghost/core/server/data/validation/index.js: 198,213,229,250
58+
src/Ghost/core/server/models/base/index.js: 107,208,214,220,226,248,255,261,802,816
59+
src/Ghost/core/server/models/plugins/collision.js: 57
60+
src/Ghost/core/server/models/subscriber.js: 65
61+
src/Ghost/core/server/models/user.js: 335
62+
src/Ghost/core/server/services/permissions/public.js: 12
63+
src/Ghost/core/server/services/url/utils.js: 334
64+
src/jquery/external/qunit/qunit.js: 107,397
65+
src/jquery/external/requirejs/require.js: 95,711,1286
66+
src/jquery/external/sinon/sinon.js: 269,275,631,914,4649
67+
src/jquery/src/effects.js: 180
68+
src/jquery/src/event.js: 195,343
69+
src/jquery/src/event/trigger.js: 126,132
70+
src/react-native/IntegrationTests/SizeFlexibilityUpdateTest.js: 50,56,62,68
71+
src/react-native/Libraries/Components/Navigation/NavigatorIOS.ios.js: 705
72+
src/react-native/Libraries/Components/ScrollView/ScrollView.js: 673,684
73+
src/react-native/Libraries/Core/InitializeCore.js: 187
74+
src/react-native/Libraries/Experimental/WindowedListView.js: 419
75+
src/react-native/Libraries/Lists/VirtualizedList.js: 1414,1520
76+
src/react-native/Libraries/Network/XHRInterceptor.js: 125
77+
src/react-native/Libraries/Performance/Systrace.js: 78,94,112
78+
src/react-native/Libraries/polyfills/Object.es6.js: 36
79+
src/react-native/Libraries/ReactNative/AppContainer.js: 59,94
80+
src/react-native/Libraries/Renderer/ReactFabric-dev.js: 589,2856,4804,4813,6156,6350,6462,6470,6516,6524,7253,7318,7436,7497,9805,12437,12472
81+
src/react-native/Libraries/Renderer/ReactNativeRenderer-dev.js: 634,3306,5140,5149,6492,6686,6798,6806,6852,6860,7589,7654,7772,7833,10175,12807,12842
82+
src/react-native/Libraries/Renderer/ReactNativeRenderer-prod.js: 1042
83+
src/react-native/Libraries/Renderer/shims/ReactNativeViewConfigRegistry.js: 37
84+
src/react-native/Libraries/vendor/core/Map.js: 454
85+
src/react-native/scripts/run-ci-e2e-tests.js: 50,59
86+
src/react-router/website/modules/components/DelegateMarkdownLinks.js: 11
87+
src/react-router/website/modules/LoadServiceWorker.js: 3
88+
src/react/packages/events/EventPluginUtils.js: 143
89+
src/react/packages/react-call-return/src/ReactCallReturn.js: 41,64
90+
src/react/packages/react-dom/src/client/ReactDOM.js: 1007,1025,1309,1315
91+
src/react/packages/react-dom/src/client/ReactDOMFiberComponent.js: 254,387,388,653,936,1057
92+
src/react/packages/react-dom/src/client/ReactDOMFiberOption.js: 41
93+
src/react/packages/react-dom/src/client/ReactDOMFiberSelect.js: 165
94+
src/react/packages/react-dom/src/events/BeforeInputEventPlugin.js: 226
95+
src/react/packages/react-dom/src/events/SimpleEventPlugin.js: 277
96+
src/react/packages/react-dom/src/server/ReactPartialRenderer.js: 180,271,305,433,455,475,515,587,953
97+
src/react/packages/react-dom/src/shared/CSSPropertyOperations.js: 62
98+
src/react/packages/react-native-renderer/src/__mocks__/ReactNativeViewConfigRegistry.js: 36
99+
src/react/packages/react-native-renderer/src/NativeMethodsMixinUtils.js: 30
100+
src/react/packages/react-reconciler/src/ReactChildFiber.js: 113,506,594,673,792,857,971,1032,1282
101+
src/react/packages/react-reconciler/src/ReactDebugFiberPerf.js: 244,256
102+
src/react/packages/react-reconciler/src/ReactFiberClassComponent.js: 663,775,973,1085,1093,1139,1147
103+
src/react/packages/react-reconciler/src/ReactFiberCommitWork.js: 171,392
104+
src/react/packages/react-reconciler/src/ReactFiberHydrationContext.js: 283
105+
src/react/packages/react-reconciler/src/ReactFiberReconciler.js: 325,379
106+
src/react/packages/react-reconciler/src/ReactFiberScheduler.js: 1168,1190,1207,1248
107+
src/react/packages/react-reconciler/src/ReactFiberStack.js: 59,89
108+
src/react/packages/react-reconciler/src/ReactFiberUpdateQueue.js: 157
109+
src/react/packages/react/src/ReactChildren.js: 165
110+
src/react/packages/react/src/ReactElement.js: 27,39,214,231,232
111+
src/react/packages/react/src/ReactElementValidator.js: 196
112+
src/react/packages/shared/invokeGuardedCallback.js: 56
113+
src/react/packages/shared/ReactDOMFrameScheduling.js: 25
114+
src/react/scripts/rollup/shims/react-native/ReactNativeViewConfigRegistry.js: 37
115+
src/redux/src/combineReducers.js: 125
116+
src/reveal.js/js/reveal.js: 4174
117+
src/reveal.js/plugin/markdown/markdown.js: 333
118+
src/reveal.js/plugin/search/search.js: 54
119+
src/socket.io/lib/index.js: 364,394
120+
src/spectrum/api/utils/create-graphql-error-formatter.js: 18
121+
src/spectrum/shared/graphql/queries/thread/getThreadMessageConnection.js: 58
122+
src/spectrum/src/components/chatInput/index.js: 263
123+
src/spectrum/src/components/stripeCardForm/modalWell.js: 33
124+
src/three.js/editor/js/Sidebar.Object.js: 486
125+
src/three.js/src/animation/KeyframeTrack.js: 406
126+
src/three.js/src/core/BufferGeometry.js: 333
127+
src/three.js/src/extras/core/ShapePath.js: 255
128+
src/three.js/src/loaders/ImageLoader.js: 78
129+
src/three.js/src/loaders/JSONLoader.js: 54
130+
src/three.js/src/loaders/LoadingManager.js: 25
131+
src/three.js/src/loaders/ObjectLoader.js: 148,644
132+
src/three.js/src/objects/Mesh.js: 251
133+
src/three.js/src/renderers/webgl/WebGLCapabilities.js: 46
134+
src/three.js/src/renderers/webgl/WebGLShadowMap.js: 381
135+
src/three.js/src/renderers/webgl/WebGLTextures.js: 497
136+
src/three.js/src/renderers/WebGLRenderer.js: 842,1613
137+
src/vue/packages/vue-server-renderer/basic.js: 1759
138+
src/vue/packages/vue-server-renderer/build.js: 1765,4101,5562
139+
src/vue/packages/vue-template-compiler/build.js: 3075,4189
140+
src/vue/packages/weex-template-compiler/build.js: 3614
141+
src/vue/packages/weex-vue-framework/factory.js: 1637,3384,4559,5996,6034
142+
src/vue/src/compiler/to-function.js: 88
143+
src/vue/src/core/instance/state.js: 133
144+
src/vue/src/core/util/props.js: 141
145+
src/vue/src/core/vdom/create-element.js: 74
146+
src/vue/src/core/vdom/patch.js: 557,593
147+
src/vue/src/platforms/web/compiler/directives/model.js: 28
148+
src/vue/src/platforms/web/runtime/transition-util.js: 103

src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const sonarjsRules: [string, Linter.RuleLevel][] = [
2323
["cognitive-complexity", "error"],
2424
["max-switch-cases", "error"],
2525
["no-all-duplicated-branches", "error"],
26+
["no-collapsible-if", "error"],
2627
["no-duplicate-string", "error"],
2728
["no-duplicated-branches", "error"],
2829
["no-element-overwrite", "error"],

src/rules/no-collapsible-if.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018 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+
// https://jira.sonarsource.com/browse/RSPEC-1066
21+
22+
import { Rule } from "eslint";
23+
import * as estree from "estree";
24+
import { isIfStatement, isBlockStatement } from "../utils/nodes";
25+
import { report, issueLocation } from "../utils/locations";
26+
27+
const rule: Rule.RuleModule = {
28+
meta: {
29+
schema: [
30+
{
31+
// internal parameter
32+
enum: ["sonar-runtime"],
33+
},
34+
],
35+
},
36+
create(context: Rule.RuleContext) {
37+
return {
38+
IfStatement(node: estree.Node) {
39+
let { consequent } = node as estree.IfStatement;
40+
if (isBlockStatement(consequent) && consequent.body.length === 1) {
41+
consequent = consequent.body[0];
42+
}
43+
if (isIfStatementWithoutElse(node) && isIfStatementWithoutElse(consequent)) {
44+
const ifKeyword = context.getSourceCode().getFirstToken(consequent);
45+
const enclosingIfKeyword = context.getSourceCode().getFirstToken(node);
46+
if (ifKeyword && enclosingIfKeyword) {
47+
report(
48+
context,
49+
{
50+
message: `Merge this if statement with the enclosing one.`,
51+
loc: ifKeyword.loc,
52+
},
53+
[issueLocation(enclosingIfKeyword.loc, enclosingIfKeyword.loc, `Enclosing "if" statement`)],
54+
);
55+
}
56+
}
57+
},
58+
};
59+
60+
function isIfStatementWithoutElse(node: estree.Node): node is estree.IfStatement {
61+
return isIfStatement(node) && !node.alternate;
62+
}
63+
},
64+
};
65+
66+
export = rule;
Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
/*
2+
* eslint-plugin-sonarjs
3+
* Copyright (C) 2018 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 "eslint";
21+
22+
const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015 } });
23+
import rule = require("../../src/rules/no-collapsible-if");
24+
25+
ruleTester.run("no-collapsible-if", rule, {
26+
valid: [
27+
{
28+
code: `
29+
if (x) {
30+
console.log(x);
31+
}`,
32+
},
33+
{
34+
code: `
35+
if (x) {
36+
if (y) {}
37+
console.log(x);
38+
}`,
39+
},
40+
{
41+
code: `
42+
if (x) {
43+
console.log(x);
44+
if (y) {}
45+
}`,
46+
},
47+
{
48+
code: `
49+
if (x) {
50+
if (y) {}
51+
} else {}`,
52+
},
53+
{
54+
code: `
55+
if (x) {
56+
if (y) {} else {}
57+
}`,
58+
},
59+
],
60+
61+
invalid: [
62+
{
63+
code: `
64+
if (x) {
65+
//^^ > {{Enclosing "if" statement}}
66+
if (y) {}
67+
//^^ {{Merge this if statement with the enclosing one.}}
68+
}`,
69+
options: ["sonar-runtime"],
70+
errors: [
71+
{
72+
message: JSON.stringify({
73+
secondaryLocations: [
74+
{
75+
line: 2,
76+
column: 6,
77+
endLine: 2,
78+
endColumn: 8,
79+
message: `Enclosing "if" statement`,
80+
},
81+
],
82+
message: "Merge this if statement with the enclosing one.",
83+
}),
84+
line: 4,
85+
column: 9,
86+
endLine: 4,
87+
endColumn: 11,
88+
},
89+
],
90+
},
91+
{
92+
code: `
93+
if (x)
94+
if(y) {}`,
95+
errors: [{ message: "Merge this if statement with the enclosing one." }],
96+
},
97+
{
98+
code: `
99+
if (x) {
100+
if(y) {
101+
if(z) {
102+
}
103+
}
104+
}`,
105+
errors: 2,
106+
},
107+
],
108+
});

0 commit comments

Comments
 (0)