1
1
import { isRecipeVariant , isPandaAttribute , isPandaProp , resolveLonghand } from '../utils/helpers'
2
2
import { type Rule , createRule } from '../utils'
3
- import { isIdentifier , isJSXIdentifier } from '../utils/nodes'
4
- import { physicalProperties } from '../utils/physical-properties'
3
+ import { isIdentifier , isJSXIdentifier , isLiteral , isJSXExpressionContainer } from '../utils/nodes'
4
+ import { physicalProperties , physicalPropertyValues } from '../utils/physical-properties'
5
5
import type { TSESTree } from '@typescript-eslint/utils'
6
6
7
7
export const RULE_NAME = 'no-physical-properties'
@@ -15,6 +15,7 @@ const rule: Rule = createRule({
15
15
} ,
16
16
messages : {
17
17
physical : 'Use logical property instead of {{physical}}. Prefer `{{logical}}`.' ,
18
+ physicalValue : 'Use logical value instead of {{physical}}. Prefer `{{logical}}`.' ,
18
19
replace : 'Replace `{{physical}}` with `{{logical}}`.' ,
19
20
} ,
20
21
type : 'suggestion' ,
@@ -52,6 +53,32 @@ const rule: Rule = createRule({
52
53
const pandaAttributeCache = new WeakMap < TSESTree . Property , boolean | undefined > ( )
53
54
const recipeVariantCache = new WeakMap < TSESTree . Property , boolean | undefined > ( )
54
55
56
+ /**
57
+ * Extract string literal value from node
58
+ * @param valueNode The value node
59
+ * @returns String literal value, or null if not found
60
+ */
61
+ const extractStringLiteralValue = (
62
+ valueNode : TSESTree . Property [ 'value' ] | TSESTree . JSXAttribute [ 'value' ] ,
63
+ ) : string | null => {
64
+ // Regular literal value (e.g., "left")
65
+ if ( isLiteral ( valueNode ) && typeof valueNode . value === 'string' ) {
66
+ return valueNode . value
67
+ }
68
+
69
+ // Literal value in JSX expression container (e.g., {"left"})
70
+ if (
71
+ isJSXExpressionContainer ( valueNode ) &&
72
+ isLiteral ( valueNode . expression ) &&
73
+ typeof valueNode . expression . value === 'string'
74
+ ) {
75
+ return valueNode . expression . value
76
+ }
77
+
78
+ // Not a string literal
79
+ return null
80
+ }
81
+
55
82
const getLonghand = ( name : string ) : string => {
56
83
if ( longhandCache . has ( name ) ) {
57
84
return longhandCache . get ( name ) !
@@ -118,20 +145,83 @@ const rule: Rule = createRule({
118
145
} )
119
146
}
120
147
148
+ // Check property values for physical values that should use logical values
149
+ const checkPropertyValue = (
150
+ keyNode : TSESTree . Identifier | TSESTree . JSXIdentifier ,
151
+ valueNode : NonNullable < TSESTree . Property [ 'value' ] | TSESTree . JSXAttribute [ 'value' ] > ,
152
+ ) => {
153
+ // Skip if property name doesn't have physical values mapping
154
+ const propName = keyNode . name
155
+ if ( ! ( propName in physicalPropertyValues ) ) return false
156
+
157
+ // Extract string literal value
158
+ const valueText = extractStringLiteralValue ( valueNode )
159
+ if ( valueText === null ) {
160
+ // Skip if not a string literal
161
+ return false
162
+ }
163
+
164
+ // Check if value is a physical value
165
+ const valueMap = physicalPropertyValues [ propName ]
166
+ if ( ! valueMap [ valueText ] ) return false
167
+
168
+ const logical = valueMap [ valueText ]
169
+
170
+ context . report ( {
171
+ node : valueNode ,
172
+ messageId : 'physicalValue' ,
173
+ data : {
174
+ physical : `"${ valueText } "` ,
175
+ logical : `"${ logical } "` ,
176
+ } ,
177
+ suggest : [
178
+ {
179
+ messageId : 'replace' ,
180
+ data : {
181
+ physical : `"${ valueText } "` ,
182
+ logical : `"${ logical } "` ,
183
+ } ,
184
+ fix : ( fixer ) => {
185
+ if ( isLiteral ( valueNode ) ) {
186
+ return fixer . replaceText ( valueNode , `"${ logical } "` )
187
+ } else if ( isJSXExpressionContainer ( valueNode ) && isLiteral ( valueNode . expression ) ) {
188
+ return fixer . replaceText ( valueNode . expression , `"${ logical } "` )
189
+ }
190
+ return null
191
+ } ,
192
+ } ,
193
+ ] ,
194
+ } )
195
+
196
+ return true
197
+ }
198
+
121
199
return {
122
200
JSXAttribute ( node : TSESTree . JSXAttribute ) {
123
201
if ( ! isJSXIdentifier ( node . name ) ) return
124
202
if ( ! isCachedPandaProp ( node ) ) return
125
203
204
+ // Check property name
126
205
sendReport ( node . name )
206
+
207
+ // Check property value if needed
208
+ if ( node . value ) {
209
+ checkPropertyValue ( node . name , node . value )
210
+ }
127
211
} ,
128
212
129
213
Property ( node : TSESTree . Property ) {
130
214
if ( ! isIdentifier ( node . key ) ) return
131
215
if ( ! isCachedPandaAttribute ( node ) ) return
132
216
if ( isCachedRecipeVariant ( node ) ) return
133
217
218
+ // Check property name
134
219
sendReport ( node . key )
220
+
221
+ // Check property value if needed
222
+ if ( node . value ) {
223
+ checkPropertyValue ( node . key , node . value )
224
+ }
135
225
} ,
136
226
}
137
227
} ,
0 commit comments