Skip to content

Commit 43296c8

Browse files
tanmoyopenrootadidahiya
authored andcommitted
[new-rule] react-no-unnecessary-fragment (#245)
1 parent 182810a commit 43296c8

File tree

3 files changed

+285
-0
lines changed

3 files changed

+285
-0
lines changed
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
/**
2+
* @license
3+
* Copyright 2019 Palantir Technologies, Inc.
4+
*
5+
* Licensed under the Apache License, Version 2.0 (the "License");
6+
* you may not use this file except in compliance with the License.
7+
* You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
import * as Lint from "tslint";
19+
import {
20+
isJsxElement,
21+
isJsxFragment,
22+
isJsxOpeningFragment,
23+
isJsxSelfClosingElement,
24+
isJsxText,
25+
} from "tsutils/typeguard/3.0";
26+
import * as ts from "typescript";
27+
28+
const FRAGMENT_TAGNAME = "Fragment";
29+
const REACT_FRAGEMNT_TAGNAME = "React.Fragment";
30+
31+
export class Rule extends Lint.Rules.AbstractRule {
32+
public static metadata: Lint.IRuleMetadata = {
33+
description: Lint.Utils.dedent`
34+
Warn if unnecessary fragment is used'
35+
`,
36+
optionExamples: ["true"],
37+
options: null,
38+
optionsDescription: "",
39+
ruleName: "react-no-unnecessary-fragment",
40+
type: "style",
41+
typescriptOnly: false,
42+
};
43+
44+
public static FAILURE_STRING = "Unnecessary Fragment are forbidden";
45+
46+
public apply(sourceFile: ts.SourceFile): Lint.RuleFailure[] {
47+
return this.applyWithFunction(sourceFile, walk);
48+
}
49+
}
50+
51+
function walk(ctx: Lint.WalkContext<void>): void {
52+
return ts.forEachChild(ctx.sourceFile, function cb(node: ts.Node): void {
53+
if (
54+
(isJsxFragment(node) && isJsxOpeningFragment(node.openingFragment)) ||
55+
(isJsxElement(node) && isJSXFragmentElement(node.openingElement))
56+
) {
57+
let numValidChildren = 0;
58+
59+
for (const child of node.children) {
60+
if (isJsxText(child)) {
61+
if (!isInvalidJSXText(child)) {
62+
numValidChildren += 1;
63+
}
64+
} else {
65+
numValidChildren += 1;
66+
}
67+
68+
if (numValidChildren > 1) {
69+
break;
70+
}
71+
}
72+
73+
if (numValidChildren <= 1) {
74+
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
75+
}
76+
77+
} else if (isJsxSelfClosingElement(node) && isJSXFragmentElement(node)) {
78+
ctx.addFailureAtNode(node, Rule.FAILURE_STRING);
79+
}
80+
81+
return ts.forEachChild(node, cb);
82+
});
83+
}
84+
85+
function isJSXFragmentElement(node: ts.JsxSelfClosingElement | ts.JsxOpeningElement): boolean {
86+
if (
87+
node.tagName.getText() === FRAGMENT_TAGNAME ||
88+
node.tagName.getText() === REACT_FRAGEMNT_TAGNAME
89+
) {
90+
return true;
91+
}
92+
93+
return false;
94+
}
95+
96+
function isInvalidJSXText(node: ts.JsxText): boolean {
97+
return node.getText().trim() === "" ? true : false;
98+
}
Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
<React.Fragment>
2+
~~~~~~~~~~~~~~~~
3+
<div>
4+
~~~~~~~~~
5+
<Component1 />
6+
~~~~~~~~~~~~~~~~~~~~~~
7+
<Component2 />
8+
~~~~~~~~~~~~~~~~~~~~~~
9+
<Component3 />
10+
~~~~~~~~~~~~~~~~~~~~~~
11+
</div>
12+
~~~~~~~~~~
13+
</React.Fragment>
14+
~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
15+
16+
<React.Fragment>test
17+
<div>
18+
<Component1 />
19+
<Component2 />
20+
<Component3 />
21+
</div></React.Fragment>
22+
23+
<React.Fragment>
24+
~~~~~~~~~~~~~~~~
25+
<React.Fragment>
26+
~~~~~~~~~~~~~~~~~~~~
27+
<Component1 />
28+
~~~~~~~~~~~~~~~~~~~~~~
29+
<Component2 />
30+
~~~~~~~~~~~~~~~~~~~~~~
31+
<Component3 />
32+
~~~~~~~~~~~~~~~~~~~~~~
33+
</React.Fragment>
34+
~~~~~~~~~~~~~~~~~~~~~
35+
</React.Fragment>
36+
~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
37+
38+
<React.Fragment>
39+
~~~~~~~~~~~~~~~~
40+
<React.Fragment>
41+
~~~~~~~~~~~~~~~~~~~~
42+
~~~~~~~~~~~~~~~~
43+
<>
44+
~~~~~~~~~~
45+
~~~~~~~~~~
46+
<Component1 />
47+
~~~~~~~~~~~~~~~~~~~~~~~~~~
48+
~~~~~~~~~~~~~~~~~~~~~~~~~~
49+
<Component2 />
50+
~~~~~~~~~~~~~~~~~~~~~~~~~~
51+
~~~~~~~~~~~~~~~~~~~~~~~~~~
52+
<Component3 />
53+
~~~~~~~~~~~~~~~~~~~~~~~~~~
54+
~~~~~~~~~~~~~~~~~~~~~~~~~~
55+
</>
56+
~~~~~~~~~~~
57+
~~~~~~~~~~~
58+
</React.Fragment>
59+
~~~~~~~~~~~~~~~~~~~~~
60+
~~~~~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
61+
</React.Fragment>
62+
~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
63+
64+
<>
65+
~~
66+
<>
67+
~~~~~~
68+
~~
69+
<>
70+
~~~~~~~~~~
71+
~~~~~~~~~~
72+
<Component1 />
73+
~~~~~~~~~~~~~~~~~~~~~~~~~~
74+
~~~~~~~~~~~~~~~~~~~~~~~~~~
75+
<Component2 />
76+
~~~~~~~~~~~~~~~~~~~~~~~~~~
77+
~~~~~~~~~~~~~~~~~~~~~~~~~~
78+
<Component3 />
79+
~~~~~~~~~~~~~~~~~~~~~~~~~~
80+
~~~~~~~~~~~~~~~~~~~~~~~~~~
81+
</>
82+
~~~~~~~~~~~
83+
~~~~~~~~~~~
84+
</>
85+
~~~~~~~
86+
~~~~~~~ [FAILURE_STRING]
87+
</>
88+
~~~ [FAILURE_STRING]
89+
90+
<>
91+
~~
92+
<Fragment>
93+
~~~~~~~~~~~~~~
94+
~~~~~~~~~~
95+
<>
96+
~~~~~~~~~~
97+
~~~~~~~~~~
98+
<Component1 />
99+
~~~~~~~~~~~~~~~~~~~~~~~~~~
100+
~~~~~~~~~~~~~~~~~~~~~~~~~~
101+
<Component2 />
102+
~~~~~~~~~~~~~~~~~~~~~~~~~~
103+
~~~~~~~~~~~~~~~~~~~~~~~~~~
104+
<Component3 />
105+
~~~~~~~~~~~~~~~~~~~~~~~~~~
106+
~~~~~~~~~~~~~~~~~~~~~~~~~~
107+
</>
108+
~~~~~~~~~~~
109+
~~~~~~~~~~~
110+
</Fragment>
111+
~~~~~~~~~~~~~~~
112+
~~~~~~~~~~~~~~~ [FAILURE_STRING]
113+
</>
114+
~~~ [FAILURE_STRING]
115+
116+
<Fragment>
117+
~~~~~~~~~~
118+
<Fragment>
119+
~~~~~~~~~~~~~~
120+
<Component1 />
121+
~~~~~~~~~~~~~~~~~~~~~~
122+
<Component2 />
123+
~~~~~~~~~~~~~~~~~~~~~~
124+
<Component3 />
125+
~~~~~~~~~~~~~~~~~~~~~~
126+
</Fragment>
127+
~~~~~~~~~~~~~~~
128+
</Fragment>
129+
~~~~~~~~~~~ [FAILURE_STRING]
130+
131+
<Fragment>
132+
test
133+
<div>
134+
<Component1 />
135+
<Component2 />
136+
<Component3 />
137+
</div></Fragment>
138+
139+
<Fragment>
140+
<Component1 />
141+
<Component2 />
142+
</Fragment>
143+
144+
<Fragment>
145+
~~~~~~~~~~
146+
</Fragment>
147+
~~~~~~~~~~~ [FAILURE_STRING]
148+
149+
<React.Fragment>
150+
~~~~~~~~~~~~~~~~
151+
</React.Fragment>
152+
~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
153+
154+
<Fragment><Component1 /><Component2 /></Fragment>
155+
156+
<Fragment></Fragment>
157+
~~~~~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
158+
159+
<React.Fragment></React.Fragment>
160+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
161+
162+
<Fragment />
163+
~~~~~~~~~~~~ [FAILURE_STRING]
164+
165+
<React.Fragment />
166+
~~~~~~~~~~~~~~~~~~ [FAILURE_STRING]
167+
168+
<>
169+
~~
170+
<div>
171+
~~~~~~~~~
172+
<Component1 />
173+
~~~~~~~~~~~~~~~~~~~~~~
174+
<Component2 />
175+
~~~~~~~~~~~~~~~~~~~~~~
176+
<Component3 />
177+
~~~~~~~~~~~~~~~~~~~~~~
178+
</div>
179+
~~~~~~~~~~
180+
</>
181+
~~~ [FAILURE_STRING]
182+
[FAILURE_STRING]: Unnecessary Fragment are forbidden
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"rules": {
3+
"react-no-unnecessary-fragment": true
4+
}
5+
}

0 commit comments

Comments
 (0)