1
1
import { isRecipeVariant , isPandaAttribute , isPandaProp , resolveLonghand } from '../utils/helpers'
2
2
import { 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'
11
6
12
7
export const RULE_NAME = 'no-physical-properties'
13
8
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
-
129
9
const rule : Rule = createRule ( {
130
10
name : RULE_NAME ,
131
11
meta : {
132
12
docs : {
133
13
description :
134
14
'Encourage the use of logical properties over physical properties to foster a responsive and adaptable user interface.' ,
135
15
} ,
136
- messages : MESSAGES ,
16
+ messages : {
17
+ physical : 'Use logical property instead of {{physical}}. Prefer `{{logical}}`.' ,
18
+ replace : 'Replace `{{physical}}` with `{{logical}}`.' ,
19
+ } ,
137
20
type : 'suggestion' ,
138
21
hasSuggestions : true ,
139
22
schema : [
@@ -153,54 +36,102 @@ const rule: Rule = createRule({
153
36
} ,
154
37
] ,
155
38
} ,
156
- defaultOptions : [ { whitelist : [ ] } ] ,
39
+ defaultOptions : [
40
+ {
41
+ whitelist : [ ] ,
42
+ } ,
43
+ ] ,
157
44
create ( context ) {
158
45
const whitelist : string [ ] = context . options [ 0 ] ?. whitelist ?? [ ]
159
- const cache = new PropertyCache ( )
160
46
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
+ }
165
63
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
168
71
}
169
72
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
+ }
173
81
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
+ }
176
90
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
179
95
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
+ } )
182
119
}
183
120
184
121
return {
185
122
JSXAttribute ( node : TSESTree . JSXAttribute ) {
186
123
if ( ! isJSXIdentifier ( node . name ) ) return
187
- if ( ! cache . isPandaProp ( node , context ) ) return
124
+ if ( ! isCachedPandaProp ( node ) ) return
188
125
189
- checkPropertyName ( node . name )
190
- if ( node . value ) {
191
- checkPropertyValue ( node . name , node . value )
192
- }
126
+ sendReport ( node . name )
193
127
} ,
194
128
195
129
Property ( node : TSESTree . Property ) {
196
130
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
199
133
200
- checkPropertyName ( node . key )
201
- if ( node . value ) {
202
- checkPropertyValue ( node . key , node . value )
203
- }
134
+ sendReport ( node . key )
204
135
} ,
205
136
}
206
137
} ,
0 commit comments