Skip to content

Commit e628743

Browse files
authored
refactor(plugins/x): xhtml entities should be allowed inside of 'no-u… (#862)
1 parent b0dbe3e commit e628743

File tree

7 files changed

+300
-2
lines changed

7 files changed

+300
-2
lines changed

packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,16 @@ ruleTester.run(RULE_NAME, rule, {
184184
{moo}
185185
</>
186186
`,
187+
/* tsx */ `
188+
function Foo() {
189+
return <Bar><>&nbsp;</></Bar>;
190+
}
191+
`,
192+
/* tsx */ `
193+
function Foo() {
194+
return <Bar><>a&nbsp;b</></Bar>;
195+
}
196+
`,
187197
/* tsx */ `<>{cloneElement(children, { ref: childrenRef })}</>`,
188198
{
189199
code: /* tsx */ `

packages/plugins/eslint-plugin-react-x/src/rules/no-useless-fragment.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ function check(
2424
if (JSX.isKeyedElement(node, initialScope)) return;
2525
if (JSX.isBuiltInElement(node.parent)) context.report({ messageId: "noUselessFragmentInBuiltIn", node });
2626
if (node.children.length === 0) return context.report({ messageId: "noUselessFragment", node });
27+
if (node.children.some((child) => JSX.isLiteral(child) && child.value !== child.raw)) return;
2728
const isChildren = AST.isOneOf([AST_NODE_TYPES.JSXElement, AST_NODE_TYPES.JSXFragment])(node.parent);
2829
const [firstChildren] = node.children;
2930
// <Foo content={<>ee eeee eeee ...</>} />

packages/utilities/jsx/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"@typescript-eslint/scope-manager": "^8.15.0",
4949
"@typescript-eslint/types": "^8.15.0",
5050
"@typescript-eslint/utils": "^8.15.0",
51+
"birecord": "^0.1.1",
5152
"ts-pattern": "^5.5.0"
5253
},
5354
"devDependencies": {

packages/utilities/jsx/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,5 @@ export * from "./is-element";
66
export * from "./is-jsx-value";
77
export * from "./is-literal";
88
export * from "./traverse-up-prop";
9+
export * from "./unescape-string-literal-text";
10+
export * from "./xhtml-entities";
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { xhtmlEntities } from "./xhtml-entities";
2+
3+
/**
4+
* Unescape the text content of string literals, e.g. &amp; -> &
5+
* @param text The escaped string literal text.
6+
* @returns The unescaped string literal text.
7+
*/
8+
export function unescapeStringLiteralText(text: string): string {
9+
return text.replaceAll(/&(?:#\d+|#x[\da-fA-F]+|[0-9a-zA-Z]+);/g, entity => {
10+
const item = entity.slice(1, -1);
11+
// eslint-disable-next-line @typescript-eslint/prefer-string-starts-ends-with
12+
if (item[0] === "#") {
13+
const codePoint = item[1] === "x"
14+
? parseInt(item.slice(2), 16)
15+
: parseInt(item.slice(1), 10);
16+
return codePoint > 0x10ffff // RangeError: Invalid code point
17+
? entity
18+
: String.fromCodePoint(codePoint);
19+
}
20+
return xhtmlEntities.has(item)
21+
? xhtmlEntities.get(item)
22+
: entity;
23+
});
24+
}
Lines changed: 257 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,257 @@
1+
import birecord from "birecord";
2+
3+
export const xhtmlEntities = birecord({
4+
Aacute: "\u00C1",
5+
aacute: "\u00E1",
6+
Acirc: "\u00C2",
7+
acirc: "\u00E2",
8+
acute: "\u00B4",
9+
AElig: "\u00C6",
10+
aelig: "\u00E6",
11+
Agrave: "\u00C0",
12+
agrave: "\u00E0",
13+
alefsym: "\u2135",
14+
Alpha: "\u0391",
15+
alpha: "\u03B1",
16+
amp: "&",
17+
and: "\u2227",
18+
ang: "\u2220",
19+
apos: "\u0027",
20+
Aring: "\u00C5",
21+
aring: "\u00E5",
22+
asymp: "\u2248",
23+
Atilde: "\u00C3",
24+
atilde: "\u00E3",
25+
Auml: "\u00C4",
26+
auml: "\u00E4",
27+
bdquo: "\u201E",
28+
Beta: "\u0392",
29+
beta: "\u03B2",
30+
brvbar: "\u00A6",
31+
bull: "\u2022",
32+
cap: "\u2229",
33+
Ccedil: "\u00C7",
34+
ccedil: "\u00E7",
35+
cedil: "\u00B8",
36+
cent: "\u00A2",
37+
Chi: "\u03A7",
38+
chi: "\u03C7",
39+
circ: "\u02C6",
40+
clubs: "\u2663",
41+
cong: "\u2245",
42+
copy: "\u00A9",
43+
crarr: "\u21B5",
44+
cup: "\u222A",
45+
curren: "\u00A4",
46+
dagger: "\u2020",
47+
Dagger: "\u2021",
48+
darr: "\u2193",
49+
dArr: "\u21D3",
50+
deg: "\u00B0",
51+
Delta: "\u0394",
52+
delta: "\u03B4",
53+
diams: "\u2666",
54+
divide: "\u00F7",
55+
Eacute: "\u00C9",
56+
eacute: "\u00E9",
57+
Ecirc: "\u00CA",
58+
ecirc: "\u00EA",
59+
Egrave: "\u00C8",
60+
egrave: "\u00E8",
61+
empty: "\u2205",
62+
emsp: "\u2003",
63+
ensp: "\u2002",
64+
Epsilon: "\u0395",
65+
epsilon: "\u03B5",
66+
equiv: "\u2261",
67+
Eta: "\u0397",
68+
eta: "\u03B7",
69+
ETH: "\u00D0",
70+
eth: "\u00F0",
71+
Euml: "\u00CB",
72+
euml: "\u00EB",
73+
euro: "\u20AC",
74+
exist: "\u2203",
75+
fnof: "\u0192",
76+
forall: "\u2200",
77+
frac12: "\u00BD",
78+
frac14: "\u00BC",
79+
frac34: "\u00BE",
80+
frasl: "\u2044",
81+
Gamma: "\u0393",
82+
gamma: "\u03B3",
83+
ge: "\u2265",
84+
gt: ">",
85+
harr: "\u2194",
86+
hArr: "\u21D4",
87+
hearts: "\u2665",
88+
hellip: "\u2026",
89+
Iacute: "\u00CD",
90+
iacute: "\u00ED",
91+
Icirc: "\u00CE",
92+
icirc: "\u00EE",
93+
iexcl: "\u00A1",
94+
Igrave: "\u00CC",
95+
igrave: "\u00EC",
96+
image: "\u2111",
97+
infin: "\u221E",
98+
int: "\u222B",
99+
Iota: "\u0399",
100+
iota: "\u03B9",
101+
iquest: "\u00BF",
102+
isin: "\u2208",
103+
Iuml: "\u00CF",
104+
iuml: "\u00EF",
105+
Kappa: "\u039A",
106+
kappa: "\u03BA",
107+
Lambda: "\u039B",
108+
lambda: "\u03BB",
109+
lang: "\u2329",
110+
laquo: "\u00AB",
111+
larr: "\u2190",
112+
lArr: "\u21D0",
113+
lceil: "\u2308",
114+
ldquo: "\u201C",
115+
le: "\u2264",
116+
lfloor: "\u230A",
117+
lowast: "\u2217",
118+
loz: "\u25CA",
119+
lrm: "\u200E",
120+
lsaquo: "\u2039",
121+
lsquo: "\u2018",
122+
lt: "<",
123+
macr: "\u00AF",
124+
mdash: "\u2014",
125+
micro: "\u00B5",
126+
middot: "\u00B7",
127+
minus: "\u2212",
128+
Mu: "\u039C",
129+
mu: "\u03BC",
130+
nabla: "\u2207",
131+
nbsp: "\u00A0",
132+
ndash: "\u2013",
133+
ne: "\u2260",
134+
ni: "\u220B",
135+
not: "\u00AC",
136+
notin: "\u2209",
137+
nsub: "\u2284",
138+
Ntilde: "\u00D1",
139+
ntilde: "\u00F1",
140+
Nu: "\u039D",
141+
nu: "\u03BD",
142+
Oacute: "\u00D3",
143+
oacute: "\u00F3",
144+
Ocirc: "\u00D4",
145+
ocirc: "\u00F4",
146+
OElig: "\u0152",
147+
oelig: "\u0153",
148+
Ograve: "\u00D2",
149+
ograve: "\u00F2",
150+
oline: "\u203E",
151+
Omega: "\u03A9",
152+
omega: "\u03C9",
153+
Omicron: "\u039F",
154+
omicron: "\u03BF",
155+
oplus: "\u2295",
156+
or: "\u2228",
157+
ordf: "\u00AA",
158+
ordm: "\u00BA",
159+
Oslash: "\u00D8",
160+
oslash: "\u00F8",
161+
Otilde: "\u00D5",
162+
otilde: "\u00F5",
163+
otimes: "\u2297",
164+
Ouml: "\u00D6",
165+
ouml: "\u00F6",
166+
para: "\u00B6",
167+
part: "\u2202",
168+
permil: "\u2030",
169+
perp: "\u22A5",
170+
Phi: "\u03A6",
171+
phi: "\u03C6",
172+
Pi: "\u03A0",
173+
pi: "\u03C0",
174+
piv: "\u03D6",
175+
plusmn: "\u00B1",
176+
pound: "\u00A3",
177+
prime: "\u2032",
178+
Prime: "\u2033",
179+
prod: "\u220F",
180+
prop: "\u221D",
181+
Psi: "\u03A8",
182+
psi: "\u03C8",
183+
quot: "\u0022",
184+
radic: "\u221A",
185+
rang: "\u232A",
186+
raquo: "\u00BB",
187+
rarr: "\u2192",
188+
rArr: "\u21D2",
189+
rceil: "\u2309",
190+
rdquo: "\u201D",
191+
real: "\u211C",
192+
reg: "\u00AE",
193+
rfloor: "\u230B",
194+
Rho: "\u03A1",
195+
rho: "\u03C1",
196+
rlm: "\u200F",
197+
rsaquo: "\u203A",
198+
rsquo: "\u2019",
199+
sbquo: "\u201A",
200+
Scaron: "\u0160",
201+
scaron: "\u0161",
202+
sdot: "\u22C5",
203+
sect: "\u00A7",
204+
shy: "\u00AD",
205+
Sigma: "\u03A3",
206+
sigma: "\u03C3",
207+
sigmaf: "\u03C2",
208+
sim: "\u223C",
209+
spades: "\u2660",
210+
sub: "\u2282",
211+
sube: "\u2286",
212+
sum: "\u2211",
213+
sup: "\u2283",
214+
sup1: "\u00B9",
215+
sup2: "\u00B2",
216+
sup3: "\u00B3",
217+
supe: "\u2287",
218+
szlig: "\u00DF",
219+
Tau: "\u03A4",
220+
tau: "\u03C4",
221+
there4: "\u2234",
222+
Theta: "\u0398",
223+
theta: "\u03B8",
224+
thetasym: "\u03D1",
225+
thinsp: "\u2009",
226+
THORN: "\u00DE",
227+
thorn: "\u00FE",
228+
tilde: "\u02DC",
229+
times: "\u00D7",
230+
trade: "\u2122",
231+
Uacute: "\u00DA",
232+
uacute: "\u00FA",
233+
uarr: "\u2191",
234+
uArr: "\u21D1",
235+
Ucirc: "\u00DB",
236+
ucirc: "\u00FB",
237+
Ugrave: "\u00D9",
238+
ugrave: "\u00F9",
239+
uml: "\u00A8",
240+
upsih: "\u03D2",
241+
Upsilon: "\u03A5",
242+
upsilon: "\u03C5",
243+
Uuml: "\u00DC",
244+
uuml: "\u00FC",
245+
weierp: "\u2118",
246+
Xi: "\u039E",
247+
xi: "\u03BE",
248+
Yacute: "\u00DD",
249+
yacute: "\u00FD",
250+
yen: "\u00A5",
251+
yuml: "\u00FF",
252+
Yuml: "\u0178",
253+
Zeta: "\u0396",
254+
zeta: "\u03B6",
255+
zwj: "\u200D",
256+
zwnj: "\u200C",
257+
});

pnpm-lock.yaml

Lines changed: 5 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)