Skip to content

Commit

Permalink
Better style-prop errors, relax numericStyleValue to only warn on <le…
Browse files Browse the repository at this point in the history
…ngth-percentage> properties. Fixes #15.
  • Loading branch information
joshwilsonvu committed Mar 31, 2022
1 parent 3ede19c commit de78f22
Show file tree
Hide file tree
Showing 4 changed files with 47 additions and 40 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ would like to use.
|| 🔧 | [solid/prefer-for](docs/prefer-for.md) | Enforce using Solid's `<For />` component for mapping an array to JSX elements. |
|| 🔧 | [solid/prefer-show](docs/prefer-show.md) | Enforce using Solid's `<Show />` component for conditionally showing content. |
|| | [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. |
|| 🔧 | [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. |
|| 🔧 | [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. |
<!-- AUTO-GENERATED-CONTENT:END -->

## Versioning
Expand Down
16 changes: 9 additions & 7 deletions docs/style-prop.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- AUTO-GENERATED-CONTENT:START (HEADER) -->
# solid/style-prop
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.
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.
This rule is **a warning** by default.

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

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

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

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

let el = <div style={{ padding: 0 }}>Hello, world!</div>;
// after eslint --fix:
let el = <div style={{ padding: "0" }}>Hello, world!</div>;

```

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

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

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

let el = <div STYLE={{ fontSize: 10 }}>Hello, world!</div>;

let el = <div style={{ "flex-grow": 1 }}>Hello, world!</div>;

let el = <div style={{ "--custom-width": 1 }}>Hello, world!</div>;

/* eslint solid/style-prop: ["error", { "allowString": true }] */
let el = <div style="color: red;" />;

Expand Down
42 changes: 22 additions & 20 deletions src/rules/style-prop.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@ import { propName } from "jsx-ast-utils";

const { getPropertyName, getStaticValue } = ASTUtils;

const lengthPercentageRegex = /\b(?:width|height|margin|padding|border-width|font-size)\b/i;

const rule: TSESLint.RuleModule<
"invalidStyleProp" | "numericStyleValue" | "zeroStyleValue" | "stringStyle",
"kebabStyleProp" | "invalidStyleProp" | "numericStyleValue" | "stringStyle",
[{ styleProps?: [string, ...Array<string>]; allowString?: boolean }?]
> = {
meta: {
Expand All @@ -16,7 +18,7 @@ const rule: TSESLint.RuleModule<
recommended: "warn",
description:
"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.",
"and that property values with dimensions are strings, not numbers with implicit 'px' units.",
url: "https://github.com/joshwilsonvu/eslint-plugin-solid/blob/main/docs/style-prop.md",
},
fixable: "code",
Expand Down Expand Up @@ -44,10 +46,10 @@ const rule: TSESLint.RuleModule<
},
],
messages: {
kebabStyleProp: "Use {{ kebabName }} instead of {{ name }}.",
invalidStyleProp: "{{ name }} is not a valid CSS property.",
numericStyleValue:
'CSS property values should be strings only, but {{ value }} is a number; convert to string and add a unit like "px" if appropriate.',
zeroStyleValue: 'A CSS property value of 0 should be passed as the string "0".',
'This CSS property value should be a string with a unit; Solid does not automatically append a "px" unit.',
stringStyle: "Use an object for the style prop instead of a string.",
},
},
Expand Down Expand Up @@ -94,26 +96,26 @@ const rule: TSESLint.RuleModule<
const name: string | null = getPropertyName(prop, context.getScope());
if (name && !name.startsWith("--") && !allCssPropertiesSet.has(name)) {
const kebabName: string = kebabCase(name);
context.report({
node: prop.key,
messageId: "invalidStyleProp",
data: { name },
if (allCssPropertiesSet.has(kebabName)) {
// if it's not valid simply because it's camelCased instead of kebab-cased, provide a fix
fix: allCssPropertiesSet.has(kebabName)
? (fixer) => fixer.replaceText(prop.key, `"${kebabName}"`) // wrap kebab name in quotes to be a valid object key
: undefined,
});
}
// catches numeric values (ex. { "font-size": 12 }) and suggests quoting or appending 'px'
const value: unknown = getStaticValue(prop.value)?.value;
if (typeof value === "number") {
if (value === 0) {
context.report({
node: prop.value,
messageId: "zeroStyleValue",
fix: (fixer) => fixer.replaceText(prop.value, '"0"'),
node: prop.key,
messageId: "kebabStyleProp",
data: { name, kebabName },
fix: (fixer) => fixer.replaceText(prop.key, `"${kebabName}"`), // wrap kebab name in quotes to be a valid object key
});
} else {
context.report({
node: prop.key,
messageId: "invalidStyleProp",
data: { name },
});
}
} else if (!name || (!name.startsWith("--") && lengthPercentageRegex.test(name))) {
// catches numeric values (ex. { "font-size": 12 }) for common <length-percentage> peroperties
// and suggests quoting or appending 'px'
const value: unknown = getStaticValue(prop.value)?.value;
if (typeof value === "number" && value !== 0) {
context.report({
node: prop.value,
messageId: "numericStyleValue",
Expand Down
27 changes: 15 additions & 12 deletions test/rules/style-prop.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ export const cases = run("style-prop", rule, {
`let el = <div style={{ "-webkit-align-content": "center" }}>Hello, world!</div>`,
`let el = <div style={{ "font-size": "10px" }}>Hello, world!</div>`,
`let el = <div style={{ "font-size": "0" }}>Hello, world!</div>`,
`let el = <div style={{ "font-size": 0 }}>Hello, world!</div>`,
`let el = <div STYLE={{ fontSize: 10 }}>Hello, world!</div>`,
`let el = <div style={{ "flex-grow": 1 }}>Hello, world!</div>`,
`let el = <div style={{ "--custom-width": 1 }}>Hello, world!</div>`,
{
code: `let el = <div style="color: red;" />`,
options: [{ allowString: true }],
Expand All @@ -30,17 +33,22 @@ export const cases = run("style-prop", rule, {
invalid: [
{
code: `let el = <div style={{ fontSize: '10px' }}>Hello, world!</div>`,
errors: [{ messageId: "invalidStyleProp", data: { name: "fontSize" } }],
errors: [{ messageId: "kebabStyleProp", data: { name: "fontSize", kebabName: "font-size" } }],
output: `let el = <div style={{ "font-size": '10px' }}>Hello, world!</div>`,
},
{
code: `let el = <div style={{ backgroundColor: '10px' }}>Hello, world!</div>`,
errors: [{ messageId: "invalidStyleProp", data: { name: "backgroundColor" } }],
output: `let el = <div style={{ "background-color": '10px' }}>Hello, world!</div>`,
code: `let el = <div style={{ backgroundColor: 'red' }}>Hello, world!</div>`,
errors: [
{
messageId: "kebabStyleProp",
data: { name: "backgroundColor", kebabName: "background-color" },
},
],
output: `let el = <div style={{ "background-color": 'red' }}>Hello, world!</div>`,
},
{
code: `let el = <div style={{ "-webkitAlignContent": "center" }}>Hello, world!</div>`,
errors: [{ messageId: "invalidStyleProp" }],
errors: [{ messageId: "kebabStyleProp" }],
output: `let el = <div style={{ "-webkit-align-content": "center" }}>Hello, world!</div>`,
},
{
Expand All @@ -54,13 +62,13 @@ export const cases = run("style-prop", rule, {
{
code: `let el = <div css={{ fontSize: '10px' }}>Hello, world!</div>`,
options: [{ styleProps: ["style", "css"] }],
errors: [{ messageId: "invalidStyleProp", data: { name: "fontSize" } }],
errors: [{ messageId: "kebabStyleProp", data: { name: "fontSize", kebabName: "font-size" } }],
output: `let el = <div css={{ "font-size": '10px' }}>Hello, world!</div>`,
},
{
code: `let el = <div css={{ fontSize: '10px' }}>Hello, world!</div>`,
options: [{ styleProps: ["css"] }],
errors: [{ messageId: "invalidStyleProp", data: { name: "fontSize" } }],
errors: [{ messageId: "kebabStyleProp", data: { name: "fontSize", kebabName: "font-size" } }],
output: `let el = <div css={{ "font-size": '10px' }}>Hello, world!</div>`,
},
{
Expand Down Expand Up @@ -94,10 +102,5 @@ export const cases = run("style-prop", rule, {
code: `let el = <div style={{ 'margin-top': -10 }}>Hello, world!</div>`,
errors: [{ messageId: "numericStyleValue" }],
},
{
code: `let el = <div style={{ padding: 0 }}>Hello, world!</div>`,
errors: [{ messageId: "zeroStyleValue" }],
output: `let el = <div style={{ padding: "0" }}>Hello, world!</div>`,
},
],
});

0 comments on commit de78f22

Please sign in to comment.