Skip to content

Commit 16a5209

Browse files
authored
Merge pull request #122 from solidjs-community/fix/show-fragments
Check JSXFragment in most places where checking JSXElement, fixes #106.
2 parents 85c4f5a + 794a64a commit 16a5209

File tree

8 files changed

+59
-8
lines changed

8 files changed

+59
-8
lines changed

docs/prefer-for.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ let Component = (props) => (
3333
</ol>
3434
);
3535

36+
let Component = (props) => (
37+
<>
38+
{props.data.map((d) => (
39+
<li>{d.text}</li>
40+
))}
41+
</>
42+
);
43+
// after eslint --fix:
44+
let Component = (props) => (
45+
<>
46+
<For each={props.data}>{(d) => <li>{d.text}</li>}</For>
47+
</>
48+
);
49+
3650
let Component = (props) => (
3751
<ol>
3852
{props.data.map((d) => (

docs/prefer-show.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,20 @@ function Component(props) {
3333
);
3434
}
3535

36+
function Component(props) {
37+
return <>{props.cond && <span>Content</span>}</>;
38+
}
39+
// after eslint --fix:
40+
function Component(props) {
41+
return (
42+
<>
43+
<Show when={props.cond}>
44+
<span>Content</span>
45+
</Show>
46+
</>
47+
);
48+
}
49+
3650
function Component(props) {
3751
return <div>{props.cond ? <span>Content</span> : <span>Fallback</span>}</div>;
3852
}

src/rules/prefer-for.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { TSESTree as T, ESLintUtils, ASTUtils } from "@typescript-eslint/utils";
2-
import { isFunctionNode } from "../utils";
2+
import { isFunctionNode, isJSXElementOrFragment } from "../utils";
33

44
const createRule = ESLintUtils.RuleCreator.withoutDocs;
55
const { getPropertyName } = ASTUtils;
@@ -55,7 +55,7 @@ export default createRule({
5555
const callOrChain = node.parent?.type === "ChainExpression" ? node.parent : node;
5656
if (
5757
callOrChain.parent?.type === "JSXExpressionContainer" &&
58-
callOrChain.parent.parent?.type === "JSXElement"
58+
isJSXElementOrFragment(callOrChain.parent.parent)
5959
) {
6060
// check for Array.prototype.map in JSX
6161
if (

src/rules/prefer-show.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { TSESTree as T, ESLintUtils } from "@typescript-eslint/utils";
2+
import { isJSXElementOrFragment } from "../utils";
23

34
const createRule = ESLintUtils.RuleCreator.withoutDocs;
45

@@ -25,7 +26,7 @@ export default createRule({
2526
const sourceCode = context.getSourceCode();
2627
const putIntoJSX = (node: T.Node): string => {
2728
const text = sourceCode.getText(node);
28-
return node.type === "JSXElement" || node.type === "JSXFragment" ? text : `{${text}}`;
29+
return isJSXElementOrFragment(node) ? text : `{${text}}`;
2930
};
3031

3132
const logicalExpressionHandler = (node: T.LogicalExpression) => {
@@ -36,7 +37,7 @@ export default createRule({
3637
fix: (fixer) =>
3738
fixer.replaceText(
3839
node.parent?.type === "JSXExpressionContainer" &&
39-
node.parent.parent?.type === "JSXElement"
40+
isJSXElementOrFragment(node.parent.parent)
4041
? node.parent
4142
: node,
4243
`<Show when={${sourceCode.getText(node.left)}}>${putIntoJSX(node.right)}</Show>`
@@ -55,7 +56,7 @@ export default createRule({
5556
fix: (fixer) =>
5657
fixer.replaceText(
5758
node.parent?.type === "JSXExpressionContainer" &&
58-
node.parent.parent?.type === "JSXElement"
59+
isJSXElementOrFragment(node.parent.parent)
5960
? node.parent
6061
: node,
6162
`<Show when={${sourceCode.getText(node.test)}} fallback={${sourceCode.getText(
@@ -68,7 +69,7 @@ export default createRule({
6869

6970
return {
7071
JSXExpressionContainer(node) {
71-
if (node.parent?.type !== "JSXElement") {
72+
if (!isJSXElementOrFragment(node.parent)) {
7273
return;
7374
}
7475
if (node.expression.type === "LogicalExpression") {

src/rules/reactivity.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import {
1616
isDOMElementName,
1717
ignoreTransparentWrappers,
1818
getFunctionName,
19+
isJSXElementOrFragment,
1920
} from "../utils";
2021

2122
const { findVariable, getFunctionHeadLocation } = ASTUtils;
@@ -527,7 +528,7 @@ export default createRule<Options, MessageIds>({
527528
if (
528529
// The signal is not being called and is being used as a props.children, where calling
529530
// the signal was the likely intent.
530-
elementOrAttribute?.type === "JSXElement" ||
531+
isJSXElementOrFragment(elementOrAttribute) ||
531532
// We can't say for sure about user components, but we know for a fact that a signal
532533
// should not be passed to a non-event handler DOM element attribute without calling it.
533534
(elementOrAttribute?.type === "JSXAttribute" &&
@@ -886,7 +887,7 @@ export default createRule<Options, MessageIds>({
886887
// to the DOM. This is semantically a "called function", so it's fine to read reactive
887888
// variables here.
888889
pushTrackedScope(node.expression, "called-function");
889-
} else if (node.parent?.type === "JSXElement" && isFunctionNode(node.expression)) {
890+
} else if (isJSXElementOrFragment(node.parent) && isFunctionNode(node.expression)) {
890891
pushTrackedScope(node.expression, "function"); // functions inline in JSX containers will be tracked
891892
} else {
892893
pushTrackedScope(node.expression, "expression");

src/utils.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,11 @@ export const isProgramOrFunctionNode = (
9797
node: T.Node | null | undefined
9898
): node is ProgramOrFunctionNode => !!node && PROGRAM_OR_FUNCTION_TYPES.includes(node.type);
9999

100+
export const isJSXElementOrFragment = (
101+
node: T.Node | null | undefined
102+
): node is T.JSXElement | T.JSXFragment =>
103+
node?.type === "JSXElement" || node?.type === "JSXFragment";
104+
100105
export const getFunctionName = (node: FunctionNode): string | null => {
101106
if (
102107
(node.type === "FunctionDeclaration" || node.type === "FunctionExpression") &&

test/rules/prefer-for.test.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ export const cases = run("prefer-for", rule, {
1717
errors: [{ messageId: "preferFor" }],
1818
output: `let Component = (props) => <ol><For each={props.data}>{d => <li>{d.text}</li>}</For></ol>;`,
1919
},
20+
{
21+
code: `let Component = (props) => <>{props.data.map(d => <li>{d.text}</li>)}</>;`,
22+
errors: [{ messageId: "preferFor" }],
23+
output: `let Component = (props) => <><For each={props.data}>{d => <li>{d.text}</li>}</For></>;`,
24+
},
2025
{
2126
code: `let Component = (props) => <ol>{props.data.map(d => <li key={d.id}>{d.text}</li>)}</ol>;`,
2227
errors: [{ messageId: "preferFor" }],

test/rules/prefer-show.test.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,17 @@ export const cases = run("prefer-show", rule, {
2222
return <div><Show when={props.cond}><span>Content</span></Show></div>;
2323
}`,
2424
},
25+
{
26+
code: `
27+
function Component(props) {
28+
return <>{props.cond && <span>Content</span>}</>;
29+
}`,
30+
errors: [{ messageId: "preferShowAnd" }],
31+
output: `
32+
function Component(props) {
33+
return <><Show when={props.cond}><span>Content</span></Show></>;
34+
}`,
35+
},
2536
{
2637
code: `
2738
function Component(props) {

0 commit comments

Comments
 (0)