Skip to content

Commit

Permalink
feat: add no-physical-properties rule
Browse files Browse the repository at this point in the history
  • Loading branch information
anubra266 committed Feb 10, 2024
1 parent 7ecd6ad commit b8cfeec
Show file tree
Hide file tree
Showing 13 changed files with 254 additions and 3 deletions.
5 changes: 5 additions & 0 deletions .changeset/strong-penguins-search.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@pandacss/eslint-plugin": patch
---

Add `no-physical-properties` rule
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ Where rules are included in the configs `recommended`, or `all` it is indicated
| [`@pandacss/no-invalid-token-paths`](docs/rules/no-invalid-token-paths.md) | ✔️ |
| [`@pandacss/no-invalid-nesting`](docs/rules/no-invalid-nesting.md) | ✔️ |
| [`@pandacss/no-property-renaming`](docs/rules/no-property-renaming.md) | ✔️ |
| [`@pandacss/no-physical-properties`](docs/rules/no-physical-properties.md) | |
| [`@pandacss/no-unsafe-token-fn-usage`](docs/rules/no-unsafe-token-fn-usage.md) | |
| [`@pandacss/prefer-longhand-properties`](docs/rules/prefer-longhand-properties.md) | |
| [`@pandacss/prefer-shorthand-properties`](docs/rules/prefer-shorthand-properties.md) | |
Expand Down
60 changes: 60 additions & 0 deletions docs/rules/no-physical-properties.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
[//]: # (This file is generated by eslint-docgen. Do not edit it directly.)

# no-physical-properties

Encourage the use of [logical properties](https://mdn.io/logical-properties-basic-concepts) over physical proeprties, to foster a responsive and adaptable user interface.

📋 This rule is enabled in `plugin:@pandacss/all`.

## Rule details

❌ Examples of **incorrect** code:
```js
import { css } from './panda/css';

const styles = css({ left: '0' });
```
```js

import { css } from './panda/css';

function App(){
return <div className={css({ marginLeft: '4' })} />;
};
```
```js

import { Circle } from './panda/jsx';

function App(){
return <Circle _hover={{ borderBottom: 'solid 1px' }} />;
}
```

✔️ Examples of **correct** code:
```js
import { css } from './panda/css';

const styles = css({ insetInlineStart: '0' });
```
```js

import { css } from './panda/css';

function App(){
return <div className={css({ marginInlineStart: '4' })} />;
};
```
```js

import { Circle } from './panda/jsx';

function App(){
return <Circle _hover={{ borderBlockEnd: 'solid 1px' }} />;
}
```

## Resources

* [Rule source](/plugin/src/rules/no-physical-properties.ts)
* [Test source](/tests/no-physical-properties.test.ts)
2 changes: 2 additions & 0 deletions plugin/src/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import noHardCodedColor, { RULE_NAME as NoHardCodedColor } from './no-hardcoded-
import noImportant, { RULE_NAME as NoImportant } from './no-important'
import noInvalidNesting, { RULE_NAME as NoInvalidNesting } from './no-invalid-nesting'
import noInvalidTokenPaths, { RULE_NAME as NoInvalidTokenPaths } from './no-invalid-token-paths'
import noPhysicalProperties, { RULE_NAME as NoPhysicalProperties } from './no-physical-properties'
import noPropertyRenaming, { RULE_NAME as NoPropertyRenaming } from './no-property-renaming'
import noUnsafeTokenUsage, { RULE_NAME as NoUnsafeTokenUsage } from './no-unsafe-token-fn-usage'
import preferLonghandProperties, { RULE_NAME as PreferLonghandProperties } from './prefer-longhand-properties'
Expand All @@ -26,6 +27,7 @@ export const rules = {
[NoInvalidTokenPaths]: noInvalidTokenPaths,
[NoInvalidNesting]: noInvalidNesting,
[NoPropertyRenaming]: noPropertyRenaming,
[NoPhysicalProperties]: noPhysicalProperties,
[NoUnsafeTokenUsage]: noUnsafeTokenUsage,
[PreferLonghandProperties]: preferLonghandProperties,
[PreferShorthandProperties]: preferShorthandProperties,
Expand Down
75 changes: 75 additions & 0 deletions plugin/src/rules/no-physical-properties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import { isPandaAttribute, isPandaProp, resolveLonghand } from '../utils/helpers'
import { type Rule, createRule } from '../utils'
import { isIdentifier, isJSXIdentifier } from '../utils/nodes'
import { physicalProperties } from '../utils/physical-properties'

export const RULE_NAME = 'no-physical-properties'

const rule: Rule = createRule({
name: RULE_NAME,
meta: {
docs: {
description:
'Encourage the use of [logical properties](https://mdn.io/logical-properties-basic-concepts) over physical proeprties, to foster a responsive and adaptable user interface.',
},
messages: {
physical: 'Use logical property of {{physical}} instead. Prefer `{{logical}}`',
replace: 'Replace `{{physical}}` with `{{logical}}`',
},
type: 'suggestion',
hasSuggestions: true,
schema: [],
},
defaultOptions: [],
create(context) {
const getLonghand = (name: string) => resolveLonghand(name, context) ?? name

const sendReport = (node: any, name: string) => {
const logical = physicalProperties[getLonghand(name)]
const longhand = resolveLonghand(name, context)

return context.report({
node,
messageId: 'physical',
data: {
physical: `\`${name}\`${longhand ? ` - \`${longhand}\`` : ''}`,
logical,
},
suggest: [
{
messageId: 'replace',
data: {
physical: name,
logical,
},
fix: (fixer) => {
return fixer.replaceTextRange(node.range, logical)
},
},
],
})
}

return {
JSXAttribute(node) {
if (!isJSXIdentifier(node.name)) return
if (!isPandaProp(node, context)) return

if (getLonghand(node.name.name) in physicalProperties) {
sendReport(node.name, node.name.name)
}
},

Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return

if (getLonghand(node.key.name) in physicalProperties) {
sendReport(node.key, node.key.name)
}
},
}
},
})

export default rule
1 change: 1 addition & 0 deletions plugin/src/rules/prefer-atomic-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return

const cmp = resolveCompositeProperty(node.key.name)
if (!cmp) return

Expand Down
1 change: 1 addition & 0 deletions plugin/src/rules/prefer-composite-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return

const atm = resolveCompositeProperty(node.key.name)
if (!atm) return

Expand Down
1 change: 1 addition & 0 deletions plugin/src/rules/prefer-longhand-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return

const longhand = resolveLonghand(node.key.name, context)
if (!longhand) return

Expand Down
1 change: 1 addition & 0 deletions plugin/src/rules/prefer-shorthand-properties.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return

const longhand = resolveLonghand(node.key.name, context)
if (longhand) return

Expand Down
6 changes: 4 additions & 2 deletions plugin/src/rules/prefer-unified-property-style.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,10 +24,10 @@ const rule: Rule = createRule({
const getLonghand = (name: string) => resolveLonghand(name, context) ?? name

const resolveCompositeProperty = (name: string) => {
if (Object.hasOwn(compositeProperties, name)) return name
if (name in compositeProperties) return name

const longhand = getLonghand(name)
if (isValidProperty(longhand, context) && Object.hasOwn(compositeProperties, longhand)) return longhand
if (isValidProperty(longhand, context) && longhand in compositeProperties) return longhand
}

const sendReport = (node: any, cmp: string, siblings: string[]) => {
Expand All @@ -54,6 +54,7 @@ const rule: Rule = createRule({
JSXAttribute(node) {
if (!isJSXIdentifier(node.name)) return
if (!isPandaProp(node, context)) return

const cmp = resolveCompositeProperty(node.name.name)
if (!cmp) return
if (!isJSXOpeningElement(node.parent)) return
Expand All @@ -65,6 +66,7 @@ const rule: Rule = createRule({
Property(node) {
if (!isIdentifier(node.key)) return
if (!isPandaAttribute(node, context)) return

const cmp = resolveCompositeProperty(node.key.name)
if (!cmp) return
if (!isObjectExpression(node.parent)) return
Expand Down
34 changes: 34 additions & 0 deletions plugin/src/utils/physical-properties.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
export const physicalProperties: Record<string, string> = {
borderBottom: 'borderBlockEnd',
borderBottomColor: 'borderBlockEndColor',
borderBottomStyle: 'borderBlockEndStyle',
borderBottomWidth: 'borderBlockEndWidth',
borderTop: 'borderBlockStart',
borderTopColor: 'borderBlockStartColor',
borderTopStyle: 'borderBlockStartStyle',
borderTopWidth: 'borderBlockStartWidth',
borderRight: 'borderInlineEnd',
borderRightColor: 'borderInlineEndColor',
borderRightStyle: 'borderInlineEndStyle',
borderRightWidth: 'borderInlineEndWidth',
borderLeft: 'borderInlineStart',
borderLeftColor: 'borderInlineStartColor',
borderLeftStyle: 'borderInlineStartStyle',
borderLeftWidth: 'borderInlineStartWidth',
borderTopLeftRadius: 'borderStartStartRadius',
borderBottomLeftRadius: 'borderEndStartRadius',
borderTopRightRadius: 'borderStartEndRadius',
borderBottomRightRadius: 'borderEndEndRadius',
marginBottom: 'marginBlockEnd',
marginTop: 'marginBlockStart',
marginRight: 'marginInlineEnd',
marginLeft: 'marginInlineStart',
paddingBottom: 'paddingBlockEnd',
paddingTop: 'paddingBlockStart',
paddingRight: 'paddingInlineEnd',
paddingLeft: 'paddingInlineStart',
left: 'insetInlineStart',
right: 'insetInlineEnd',
top: 'insetBlockStart',
bottom: 'insetBlockEnd',
}
68 changes: 68 additions & 0 deletions plugin/tests/no-physical-proeprties.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { tester } from '../test-utils'
import rule, { RULE_NAME } from '../src/rules/no-physical-properties'

const javascript = String.raw

const valids = [
{
code: javascript`
import { css } from './panda/css';
const styles = css({ insetInlineStart: '0' })`,
},

{
code: javascript`
import { css } from './panda/css';
function App(){
return <div className={css({ marginInlineStart: '4' })} />;
}`,
},

{
code: javascript`
import { Circle } from './panda/jsx';
function App(){
return <Circle _hover={{ borderBlockEnd: 'solid 1px' }} />;
}`,
},
]

const invalids = [
{
code: javascript`
import { css } from './panda/css';
const styles = css({ left: '0' })`,
},

{
code: javascript`
import { css } from './panda/css';
function App(){
return <div className={css({ marginLeft: '4' })} />;
}`,
},

{
code: javascript`
import { Circle } from './panda/jsx';
function App(){
return <Circle _hover={{ borderBottom: 'solid 1px' }} />;
}`,
},
]

tester.run(RULE_NAME, rule, {
valid: valids.map(({ code }) => ({
code,
})),
invalid: invalids.map(({ code }) => ({
code,
errors: 1,
})),
})
2 changes: 1 addition & 1 deletion sandbox/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ function App() {
fontSize: 'token(fontSizes.2xl, 4px)',
marginTop: '{spacings.4} token(spacing.600)',
margin: '4',
paddingTop: token('sizes.4'),
pt: token('sizes.4'),
})

const color = 'red'
Expand Down

0 comments on commit b8cfeec

Please sign in to comment.