Skip to content

Commit de78f22

Browse files
committed
Better style-prop errors, relax numericStyleValue to only warn on <length-percentage> properties. Fixes #15.
1 parent 3ede19c commit de78f22

File tree

4 files changed

+47
-40
lines changed

4 files changed

+47
-40
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ would like to use.
9494
|| 🔧 | [solid/prefer-for](docs/prefer-for.md) | Enforce using Solid's `<For />` component for mapping an array to JSX elements. |
9595
|| 🔧 | [solid/prefer-show](docs/prefer-show.md) | Enforce using Solid's `<Show />` component for conditionally showing content. |
9696
|| | [solid/reactivity](docs/reactivity.md) | Enforce that reactive expressions (props, signals, memos, etc.) are only used in tracked scopes; otherwise, they won't update the view as expected. |
97-
|| 🔧 | [solid/style-prop](docs/style-prop.md) | Require CSS properties in the `style` prop to be valid and kebab-cased (ex. 'font-size'), not camel-cased (ex. 'fontSize') like in React, and that property values are strings, not numbers with implicit 'px' units. |
97+
|| 🔧 | [solid/style-prop](docs/style-prop.md) | Require CSS properties in the `style` prop to be valid and kebab-cased (ex. 'font-size'), not camel-cased (ex. 'fontSize') like in React, and that property values with dimensions are strings, not numbers with implicit 'px' units. |
9898
<!-- AUTO-GENERATED-CONTENT:END -->
9999

100100
## Versioning

docs/style-prop.md

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<!-- AUTO-GENERATED-CONTENT:START (HEADER) -->
22
# solid/style-prop
3-
Require CSS properties in the `style` prop to be valid and kebab-cased (ex. 'font-size'), not camel-cased (ex. 'fontSize') like in React, and that property values are strings, not numbers with implicit 'px' units.
3+
Require CSS properties in the `style` prop to be valid and kebab-cased (ex. 'font-size'), not camel-cased (ex. 'fontSize') like in React, and that property values with dimensions are strings, not numbers with implicit 'px' units.
44
This rule is **a warning** by default.
55

66
[View source](../src/rules/style-prop.ts) · [View tests](../test/rules/style-prop.test.ts)
@@ -30,9 +30,9 @@ let el = <div style={{ fontSize: "10px" }}>Hello, world!</div>;
3030
// after eslint --fix:
3131
let el = <div style={{ "font-size": "10px" }}>Hello, world!</div>;
3232

33-
let el = <div style={{ backgroundColor: "10px" }}>Hello, world!</div>;
33+
let el = <div style={{ backgroundColor: "red" }}>Hello, world!</div>;
3434
// after eslint --fix:
35-
let el = <div style={{ "background-color": "10px" }}>Hello, world!</div>;
35+
let el = <div style={{ "background-color": "red" }}>Hello, world!</div>;
3636

3737
let el = <div style={{ "-webkitAlignContent": "center" }}>Hello, world!</div>;
3838
// after eslint --fix:
@@ -72,10 +72,6 @@ let el = <div style={{ "font-size": 10 }}>Hello, world!</div>;
7272

7373
let el = <div style={{ "margin-top": -10 }}>Hello, world!</div>;
7474

