11import { isRecipeVariant , isPandaAttribute , isPandaProp , resolveLonghand } from '../utils/helpers'
22import { type Rule , createRule } from '../utils'
3- import { isIdentifier , isJSXIdentifier , isLiteral , isJSXExpressionContainer } from '../utils/nodes'
4- import { physicalProperties , physicalPropertyValues } from '../utils/physical-properties'
5- import type { TSESTree , TSESLint } from '@typescript-eslint/utils'
6-
7- type CacheMap < K extends object , V > = WeakMap < K , V | undefined >
8- type ValueNode = TSESTree . Property [ 'value' ] | TSESTree . JSXAttribute [ 'value' ]
9- type IdentifierNode = TSESTree . Identifier | TSESTree . JSXIdentifier
10- type RuleContextType = TSESLint . RuleContext < keyof typeof MESSAGES , [ { whitelist : string [ ] } ] >
3+ import { isIdentifier , isJSXIdentifier } from '../utils/nodes'
4+ import { physicalProperties } from '../utils/physical-properties'
5+ import type { TSESTree } from '@typescript-eslint/utils'
116
127export const RULE_NAME = 'no-physical-properties'
138
14- const MESSAGES = {
15- physical : 'Use logical property instead of {{physical}}. Prefer `{{logical}}`.' ,
16- physicalValue : 'Use logical value instead of {{physical}}. Prefer `{{logical}}`.' ,
17- replace : 'Replace `{{physical}}` with `{{logical}}`.' ,
18- } as const
19-
20- class PropertyCache {
21- private longhandCache = new Map < string , string > ( )
22- private pandaPropCache : CacheMap < TSESTree . JSXAttribute , boolean > = new WeakMap ( )
23- private pandaAttributeCache : CacheMap < TSESTree . Property , boolean > = new WeakMap ( )
24- private recipeVariantCache : CacheMap < TSESTree . Property , boolean > = new WeakMap ( )
25-
26- getLonghand ( name : string , context : RuleContextType ) : string {
27- if ( this . longhandCache . has ( name ) ) {
28- return this . longhandCache . get ( name ) !
29- }
30- const longhand = resolveLonghand ( name , context ) ?? name
31- this . longhandCache . set ( name , longhand )
32- return longhand
33- }
34-
35- isPandaProp ( node : TSESTree . JSXAttribute , context : RuleContextType ) : boolean {
36- if ( this . pandaPropCache . has ( node ) ) {
37- return this . pandaPropCache . get ( node ) !
38- }
39- const result = isPandaProp ( node , context )
40- this . pandaPropCache . set ( node , result )
41- return ! ! result
42- }
43-
44- isPandaAttribute ( node : TSESTree . Property , context : RuleContextType ) : boolean {
45- if ( this . pandaAttributeCache . has ( node ) ) {
46- return this . pandaAttributeCache . get ( node ) !
47- }
48- const result = isPandaAttribute ( node , context )
49- this . pandaAttributeCache . set ( node , result )
50- return ! ! result
51- }
52-
53- isRecipeVariant ( node : TSESTree . Property , context : RuleContextType ) : boolean {
54- if ( this . recipeVariantCache . has ( node ) ) {
55- return this . recipeVariantCache . get ( node ) !
56- }
57- const result = isRecipeVariant ( node , context )
58- this . recipeVariantCache . set ( node , result )
59- return ! ! result
60- }
61- }
62-
63- const extractStringLiteralValue = ( valueNode : ValueNode ) : string | null => {
64- if ( isLiteral ( valueNode ) && typeof valueNode . value === 'string' ) {
65- return valueNode . value
66- }
67-
68- if (
69- isJSXExpressionContainer ( valueNode ) &&
70- isLiteral ( valueNode . expression ) &&
71- typeof valueNode . expression . value === 'string'
72- ) {
73- return valueNode . expression . value
74- }
75-
76- return null
77- }
78-
79- const createPropertyReport = (
80- node : IdentifierNode ,
81- longhandName : string ,
82- logical : string ,
83- context : RuleContextType ,
84- ) => {
85- const physicalName = `\`${ node . name } \`${ longhandName !== node . name ? ` (resolved to \`${ longhandName } \`)` : '' } `
86-
87- context . report ( {
88- node,
89- messageId : 'physical' ,
90- data : { physical : physicalName , logical } ,
91- suggest : [
92- {
93- messageId : 'replace' ,
94- data : { physical : node . name , logical } ,
95- fix : ( fixer : TSESLint . RuleFixer ) => fixer . replaceText ( node , logical ) ,
96- } ,
97- ] ,
98- } )
99- }
100-
101- const createValueReport = (
102- valueNode : NonNullable < ValueNode > ,
103- valueText : string ,
104- logical : string ,
105- context : RuleContextType ,
106- ) => {
107- context . report ( {
108- node : valueNode ,
109- messageId : 'physicalValue' ,
110- data : { physical : `"${ valueText } "` , logical : `"${ logical } "` } ,
111- suggest : [
112- {
113- messageId : 'replace' ,
114- data : { physical : `"${ valueText } "` , logical : `"${ logical } "` } ,
115- fix : ( fixer : TSESLint . RuleFixer ) => {
116- if ( isLiteral ( valueNode ) ) {
117- return fixer . replaceText ( valueNode , `"${ logical } "` )
118- }
119- if ( isJSXExpressionContainer ( valueNode ) && isLiteral ( valueNode . expression ) ) {
120- return fixer . replaceText ( valueNode . expression , `"${ logical } "` )
121- }
122- return null
123- } ,
124- } ,
125- ] ,
126- } )
127- }
128-
1299const rule : Rule = createRule ( {
13010 name : RULE_NAME ,
13111 meta : {
13212 docs : {
13313 description :
13414 'Encourage the use of logical properties over physical properties to foster a responsive and adaptable user interface.' ,
13515 } ,
136- messages : MESSAGES ,
16+ messages : {
17+ physical : 'Use logical property instead of {{physical}}. Prefer `{{logical}}`.' ,
18+ replace : 'Replace `{{physical}}` with `{{logical}}`.' ,
19+ } ,
13720 type : 'suggestion' ,
13821 hasSuggestions : true ,
13922 schema : [
@@ -153,54 +36,102 @@ const rule: Rule = createRule({
15336 } ,
15437 ] ,
15538 } ,
156- defaultOptions : [ { whitelist : [ ] } ] ,
39+ defaultOptions : [
40+ {
41+ whitelist : [ ] ,
42+ } ,
43+ ] ,
15744 create ( context ) {
15845 const whitelist : string [ ] = context . options [ 0 ] ?. whitelist ?? [ ]
159- const cache = new PropertyCache ( )
16046
161- const checkPropertyName = ( node : IdentifierNode ) => {
162- if ( whitelist . includes ( node . name ) ) return
163- const longhandName = cache . getLonghand ( node . name , context )
164- if ( ! ( longhandName in physicalProperties ) ) return
47+ // Cache for resolved longhand properties
48+ const longhandCache = new Map < string , string > ( )
49+
50+ // Cache for helper functions
51+ const pandaPropCache = new WeakMap < TSESTree . JSXAttribute , boolean | undefined > ( )
52+ const pandaAttributeCache = new WeakMap < TSESTree . Property , boolean | undefined > ( )
53+ const recipeVariantCache = new WeakMap < TSESTree . Property , boolean | undefined > ( )
54+
55+ const getLonghand = ( name : string ) : string => {
56+ if ( longhandCache . has ( name ) ) {
57+ return longhandCache . get ( name ) !
58+ }
59+ const longhand = resolveLonghand ( name , context ) ?? name
60+ longhandCache . set ( name , longhand )
61+ return longhand
62+ }
16563
166- const logical = physicalProperties [ longhandName ]
167- createPropertyReport ( node , longhandName , logical , context )
64+ const isCachedPandaProp = ( node : TSESTree . JSXAttribute ) : boolean => {
65+ if ( pandaPropCache . has ( node ) ) {
66+ return pandaPropCache . get ( node ) !
67+ }
68+ const result = isPandaProp ( node , context )
69+ pandaPropCache . set ( node , result )
70+ return ! ! result
16871 }
16972
170- const checkPropertyValue = ( keyNode : IdentifierNode , valueNode : NonNullable < ValueNode > ) : boolean => {
171- const propName = keyNode . name
172- if ( ! ( propName in physicalPropertyValues ) ) return false
73+ const isCachedPandaAttribute = ( node : TSESTree . Property ) : boolean => {
74+ if ( pandaAttributeCache . has ( node ) ) {
75+ return pandaAttributeCache . get ( node ) !
76+ }
77+ const result = isPandaAttribute ( node , context )
78+ pandaAttributeCache . set ( node , result )
79+ return ! ! result
80+ }
17381
174- const valueText = extractStringLiteralValue ( valueNode )
175- if ( valueText === null ) return false
82+ const isCachedRecipeVariant = ( node : TSESTree . Property ) : boolean => {
83+ if ( recipeVariantCache . has ( node ) ) {
84+ return recipeVariantCache . get ( node ) !
85+ }
86+ const result = isRecipeVariant ( node , context )
87+ recipeVariantCache . set ( node , result )
88+ return ! ! result
89+ }
17690
177- const valueMap = physicalPropertyValues [ propName ]
178- if ( ! valueMap [ valueText ] ) return false
91+ const sendReport = ( node : TSESTree . Identifier | TSESTree . JSXIdentifier ) => {
92+ if ( whitelist . includes ( node . name ) ) return
93+ const longhandName = getLonghand ( node . name )
94+ if ( ! ( longhandName in physicalProperties ) ) return
17995
180- createValueReport ( valueNode , valueText , valueMap [ valueText ] , context )
181- return true
96+ const logical = physicalProperties [ longhandName ]
97+ const physicalName = `\`${ node . name } \`${ longhandName !== node . name ? ` (resolved to \`${ longhandName } \`)` : '' } `
98+
99+ context . report ( {
100+ node,
101+ messageId : 'physical' ,
102+ data : {
103+ physical : physicalName ,
104+ logical,
105+ } ,
106+ suggest : [
107+ {
108+ messageId : 'replace' ,
109+ data : {
110+ physical : node . name ,
111+ logical,
112+ } ,
113+ fix : ( fixer ) => {
114+ return fixer . replaceText ( node , logical )
115+ } ,
116+ } ,
117+ ] ,
118+ } )
182119 }
183120
184121 return {
185122 JSXAttribute ( node : TSESTree . JSXAttribute ) {
186123 if ( ! isJSXIdentifier ( node . name ) ) return
187- if ( ! cache . isPandaProp ( node , context ) ) return
124+ if ( ! isCachedPandaProp ( node ) ) return
188125
189- checkPropertyName ( node . name )
190- if ( node . value ) {
191- checkPropertyValue ( node . name , node . value )
192- }
126+ sendReport ( node . name )
193127 } ,
194128
195129 Property ( node : TSESTree . Property ) {
196130 if ( ! isIdentifier ( node . key ) ) return
197- if ( ! cache . isPandaAttribute ( node , context ) ) return
198- if ( cache . isRecipeVariant ( node , context ) ) return
131+ if ( ! isCachedPandaAttribute ( node ) ) return
132+ if ( isCachedRecipeVariant ( node ) ) return
199133
200- checkPropertyName ( node . key )
201- if ( node . value ) {
202- checkPropertyValue ( node . key , node . value )
203- }
134+ sendReport ( node . key )
204135 } ,
205136 }
206137 } ,
0 commit comments