Skip to content

Commit b8cfeec

Browse files
committed
feat: add no-physical-properties rule
1 parent 7ecd6ad commit b8cfeec

13 files changed

+254
-3
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@pandacss/eslint-plugin": patch
3+
---
4+
5+
Add `no-physical-properties` rule

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ Where rules are included in the configs `recommended`, or `all` it is indicated
8989
| [`@pandacss/no-invalid-token-paths`](docs/rules/no-invalid-token-paths.md) | ✔️ |
9090
| [`@pandacss/no-invalid-nesting`](docs/rules/no-invalid-nesting.md) | ✔️ |
9191
| [`@pandacss/no-property-renaming`](docs/rules/no-property-renaming.md) | ✔️ |
92+
| [`@pandacss/no-physical-properties`](docs/rules/no-physical-properties.md) | |
9293
| [`@pandacss/no-unsafe-token-fn-usage`](docs/rules/no-unsafe-token-fn-usage.md) | |
9394
| [`@pandacss/prefer-longhand-properties`](docs/rules/prefer-longhand-properties.md) | |
9495
| [`@pandacss/prefer-shorthand-properties`](docs/rules/prefer-shorthand-properties.md) | |
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
[//]: # (This file is generated by eslint-docgen. Do not edit it directly.)
2+
3+
# no-physical-properties
4+
5+
Encourage the use of [logical properties](https://mdn.io/logical-properties-basic-concepts) over physical proeprties, to foster a responsive and adaptable user interface.
6+
7+
📋 This rule is enabled in `plugin:@pandacss/all`.
8+
9+
## Rule details
10+
11+
❌ Examples of **incorrect** code:
12+
```js
13+
import { css } from './panda/css';
14+
15+
const styles = css({ left: '0' });
16+
```
17+
```js
18+
19+
import { css } from './panda/css';
20+
21+
function App(){
22+
return <div className={css({ marginLeft: '4' })} />;
23+
};
24+
```
25+
```js
26+
27+
import { Circle } from './panda/jsx';
28+
29+
function App(){
30+
return <Circle _hover={{ borderBottom: 'solid 1px' }} />;
31+
}
32+
```
33+
34+
✔️ Examples of **correct** code:
35+
```js
36+
import { css } from './panda/css';
37+
38+
const styles = css({ insetInlineStart: '0' });
39+
```
40+
```js
41+
42+
import { css } from './panda/css';
43+
44+
function App(){
45+
return <div className={css({ marginInlineStart: '4' })} />;
46+
};
47+
```
48+
```js
49+
50+
import { Circle } from './panda/jsx';
51+
52+
function App(){
53+
return <Circle _hover={{ borderBlockEnd: 'solid 1px' }} />;
54+
}
55+
```
56+
57+
## Resources
58+
59+
* [Rule source](/plugin/src/rules/no-physical-properties.ts)
60+
* [Test source](/tests/no-physical-properties.test.ts)

plugin/src/rules/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import noHardCodedColor, { RULE_NAME as NoHardCodedColor } from './no-hardcoded-
77
import noImportant, { RULE_NAME as NoImportant } from './no-important'
88
import noInvalidNesting, { RULE_NAME as NoInvalidNesting } from './no-invalid-nesting'
99
import noInvalidTokenPaths, { RULE_NAME as NoInvalidTokenPaths } from './no-invalid-token-paths'
10+
import noPhysicalProperties, { RULE_NAME as NoPhysicalProperties } from './no-physical-properties'
1011
import noPropertyRenaming, { RULE_NAME as NoPropertyRenaming } from './no-property-renaming'
1112
import noUnsafeTokenUsage, { RULE_NAME as NoUnsafeTokenUsage } from './no-unsafe-token-fn-usage'
1213
import preferLonghandProperties, { RULE_NAME as PreferLonghandProperties } from './prefer-longhand-properties'
@@ -26,6 +27,7 @@ export const rules = {
2627
[NoInvalidTokenPaths]: noInvalidTokenPaths,
2728
[NoInvalidNesting]: noInvalidNesting,
2829
[NoPropertyRenaming]: noPropertyRenaming,
30+
[NoPhysicalProperties]: noPhysicalProperties,
2931
[NoUnsafeTokenUsage]: noUnsafeTokenUsage,
3032
[PreferLonghandProperties]: preferLonghandProperties,
3133
[PreferShorthandProperties]: preferShorthandProperties,
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
2+
import { type Rule, createRule } from '../utils'
3+
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
4+
import { physicalProperties } from '../utils/physical-properties'
5+
6+
export const RULE_NAME = 'no-physical-properties'
7+
8+
const rule: Rule = createRule({
9+
name: RULE_NAME,
10+
meta: {
11+
docs: {
12+
description:
13+
'Encourage the use of [logical properties](https://mdn.io/logical-properties-basic-concepts) over physical proeprties, to foster a responsive and adaptable user interface.',
14+
},
15+
messages: {
16+
physical: 'Use logical property of {{physical}} instead. Prefer `{{logical}}`',
17+
replace: 'Replace `{{physical}}` with `{{logical}}`',
18+
},
19+
type: 'suggestion',
20+
hasSuggestions: true,
21+
schema: [],
22+
},
23+
defaultOptions: [],
24+
create(context) {
25+
const getLonghand = (name: string) => resolveLonghand(name, context) ?? name
26+
27+
const sendReport = (node: any, name: string) => {
28+
const logical = physicalProperties[getLonghand(name)]
29+
const longhand = resolveLonghand(name, context)
30+
31+
return context.report({
32+
node,
33+
messageId: 'physical',
34+
data: {
35+
physical: `\`${name}\`${longhand ? ` - \`${longhand}\`` : ''}`,
36+
logical,
37+
},
38+
suggest: [
39+
{
40+
messageId: 'replace',
41+
data: {
42+
physical: name,
43+
logical,
44+
},
45+
fix: (fixer) => {
46+
return fixer.replaceTextRange(node.range, logical)
47+
},
48+
},
49+
],
50+
})
51+
}
52+
53+
return {
54+
JSXAttribute(node) {
55+
if (!isJSXIdentifier(node.name)) return
56+
if (!isPandaProp(node, context)) return
57+
58+
if (getLonghand(node.name.name) in physicalProperties) {
59+
sendReport(node.name, node.name.name)
60+
}
61+
},
62+
63+
Property(node) {
64+
if (!isIdentifier(node.key)) return
65+
if (!isPandaAttribute(node, context)) return
66+
67+
if (getLonghand(node.key.name) in physicalProperties) {
68+
sendReport(node.key, node.key.name)
69+
}
70+
},
71+
}
72+
},
73+
})
74+
75+
export default rule

plugin/src/rules/prefer-atomic-properties.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ const rule: Rule = createRule({
5555
Property(node) {
5656
if (!isIdentifier(node.key)) return
5757
if (!isPandaAttribute(node, context)) return
58+
5859
const cmp = resolveCompositeProperty(node.key.name)
5960
if (!cmp) return
6061

plugin/src/rules/prefer-composite-properties.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ const rule: Rule = createRule({
5353
Property(node) {
5454
if (!isIdentifier(node.key)) return
5555
if (!isPandaAttribute(node, context)) return
56+
5657
const atm = resolveCompositeProperty(node.key.name)
5758
if (!atm) return
5859

plugin/src/rules/prefer-longhand-properties.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ const rule: Rule = createRule({
5959
Property(node) {
6060
if (!isIdentifier(node.key)) return
6161
if (!isPandaAttribute(node, context)) return
62+
6263
const longhand = resolveLonghand(node.key.name, context)
6364
if (!longhand) return
6465

plugin/src/rules/prefer-shorthand-properties.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const rule: Rule = createRule({
6262
Property(node) {
6363
if (!isIdentifier(node.key)) return
6464
if (!isPandaAttribute(node, context)) return
65+
6566
const longhand = resolveLonghand(node.key.name, context)
6667
if (longhand) return
6768

plugin/src/rules/prefer-unified-property-style.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,10 @@ const rule: Rule = createRule({
2424
const getLonghand = (name: string) => resolveLonghand(name, context) ?? name
2525

2626
const resolveCompositeProperty = (name: string) => {
27-
if (Object.hasOwn(compositeProperties, name)) return name
27+
if (name in compositeProperties) return name
2828

2929
const longhand = getLonghand(name)
30-
if (isValidProperty(longhand, context) && Object.hasOwn(compositeProperties, longhand)) return longhand
30+
if (isValidProperty(longhand, context) && longhand in compositeProperties) return longhand
3131
}
3232

3333
const sendReport = (node: any, cmp: string, siblings: string[]) => {
@@ -54,6 +54,7 @@ const rule: Rule = createRule({
5454
JSXAttribute(node) {
5555
if (!isJSXIdentifier(node.name)) return
5656
if (!isPandaProp(node, context)) return
57+
5758
const cmp = resolveCompositeProperty(node.name.name)
5859
if (!cmp) return
5960
if (!isJSXOpeningElement(node.parent)) return
@@ -65,6 +66,7 @@ const rule: Rule = createRule({
6566
Property(node) {
6667
if (!isIdentifier(node.key)) return
6768
if (!isPandaAttribute(node, context)) return
69+
6870
const cmp = resolveCompositeProperty(node.key.name)
6971
if (!cmp) return
7072
if (!isObjectExpression(node.parent)) return

0 commit comments

Comments
 (0)