75-
let el = <div style={{ padding: 0 }}>Hello, world!</div>;
76-
// after eslint --fix:
77-
let el = <div style={{ padding: "0" }}>Hello, world!</div>;
78-
7975
```
8076

8177
### Valid Examples
@@ -99,8 +95,14 @@ let el = <div style={{ "font-size": "10px" }}>Hello, world!</div>;
9995

10096
let el = <div style={{ "font-size": "0" }}>Hello, world!</div>;
10197

98+
let el = <div style={{ "font-size": 0 }}>Hello, world!</div>;
99+
102100
let el = <div STYLE={{ fontSize: 10 }}>Hello, world!</div>;
103101

102+
let el = <div style={{ "flex-grow": 1 }}>Hello, world!</div>;
103+
104+
let el = <div style={{ "--custom-width": 1 }}>Hello, world!</div>;
105+
104106
/* eslint solid/style-prop: ["error", { "allowString": true }] */
105107
let el = <div style="color: red;" />;
106108

src/rules/style-prop.ts

Lines changed: 22 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import { propName } from "jsx-ast-utils";
66

77
const { getPropertyName, getStaticValue } = ASTUtils;
88

9+
const lengthPercentageRegex = /\b(?:width|height|margin|padding|border-width|font-size)\b/i;
10+
911
const rule: TSESLint.RuleModule<
10-
"invalidStyleProp" | "numericStyleValue" | "zeroStyleValue" | "stringStyle",
12+
"kebabStyleProp" | "invalidStyleProp" | "numericStyleValue" | "stringStyle",
1113
[{ styleProps?: [string, ...Array<string>]; allowString?: boolean }?]
1214
> = {
1315
meta: {
@@ -16,7 +18,7 @@ const rule: TSESLint.RuleModule<
1618
recommended: "warn",
1719
description:
1820
"Require CSS properties in the `style` prop to be valid and kebab-cased (ex. 'font-size'), not camel-cased (ex. 'fontSize') like in React, " +
19-
"and that property values are strings, not numbers with implicit 'px' units.",
21+
"and that property values with dimensions are strings, not numbers with implicit 'px' units.",
2022
url: "https://github.com/joshwilsonvu/eslint-plugin-solid/blob/main/docs/style-prop.md",
2123
},
2224
fixable: "code",
@@ -44,10 +46,10 @@ const rule: TSESLint.RuleModule<
4446
},
4547
],
4648
messages: {
49+
kebabStyleProp: "Use {{ kebabName }} instead of {{ name }}.",
4750
invalidStyleProp: "{{ name }} is not a valid CSS property.",
4851
numericStyleValue:
49-
'CSS property values should be strings only, but {{ value }} is a number; convert to string and add a unit like "px" if appropriate.',
50-
zeroStyleValue: 'A CSS property value of 0 should be passed as the string "0".',
52+
'This CSS property value should be a string with a unit; Solid does not automatically append a "px" unit.',
5153
stringStyle: "Use an object for the style prop instead of a string.",
5254
},
5355
},
@@ -94,26 +96,26 @@ const rule: TSESLint.RuleModule<
9496
const name: string | null = getPropertyName(prop, context.getScope());
9597
if (name && !name.startsWith("--") && !allCssPropertiesSet.has(name)) {
9698
const kebabName: string = kebabCase(name);
97-
context.report({
98-
node: prop.key,
99-
messageId: "invalidStyleProp",
100-
data: { name },
99+
if (allCssPropertiesSet.has(kebabName)) {
101100
// if it's not valid simply because it's camelCased instead of kebab-cased, provide a fix
102-
fix: allCssPropertiesSet.has(kebabName)
103-
? (fixer) => fixer.replaceText(prop.key, `"${kebabName}"`) // wrap kebab name in quotes to be a valid object key
104-
: undefined,
105-
});
106-
}
107-
// catches numeric values (ex. { "font-size": 12 }) and suggests quoting or appending 'px'
108-
const value: unknown = getStaticValue(prop.value)?.value;
109-
if (typeof value === "number") {
110-
if (value === 0) {
111101
context.report({
112-
node: prop.value,
113-
messageId: "zeroStyleValue",
114-
fix: (fixer) => fixer.replaceText(prop.value, '"0"'),
102+
node: prop.key,
103+
messageId: "kebabStyleProp",
104+
data: { name, kebabName },
105+
fix: (fixer) => fixer.replaceText(prop.key, `"${kebabName}"`), // wrap kebab name in quotes to be a valid object key
115106
});
116107
} else {
108+
context.report({
109+
node: prop.key,
110+
messageId: "invalidStyleProp",
111+
data: { name },
112+
});
113+
}
114+
} else if (!name || (!name.startsWith("--") && lengthPercentageRegex.test(name))) {
115+
// catches numeric values (ex. { "font-size": 12 }) for common <length-percentage> peroperties
116+
// and suggests quoting or appending 'px'
117+
const value: unknown = getStaticValue(prop.value)?.value;
118+
if (typeof value === "number" && value !== 0) {
117119
context.report({
118120
node: prop.value,
119121
messageId: "numericStyleValue",

test/rules/style-prop.test.ts

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,10 @@ export const cases = run("style-prop", rule, {
99
`let el = <div style={{ "-webkit-align-content": "center" }}>Hello, world!</div>`,
1010
`let el = <div style={{ "font-size": "10px" }}>Hello, world!</div>`,
1111
`let el = <div style={{ "font-size": "0" }}>Hello, world!</div>`,
12+
`let el = <div style={{ "font-size": 0 }}>Hello, world!</div>`,
1213
`let el = <div STYLE={{ fontSize: 10 }}>Hello, world!</div>`,
14+
`let el = <div style={{ "flex-grow": 1 }}>Hello, world!</div>`,
15+
`let el = <div style={{ "--custom-width": 1 }}>Hello, world!</div>`,
1316
{
1417
code: `let el = <div style="color: red;" />`,
1518
options: [{ allowString: true }],
@@ -30,17 +33,22 @@ export const cases = run("style-prop", rule, {
3033
invalid: [
3134
{
3235
code: `let el = <div style={{ fontSize: '10px' }}>Hello, world!</div>`,
33-
errors: [{ messageId: "invalidStyleProp", data: { name: "fontSize" } }],
36+
errors: [{ messageId: "kebabStyleProp", data: { name: "fontSize", kebabName: "font-size" } }],
3437
output: `let el = <div style={{ "font-size": '10px' }}>Hello, world!</div>`,
3538
},
3639
{
37-
code: `let el = <div style={{ backgroundColor: '10px' }}>Hello, world!</div>`,
38-
errors: [{ messageId: "invalidStyleProp", data: { name: "backgroundColor" } }],
39-
output: `let el = <div style={{ "background-color": '10px' }}>Hello, world!</div>`,
40+
code: `let el = <div style={{ backgroundColor: 'red' }}>Hello, world!</div>`,
41+
errors: [
42+
{
43+
messageId: "kebabStyleProp",
44+
data: { name: "backgroundColor", kebabName: "background-color" },
45+
},
46+
],
47+
output: `let el = <div style={{ "background-color": 'red' }}>Hello, world!</div>`,
4048
},
4149
{
4250
code: `let el = <div style={{ "-webkitAlignContent": "center" }}>Hello, world!</div>`,
43-
errors: [{ messageId: "invalidStyleProp" }],
51+
errors: [{ messageId: "kebabStyleProp" }],
4452
output: `let el = <div style={{ "-webkit-align-content": "center" }}>Hello, world!</div>`,
4553
},
4654
{
@@ -54,13 +62,13 @@ export const cases = run("style-prop", rule, {
5462
{
5563
code: `let el = <div css={{ fontSize: '10px' }}>Hello, world!</div>`,
5664
options: [{ styleProps: ["style", "css"] }],
57-
errors: [{ messageId: "invalidStyleProp", data: { name: "fontSize" } }],
65+
errors: [{ messageId: "kebabStyleProp", data: { name: "fontSize", kebabName: "font-size" } }],
5866
output: `let el = <div css={{ "font-size": '10px' }}>Hello, world!</div>`,
5967
},
6068
{
6169
code: `let el = <div css={{ fontSize: '10px' }}>Hello, world!</div>`,
6270
options: [{ styleProps: ["css"] }],
63-
errors: [{ messageId: "invalidStyleProp", data: { name: "fontSize" } }],
71+
errors: [{ messageId: "kebabStyleProp", data: { name: "fontSize", kebabName: "font-size" } }],
6472
output: `let el = <div css={{ "font-size": '10px' }}>Hello, world!</div>`,
6573
},
6674
{
@@ -94,10 +102,5 @@ export const cases = run("style-prop", rule, {
94102
code: `let el = <div style={{ 'margin-top': -10 }}>Hello, world!</div>`,
95103
errors: [{ messageId: "numericStyleValue" }],
96104
},
97-
{
98-
code: `let el = <div style={{ padding: 0 }}>Hello, world!</div>`,
99-
errors: [{ messageId: "zeroStyleValue" }],
100-
output: `let el = <div style={{ padding: "0" }}>Hello, world!</div>`,
101-
},
102105
],
103106
});

0 commit comments

Comments
 (0)