File tree Expand file tree Collapse file tree
apps/website/content/docs/rules
eslint-plugin-react-jsx/src
rules/no-leaked-semicolon
eslint-plugin/src/configs Expand file tree Collapse file tree Original file line number Diff line number Diff line change 6464 " jsx-no-children-prop-with-children" ,
6565 " jsx-no-comment-textnodes" ,
6666 " jsx-no-key-after-spread" ,
67+ " jsx-no-leaked-semicolon" ,
6768 " jsx-no-namespace" ,
6869 " jsx-no-useless-fragment" ,
6970 " ---RSC Rules---" ,
Original file line number Diff line number Diff line change @@ -9,6 +9,7 @@ export const rules = {
99 "react-jsx/no-children-prop" : "warn" ,
1010 "react-jsx/no-comment-textnodes" : "warn" ,
1111 "react-jsx/no-key-after-spread" : "error" ,
12+ "react-jsx/no-leaked-semicolon" : "warn" ,
1213 "react-jsx/no-namespace" : "error" ,
1314} as const satisfies Record < string , RuleConfig > ;
1415
Original file line number Diff line number Diff line change @@ -6,6 +6,7 @@ import noChildrenPropWithChildren from "./rules/no-children-prop-with-children/n
66import noChildrenProp from "./rules/no-children-prop/no-children-prop" ;
77import noCommentTextnodes from "./rules/no-comment-textnodes/no-comment-textnodes" ;
88import noKeyAfterSpread from "./rules/no-key-after-spread/no-key-after-spread" ;
9+ import noLeakedSemicolon from "./rules/no-leaked-semicolon/no-leaked-semicolon" ;
910import noNamespace from "./rules/no-namespace/no-namespace" ;
1011import noUselessFragment from "./rules/no-useless-fragment/no-useless-fragment" ;
1112
@@ -19,6 +20,7 @@ export const plugin = {
1920 "no-children-prop-with-children" : noChildrenPropWithChildren ,
2021 "no-comment-textnodes" : noCommentTextnodes ,
2122 "no-key-after-spread" : noKeyAfterSpread ,
23+ "no-leaked-semicolon" : noLeakedSemicolon ,
2224 "no-namespace" : noNamespace ,
2325 "no-useless-fragment" : noUselessFragment ,
2426 } ,
Original file line number Diff line number Diff line change 1+ ---
2+ title : no-leaked-semicolon
3+ description : Disallows leaked semicolons that would be rendered as text nodes in JSX.
4+ ---
5+
6+ ** Full Name in [ ` eslint-plugin-react-jsx ` ] ( https://npmx.dev/package/eslint-plugin-react-jsx/v/latest ) **
7+
8+ ``` plain copy
9+ react-jsx/no-leaked-semicolon
10+ ```
11+
12+ ** Full Name in [ ` @eslint-react/eslint-plugin ` ] ( https://npmx.dev/package/@eslint-react/eslint-plugin/v/latest ) **
13+
14+ ``` plain copy
15+ @eslint-react/jsx-no-leaked-semicolon
16+ ```
17+
18+ ** Presets**
19+
20+ ` recommended `
21+ ` recommended-typescript `
22+ ` recommended-type-checked `
23+ ` strict `
24+ ` strict-typescript `
25+ ` strict-type-checked `
26+
27+ ## Rule Details
28+
29+ When refactoring JSX, trailing semicolons may be accidentally left immediately after JSX elements or fragments. This causes ` ; ` to be unexpectedly rendered as text nodes:
30+
31+ ``` tsx
32+ // before
33+ return <div />;
34+
35+ // after
36+ return (
37+ <div >
38+ <div />;
39+ </div >
40+ );
41+
42+ ## Common Violations
43+
44+ ### Invalid
45+
46+ ` ` ` tsx
47+ function MyComponent() {
48+ return (
49+ <div>
50+ <div />;
51+ </div>
52+ );
53+ }
54+ ` ` `
55+
56+ ` ` ` tsx
57+ function MyComponent() {
58+ return (
59+ <div>
60+ <Component>
61+ <div />
62+ </Component>;
63+ </div>
64+ );
65+ }
66+ ` ` `
67+
68+ ### Valid
69+
70+ ` ` ` tsx
71+ function MyComponent() {
72+ return (
73+ <div>
74+ <div />
75+ </div>
76+ );
77+ }
78+ ` ` `
79+
80+ ` ` ` tsx
81+ function MyComponent() {
82+ return (
83+ <div>
84+ <div />
85+ ;
86+ </div>
87+ );
88+ }
89+ ` ` `
90+
91+ ## Resources
92+
93+ - [Rule Source ](https :// github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-jsx/src/rules/no-leaked-semicolon/no-leaked-semicolon.ts)
94+ - [Test Source ](https :// github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-jsx/src/rules/no-leaked-semicolon/no-leaked-semicolon.spec.ts)
Original file line number Diff line number Diff line change 1+ import tsx from "dedent" ;
2+
3+ import { ruleTester } from "../../../../../../test" ;
4+ import rule , { RULE_NAME } from "./no-leaked-semicolon" ;
5+
6+ ruleTester . run ( RULE_NAME , rule , {
7+ invalid : [
8+ {
9+ code : tsx `
10+ const Component = () => {
11+ return (
12+ <div>
13+ <div />;
14+ </div>
15+ );
16+ }
17+ ` ,
18+ errors : [ { messageId : "default" } ] ,
19+ } ,
20+ {
21+ code : tsx `
22+ const Component = () => {
23+ return (
24+ <div>
25+ <Component>
26+ <div />
27+ </Component>;
28+ </div>
29+ );
30+ }
31+ ` ,
32+ errors : [ { messageId : "default" } ] ,
33+ } ,
34+ {
35+ code : tsx `
36+ const Component = () => (
37+ <div>
38+ <Component />;
39+ </div>
40+ )
41+ ` ,
42+ errors : [ { messageId : "default" } ] ,
43+ } ,
44+ {
45+ code : tsx `
46+ const Component = () => {
47+ return (
48+ <>
49+ <div />;
50+ </>
51+ );
52+ }
53+ ` ,
54+ errors : [ { messageId : "default" } ] ,
55+ } ,
56+ {
57+ code : tsx `
58+ const Component = () => {
59+ return (
60+ <>
61+ <Component>
62+ <div />
63+ </Component>;
64+ </>
65+ );
66+ }
67+ ` ,
68+ errors : [ { messageId : "default" } ] ,
69+ } ,
70+ ] ,
71+ valid : [
72+ tsx `
73+ const Component = () => {
74+ return (
75+ <div>
76+ <div />
77+ </div>
78+ );
79+ }
80+ ` ,
81+ tsx `
82+ const Component = () => {
83+ return (
84+ <div>
85+ <div />
86+ ;
87+ </div>
88+ );
89+ }
90+ ` ,
91+ tsx `
92+ const Component = () => {
93+ return (
94+ <div>
95+ <div />{';'}
96+ </div>
97+ );
98+ }
99+ ` ,
100+ tsx `
101+ const Component = () => {
102+ return (
103+ <div>
104+ <div />;
105+ </div>
106+ );
107+ }
108+ ` ,
109+ tsx `
110+ const Component = () => {
111+ return (
112+ <div>
113+ <span>;</span>
114+ <span />;<span />
115+ text; text;
116+ &
117+ </div>
118+ );
119+ }
120+ ` ,
121+ tsx `
122+ const Component = () => {
123+ return <div />;
124+ }
125+ ` ,
126+ tsx `
127+ const Component = () => {
128+ return (
129+ <div>
130+ <div />text;
131+ </div>
132+ );
133+ }
134+ ` ,
135+ ] ,
136+ } ) ;
Original file line number Diff line number Diff line change 1+ import * as ast from "@eslint-react/ast" ;
2+ import { type RuleContext , type RuleFeature , defineRuleListener } from "@eslint-react/shared" ;
3+ import type { TSESTree } from "@typescript-eslint/types" ;
4+
5+ import { createRule } from "../../utils" ;
6+
7+ export const RULE_NAME = "no-leaked-semicolon" ;
8+
9+ export const RULE_FEATURES = [ ] as const satisfies RuleFeature [ ] ;
10+
11+ export type MessageID = "default" ;
12+
13+ export default createRule < [ ] , MessageID > ( {
14+ meta : {
15+ type : "problem" ,
16+ docs : {
17+ description : "Disallows trailing semicolons that would be rendered as text nodes." ,
18+ } ,
19+ messages : {
20+ default : "Leaked semicolon in JSX. This semicolon will be rendered as text nodes." ,
21+ } ,
22+ schema : [ ] ,
23+ } ,
24+ name : RULE_NAME ,
25+ create,
26+ defaultOptions : [ ] ,
27+ } ) ;
28+
29+ function hasLeakedSemicolon ( text : string ) {
30+ return text . startsWith ( ";\n" ) || text . startsWith ( ";\r" ) ;
31+ }
32+
33+ export function create ( context : RuleContext < MessageID , [ ] > ) {
34+ const visitorFunction = ( node : TSESTree . JSXText | TSESTree . Literal ) => {
35+ if ( ! ast . isJSXElementLike ( node . parent ) ) {
36+ return ;
37+ }
38+ if ( ! hasLeakedSemicolon ( context . sourceCode . getText ( node ) ) ) {
39+ return ;
40+ }
41+ context . report ( {
42+ messageId : "default" ,
43+ node,
44+ } ) ;
45+ } ;
46+ return defineRuleListener ( {
47+ JSXText : visitorFunction ,
48+ Literal : visitorFunction ,
49+ } ) ;
50+ }
Original file line number Diff line number Diff line change @@ -63,6 +63,7 @@ export const rules = {
6363 "@eslint-react/jsx-no-comment-textnodes" : "warn" ,
6464 "@eslint-react/jsx-no-key-after-spread" : "error" ,
6565 "@eslint-react/jsx-no-namespace" : "error" ,
66+ "@eslint-react/jsx-no-leaked-semicolon" : "warn" ,
6667 "@eslint-react/jsx-no-useless-fragment" : "warn" ,
6768 "@eslint-react/rsc-function-definition" : "error" ,
6869
Original file line number Diff line number Diff line change @@ -7,5 +7,6 @@ export const rules = {
77 "@eslint-react/jsx-no-children-prop-with-children" : "error" ,
88 "@eslint-react/jsx-no-comment-textnodes" : "warn" ,
99 "@eslint-react/jsx-no-key-after-spread" : "error" ,
10+ "@eslint-react/jsx-no-leaked-semicolon" : "warn" ,
1011 "@eslint-react/jsx-no-namespace" : "error" ,
1112} as const satisfies Record < string , RuleConfig > ;
You can’t perform that action at this time.
0 commit comments