@trypattern/sdk 0.0.0-stage → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/README.md +127 -2
  2. package/dist/access.d.ts +31 -0
  3. package/dist/access.js +72 -0
  4. package/dist/actions.d.ts +57 -0
  5. package/dist/actions.js +198 -0
  6. package/dist/api.d.ts +30 -0
  7. package/dist/api.js +60 -0
  8. package/dist/app.d.ts +115 -0
  9. package/dist/app.js +266 -0
  10. package/dist/appInfo.d.ts +35 -0
  11. package/dist/appInfo.js +67 -0
  12. package/dist/appTitle.d.ts +6 -0
  13. package/dist/appTitle.js +3 -0
  14. package/dist/appearance.d.ts +7 -0
  15. package/dist/appearance.js +2 -0
  16. package/dist/auth/sessionApi.d.ts +41 -0
  17. package/dist/auth/sessionApi.js +158 -0
  18. package/dist/auth/sessionStore.d.ts +20 -0
  19. package/dist/auth/sessionStore.js +40 -0
  20. package/dist/auth/user.d.ts +11 -0
  21. package/dist/auth/user.js +9 -0
  22. package/dist/builtins/Account.d.ts +25 -0
  23. package/dist/builtins/Account.js +15 -0
  24. package/dist/builtins/AppSettings.d.ts +12 -0
  25. package/dist/builtins/AppSettings.js +31 -0
  26. package/dist/builtins/Palette.d.ts +29 -0
  27. package/dist/builtins/Palette.js +63 -0
  28. package/dist/builtins/SignIn.d.ts +28 -0
  29. package/dist/builtins/SignIn.js +60 -0
  30. package/dist/builtins/Toasts.d.ts +32 -0
  31. package/dist/builtins/Toasts.js +25 -0
  32. package/dist/builtins/actions.d.ts +2 -0
  33. package/dist/builtins/actions.js +45 -0
  34. package/dist/cache.d.ts +24 -0
  35. package/dist/cache.js +112 -0
  36. package/dist/cli/app.d.ts +2 -0
  37. package/dist/cli/app.js +1406 -0
  38. package/dist/cli/publishing.d.ts +21 -0
  39. package/dist/cli/publishing.js +493 -0
  40. package/dist/cli/screenshot.d.ts +1 -0
  41. package/dist/cli/screenshot.js +96 -0
  42. package/dist/cli/tools.d.ts +12 -0
  43. package/dist/cli/tools.js +35 -0
  44. package/dist/clock.d.ts +6 -0
  45. package/dist/clock.js +31 -0
  46. package/dist/components/AppFrame.d.ts +14 -0
  47. package/dist/components/AppFrame.js +64 -0
  48. package/dist/components/AppearancePicker.d.ts +1 -0
  49. package/dist/components/AppearancePicker.js +12 -0
  50. package/dist/components/Avatar.d.ts +9 -0
  51. package/dist/components/Avatar.js +63 -0
  52. package/dist/components/Badge.d.ts +11 -0
  53. package/dist/components/Badge.js +87 -0
  54. package/dist/components/Button.d.ts +24 -0
  55. package/dist/components/Button.js +252 -0
  56. package/dist/components/Card.d.ts +12 -0
  57. package/dist/components/Card.js +38 -0
  58. package/dist/components/CommandMenu.d.ts +23 -0
  59. package/dist/components/CommandMenu.js +214 -0
  60. package/dist/components/CommandPalette.d.ts +1 -0
  61. package/dist/components/CommandPalette.js +7 -0
  62. package/dist/components/Content.d.ts +7 -0
  63. package/dist/components/Content.js +24 -0
  64. package/dist/components/ContextMenu.d.ts +8 -0
  65. package/dist/components/ContextMenu.js +23 -0
  66. package/dist/components/CopyButton.d.ts +11 -0
  67. package/dist/components/CopyButton.js +16 -0
  68. package/dist/components/Dialog.d.ts +11 -0
  69. package/dist/components/Dialog.js +81 -0
  70. package/dist/components/Divider.d.ts +7 -0
  71. package/dist/components/Divider.js +30 -0
  72. package/dist/components/EmptyState.d.ts +8 -0
  73. package/dist/components/EmptyState.js +24 -0
  74. package/dist/components/Form.d.ts +9 -0
  75. package/dist/components/Form.js +23 -0
  76. package/dist/components/Icon.d.ts +10 -0
  77. package/dist/components/Icon.js +22 -0
  78. package/dist/components/IconButton.d.ts +14 -0
  79. package/dist/components/IconButton.js +53 -0
  80. package/dist/components/Kbd.d.ts +6 -0
  81. package/dist/components/Kbd.js +67 -0
  82. package/dist/components/Link.d.ts +14 -0
  83. package/dist/components/Link.js +44 -0
  84. package/dist/components/ListRow.d.ts +12 -0
  85. package/dist/components/ListRow.js +62 -0
  86. package/dist/components/LoadingRows.d.ts +5 -0
  87. package/dist/components/LoadingRows.js +6 -0
  88. package/dist/components/Menu.d.ts +12 -0
  89. package/dist/components/Menu.js +33 -0
  90. package/dist/components/MenuItems.d.ts +21 -0
  91. package/dist/components/MenuItems.js +32 -0
  92. package/dist/components/Placement.d.ts +21 -0
  93. package/dist/components/Placement.js +1 -0
  94. package/dist/components/Popup.d.ts +8 -0
  95. package/dist/components/Popup.js +43 -0
  96. package/dist/components/PopupItem.d.ts +8 -0
  97. package/dist/components/PopupItem.js +51 -0
  98. package/dist/components/PropertyRow.d.ts +7 -0
  99. package/dist/components/PropertyRow.js +23 -0
  100. package/dist/components/RadioGroup.d.ts +18 -0
  101. package/dist/components/RadioGroup.js +85 -0
  102. package/dist/components/Row.d.ts +16 -0
  103. package/dist/components/Row.js +27 -0
  104. package/dist/components/Screen.d.ts +13 -0
  105. package/dist/components/Screen.js +56 -0
  106. package/dist/components/Scrim.d.ts +8 -0
  107. package/dist/components/Scrim.js +40 -0
  108. package/dist/components/ScrollArea.d.ts +15 -0
  109. package/dist/components/ScrollArea.js +13 -0
  110. package/dist/components/SegmentedControl.d.ts +17 -0
  111. package/dist/components/SegmentedControl.js +68 -0
  112. package/dist/components/Select.d.ts +17 -0
  113. package/dist/components/Select.js +107 -0
  114. package/dist/components/SettingsRow.d.ts +8 -0
  115. package/dist/components/SettingsRow.js +32 -0
  116. package/dist/components/Sidebar.d.ts +20 -0
  117. package/dist/components/Sidebar.js +137 -0
  118. package/dist/components/SignInScreen.d.ts +1 -0
  119. package/dist/components/SignInScreen.js +40 -0
  120. package/dist/components/Size.d.ts +1 -0
  121. package/dist/components/Size.js +1 -0
  122. package/dist/components/Skeleton.d.ts +8 -0
  123. package/dist/components/Skeleton.js +48 -0
  124. package/dist/components/Spinner.d.ts +6 -0
  125. package/dist/components/Spinner.js +54 -0
  126. package/dist/components/Stack.d.ts +14 -0
  127. package/dist/components/Stack.js +19 -0
  128. package/dist/components/Switch.d.ts +10 -0
  129. package/dist/components/Switch.js +72 -0
  130. package/dist/components/Text.d.ts +21 -0
  131. package/dist/components/Text.js +77 -0
  132. package/dist/components/TextField.d.ts +25 -0
  133. package/dist/components/TextField.js +68 -0
  134. package/dist/components/Toaster.d.ts +1 -0
  135. package/dist/components/Toaster.js +83 -0
  136. package/dist/components/Tooltip.d.ts +8 -0
  137. package/dist/components/Tooltip.js +43 -0
  138. package/dist/components/controlLook.d.ts +70 -0
  139. package/dist/components/controlLook.js +80 -0
  140. package/dist/components/frame.d.ts +4 -0
  141. package/dist/components/frame.js +2 -0
  142. package/dist/components/icons.d.ts +59 -0
  143. package/dist/components/icons.js +111 -0
  144. package/dist/components/interaction.d.ts +12 -0
  145. package/dist/components/interaction.js +22 -0
  146. package/dist/components/spacing.d.ts +118 -0
  147. package/dist/components/spacing.js +46 -0
  148. package/dist/components/tones.d.ts +28 -0
  149. package/dist/components/tones.js +12 -0
  150. package/dist/entityStore.d.ts +46 -0
  151. package/dist/entityStore.js +160 -0
  152. package/dist/flows/accessFlows.d.ts +9 -0
  153. package/dist/flows/accessFlows.js +167 -0
  154. package/dist/flows/browser.d.ts +34 -0
  155. package/dist/flows/browser.js +167 -0
  156. package/dist/flows/contactSheet.d.ts +1 -0
  157. package/dist/flows/contactSheet.js +63 -0
  158. package/dist/flows/flowBrowser.d.ts +25 -0
  159. package/dist/flows/flowBrowser.js +23 -0
  160. package/dist/flows/flowSetup.d.ts +3 -0
  161. package/dist/flows/flowSetup.js +35 -0
  162. package/dist/flows/flows.d.ts +47 -0
  163. package/dist/flows/flows.js +175 -0
  164. package/dist/flows/screenshotName.d.ts +1 -0
  165. package/dist/flows/screenshotName.js +6 -0
  166. package/dist/gate.d.ts +8 -0
  167. package/dist/gate.js +52 -0
  168. package/dist/hooks/appContext.d.ts +15 -0
  169. package/dist/hooks/appContext.js +5 -0
  170. package/dist/hooks/appHooks.d.ts +21 -0
  171. package/dist/hooks/appHooks.js +24 -0
  172. package/dist/hooks/useAnimatesOnChange.d.ts +1 -0
  173. package/dist/hooks/useAnimatesOnChange.js +14 -0
  174. package/dist/hooks/useStoreState.d.ts +6 -0
  175. package/dist/hooks/useStoreState.js +5 -0
  176. package/dist/index.d.ts +44 -0
  177. package/dist/index.js +21 -0
  178. package/dist/memoryApi.d.ts +19 -0
  179. package/dist/memoryApi.js +42 -0
  180. package/dist/model.d.ts +94 -0
  181. package/dist/model.js +243 -0
  182. package/dist/page.d.ts +11 -0
  183. package/dist/page.js +9 -0
  184. package/dist/platform.d.ts +1 -0
  185. package/dist/platform.js +5 -0
  186. package/dist/publishWire.d.ts +271 -0
  187. package/dist/publishWire.js +168 -0
  188. package/dist/react.d.ts +49 -0
  189. package/dist/react.js +40 -0
  190. package/dist/registered.d.ts +60 -0
  191. package/dist/registered.js +19 -0
  192. package/dist/routes.d.ts +38 -0
  193. package/dist/routes.js +21 -0
  194. package/dist/rules/checkAllFiles.d.ts +1 -0
  195. package/dist/rules/checkAllFiles.js +749 -0
  196. package/dist/rules/checkApiRouting.d.ts +1 -0
  197. package/dist/rules/checkApiRouting.js +112 -0
  198. package/dist/rules/lintPlugin.d.ts +252 -0
  199. package/dist/rules/lintPlugin.js +1159 -0
  200. package/dist/rules/messages.d.ts +48 -0
  201. package/dist/rules/messages.js +48 -0
  202. package/dist/rules/themeTokens.d.ts +6 -0
  203. package/dist/rules/themeTokens.js +64 -0
  204. package/dist/schema.d.ts +4 -0
  205. package/dist/schema.js +2 -0
  206. package/dist/server/auth.d.ts +115 -0
  207. package/dist/server/auth.js +134 -0
  208. package/dist/server/authService.d.ts +9 -0
  209. package/dist/server/authService.js +50 -0
  210. package/dist/server/createServer.d.ts +402 -0
  211. package/dist/server/createServer.js +82 -0
  212. package/dist/server/env.d.ts +15 -0
  213. package/dist/server/env.js +1 -0
  214. package/dist/server/index.d.ts +13 -0
  215. package/dist/server/index.js +9 -0
  216. package/dist/server/services.d.ts +9 -0
  217. package/dist/server/services.js +47 -0
  218. package/dist/server/session.d.ts +29 -0
  219. package/dist/server/session.js +65 -0
  220. package/dist/server/sessions.d.ts +92 -0
  221. package/dist/server/sessions.js +47 -0
  222. package/dist/server/settings.d.ts +49 -0
  223. package/dist/server/settings.js +25 -0
  224. package/dist/server/signInWire.d.ts +53 -0
  225. package/dist/server/signInWire.js +23 -0
  226. package/dist/server/tables.d.ts +242 -0
  227. package/dist/server/tables.js +246 -0
  228. package/dist/server/users.d.ts +189 -0
  229. package/dist/server/users.js +49 -0
  230. package/dist/serviceClients.d.ts +5 -0
  231. package/dist/serviceClients.js +70 -0
  232. package/dist/services.d.ts +53 -0
  233. package/dist/services.js +30 -0
  234. package/dist/shortcuts.d.ts +3 -0
  235. package/dist/shortcuts.js +40 -0
  236. package/dist/start.d.ts +20 -0
  237. package/dist/start.js +145 -0
  238. package/dist/state.d.ts +14 -0
  239. package/dist/state.js +17 -0
  240. package/dist/store.d.ts +45 -0
  241. package/dist/store.js +208 -0
  242. package/dist/subscribers.d.ts +5 -0
  243. package/dist/subscribers.js +13 -0
  244. package/dist/table.d.ts +143 -0
  245. package/dist/table.js +91 -0
  246. package/dist/tables.d.ts +77 -0
  247. package/dist/tables.js +216 -0
  248. package/dist/theme/reset.css +84 -0
  249. package/dist/theme/scales.d.ts +76 -0
  250. package/dist/theme/scales.js +82 -0
  251. package/dist/theme/tokens.stylex.d.ts +218 -0
  252. package/dist/theme/tokens.stylex.js +233 -0
  253. package/dist/vite/commandBridge.d.ts +2 -0
  254. package/dist/vite/commandBridge.js +46 -0
  255. package/dist/vite/designManifest.d.ts +25 -0
  256. package/dist/vite/designManifest.js +191 -0
  257. package/dist/vite/localWorker.d.ts +4 -0
  258. package/dist/vite/localWorker.js +60 -0
  259. package/dist/vite/projectFile.d.ts +9 -0
  260. package/dist/vite/projectFile.js +17 -0
  261. package/dist/vite/rootAlias.d.ts +2 -0
  262. package/dist/vite/rootAlias.js +15 -0
  263. package/dist/vite/vite.d.ts +8 -0
  264. package/dist/vite/vite.js +95 -0
  265. package/dist/wire.d.ts +7 -0
  266. package/dist/wire.js +1 -0
  267. package/package.json +103 -3
  268. package/presets/lintPlugin.js +7 -0
  269. package/presets/oxlint.json +167 -0
  270. package/presets/register.d.ts +21 -0
  271. package/presets/server.d.ts +2 -0
  272. package/presets/tsconfig.app.json +29 -0
  273. package/presets/tsconfig.server.json +27 -0
@@ -0,0 +1,1159 @@
1
+ import { ACTIONS_WHERE_MESSAGE, COMPONENT_FIRST_MESSAGE, COMPONENT_FILE_NAME_MESSAGE, DARK_MODE_IN_TOKENS_MESSAGE, DEV_ONLY_SIGN_IN_MESSAGE, EXPLICIT_ACCESS_MESSAGE, USE_BREAKPOINTS_MESSAGE, HOVER_USES_HIGHLIGHT_MESSAGE, NO_CLASSNAME_STRINGS_MESSAGE, NO_COMMENTS_MESSAGE, NO_INTERACTIVE_TRANSITION_MESSAGE, NO_POINTER_CURSOR_MESSAGE, NO_ROUTE_DATA_APIS_MESSAGE, ROUTES_UNDER_APP_LAYOUT_MESSAGE, SCROLL_VIEW_ONLY_MESSAGE, SELECTABLE_ONLY_MESSAGE, STATE_VIA_HOOKS_MESSAGE, MODEL_BY_NAME_MESSAGE, MODEL_MEMBERS_MESSAGE, PURE_ACTIONS_MESSAGE, PURE_GETTERS_MESSAGE, USE_MODEL_WHERE_MESSAGE, CLASSES_NOT_FACTORIES_MESSAGE, STORES_IN_APP_MESSAGE, TYPED_STYLE_PROP_MESSAGE, USE_COLOR_TOKEN_MESSAGE, USE_FONT_TOKEN_MESSAGE, USE_LINK_MESSAGE, USE_PRIMITIVES_MESSAGE, NO_WIDGET_ROLES_MESSAGE, NO_CLICK_ON_STATIC_MESSAGE, PRIMITIVE_NAMES_MESSAGE, USE_SPACE_TOKENS_MESSAGE, USE_SIZE_TOKENS_MESSAGE, TEXT_VIA_TEXT_MESSAGE, TEXT_ELEMENTS_MESSAGE, LOOSE_TEXT_MESSAGE, COMPOSE_IN_SCREENS_MESSAGE, NO_HOVER_IN_APP_MESSAGE } from "./messages.js";
2
+ const TOKENS_FILE = 'src/theme/tokens.stylex.ts';
3
+ const APP_THEME_FILE = 'src/theme/theme.ts';
4
+ const SCROLL_AREA_FILE = 'src/components/ScrollArea.tsx';
5
+ const SELECTABLE_FILE = 'src/components/Text.tsx';
6
+ const PRIMITIVE_ELEMENTS = new Set([
7
+ 'button',
8
+ 'input',
9
+ 'textarea',
10
+ 'select',
11
+ 'dialog',
12
+ 'progress',
13
+ 'meter'
14
+ ]);
15
+ const WIDGET_ROLES = new Set([
16
+ 'button',
17
+ 'switch',
18
+ 'checkbox',
19
+ 'radio',
20
+ 'menu',
21
+ 'menuitem',
22
+ 'tooltip',
23
+ 'dialog',
24
+ 'slider',
25
+ 'progressbar',
26
+ 'separator',
27
+ 'combobox',
28
+ 'listbox',
29
+ 'option'
30
+ ]);
31
+ const STATIC_ELEMENTS = new Set([
32
+ 'div',
33
+ 'span',
34
+ 'li',
35
+ 'section',
36
+ 'article',
37
+ 'p'
38
+ ]);
39
+ const CLICK_HANDLERS = new Set([
40
+ 'onClick',
41
+ 'onPointerDown',
42
+ 'onMouseDown',
43
+ 'onKeyDown'
44
+ ]);
45
+ const PRIMITIVES = new Set([
46
+ 'AppFrame',
47
+ 'AppearancePicker',
48
+ 'Avatar',
49
+ 'Badge',
50
+ 'Button',
51
+ 'Card',
52
+ 'CommandMenu',
53
+ 'CommandPalette',
54
+ 'Content',
55
+ 'ContextMenu',
56
+ 'Dialog',
57
+ 'Divider',
58
+ 'CopyButton',
59
+ 'EmptyState',
60
+ 'Form',
61
+ 'Icon',
62
+ 'IconButton',
63
+ 'Kbd',
64
+ 'Link',
65
+ 'ListRow',
66
+ 'LoadingRows',
67
+ 'Menu',
68
+ 'PropertyRow',
69
+ 'RadioGroup',
70
+ 'Row',
71
+ 'Screen',
72
+ 'ScrollArea',
73
+ 'SegmentedControl',
74
+ 'Select',
75
+ 'SettingsRow',
76
+ 'SignInScreen',
77
+ 'Skeleton',
78
+ 'Spinner',
79
+ 'Stack',
80
+ 'Switch',
81
+ 'Text',
82
+ 'TextField',
83
+ 'Toaster',
84
+ 'Tooltip'
85
+ ]);
86
+ const SCREEN_FILE = /(^|\/)src\/(screens|layouts)\//;
87
+ const STYLE_SOURCES = new Set([
88
+ '@stylexjs/stylex',
89
+ '@trypattern/sdk/tokens.stylex'
90
+ ]);
91
+ const POINTER_CONDITIONS = new Set([
92
+ ':hover',
93
+ ':active',
94
+ ':focus',
95
+ ':focus-visible',
96
+ ':focus-within'
97
+ ]);
98
+ const TEXT_PROPERTIES = new Set([
99
+ 'font',
100
+ 'fontSize',
101
+ 'fontWeight',
102
+ 'fontFamily',
103
+ 'fontStyle',
104
+ 'lineHeight',
105
+ 'letterSpacing',
106
+ 'textTransform',
107
+ 'textAlign',
108
+ 'textOverflow',
109
+ 'textDecoration',
110
+ 'whiteSpace'
111
+ ]);
112
+ const TEXT_ELEMENTS = new Set([
113
+ 'p',
114
+ 'h1',
115
+ 'h2',
116
+ 'h3',
117
+ 'h4',
118
+ 'h5',
119
+ 'h6',
120
+ 'label',
121
+ 'strong',
122
+ 'em',
123
+ 'small'
124
+ ]);
125
+ const SPACING_PROPERTY = /^(padding|margin)(Block|Inline)?(Start|End)?$|^(gap|rowGap|columnGap)$/;
126
+ const SIZE_PROPERTIES = new Set([
127
+ 'height',
128
+ 'minHeight',
129
+ 'width',
130
+ 'fontSize',
131
+ 'lineHeight'
132
+ ]);
133
+ const SPACING_TOKEN_GROUPS = new Set([
134
+ 'space',
135
+ 'controls',
136
+ 'borders',
137
+ 'lineHeights'
138
+ ]);
139
+ const SIZE_TOKEN_GROUPS = new Set([
140
+ 'controls',
141
+ 'iconSizes',
142
+ 'lineHeights',
143
+ 'fontSizes',
144
+ 'borders',
145
+ 'space',
146
+ 'widths'
147
+ ]);
148
+ const SIZE_KEYWORDS = /^(auto|inherit|fit-content|max-content|min-content|\d+(\.\d+)?%|\d+(\.\d+)?v[hw]|var\(--[\w-]+\))$/;
149
+ const ROUTES_FILE = 'src/routes.tsx';
150
+ const COMPONENT_FILE = /src\/(components|screens|layouts)\/(?:[^/]+\/)*([^/]+)\.tsx$/;
151
+ const COLOR_KEYWORDS = new Set([
152
+ 'transparent',
153
+ 'currentColor',
154
+ 'currentcolor',
155
+ 'inherit',
156
+ 'initial',
157
+ 'unset',
158
+ 'revert',
159
+ 'none'
160
+ ]);
161
+ const RAW_COLOR = /#[0-9a-f]{3,8}\b|\b(rgba?|hsla?|hwb|lab|lch|oklab|oklch|color)\(/i;
162
+ const FONT_PROPERTIES = new Set([
163
+ 'fontSize',
164
+ 'fontFamily',
165
+ 'fontWeight',
166
+ 'font'
167
+ ]);
168
+ const INTERACTIVE_CONDITIONS = new Set([':hover', ':active']);
169
+ const SELECTED_STYLE_NAME = /(active|selected|current)$/i;
170
+ const SCROLLING_OVERFLOW_PROPERTIES = new Set([
171
+ 'overflow',
172
+ 'overflowX',
173
+ 'overflowY',
174
+ 'overflowBlock',
175
+ 'overflowInline'
176
+ ]);
177
+ const SERVER_FILE = /(^|\/)server\//;
178
+ const STORE_IMPORT = /\/stores\//;
179
+ const MAY_IMPORT_STORES = /src\/(App\.ts$|stores\/)/;
180
+ const ROUTE_DATA_KEYS = new Set([
181
+ 'loader',
182
+ 'action',
183
+ 'lazy',
184
+ 'shouldRevalidate'
185
+ ]);
186
+ function isNode(value) {
187
+ return typeof value === 'object' && value !== null && 'type' in value;
188
+ }
189
+ function child(node, key) {
190
+ const value = node[key];
191
+ return isNode(value) ? value : undefined;
192
+ }
193
+ function children(node, key) {
194
+ const value = node[key];
195
+ return Array.isArray(value) ? value.filter(isNode) : [];
196
+ }
197
+ function keyName(property) {
198
+ const key = child(property, 'key');
199
+ if (key === undefined)
200
+ return undefined;
201
+ if (key.type === 'Identifier' && property['computed'] !== true) {
202
+ return typeof key['name'] === 'string' ? key['name'] : undefined;
203
+ }
204
+ return typeof key['value'] === 'string' ? key['value'] : undefined;
205
+ }
206
+ function stringValue(node) {
207
+ return node.type === 'Literal' && typeof node['value'] === 'string'
208
+ ? node['value']
209
+ : undefined;
210
+ }
211
+ function isRawLiteral(node) {
212
+ return ((node.type === 'Literal' && node['value'] !== null) ||
213
+ node.type === 'TemplateLiteral');
214
+ }
215
+ function isMember(node, object, property) {
216
+ const target = child(node, 'object');
217
+ const name = child(node, 'property');
218
+ return (node.type === 'MemberExpression' &&
219
+ target?.type === 'Identifier' &&
220
+ target['name'] === object &&
221
+ name?.type === 'Identifier' &&
222
+ name['name'] === property);
223
+ }
224
+ function isButtonStateToken(node) {
225
+ const target = child(node, 'object');
226
+ const name = child(node, 'property');
227
+ return (node.type === 'MemberExpression' &&
228
+ target?.type === 'Identifier' &&
229
+ target['name'] === 'colors' &&
230
+ name?.type === 'Identifier' &&
231
+ typeof name['name'] === 'string' &&
232
+ (name['name'].endsWith('Hover') || name['name'].endsWith('Active')));
233
+ }
234
+ function walk(node, visit) {
235
+ visit(node);
236
+ for (const [key, value] of Object.entries(node)) {
237
+ if (key === 'parent')
238
+ continue;
239
+ if (Array.isArray(value)) {
240
+ for (const item of value)
241
+ if (isNode(item))
242
+ walk(item, visit);
243
+ }
244
+ else if (isNode(value)) {
245
+ walk(value, visit);
246
+ }
247
+ }
248
+ }
249
+ function inFile(context, path) {
250
+ return context.filename.endsWith(path);
251
+ }
252
+ function isStylexCreate(node) {
253
+ const callee = child(node, 'callee');
254
+ return callee !== undefined && isMember(callee, 'stylex', 'create');
255
+ }
256
+ function flatten(styleName, property, value, conditions, location) {
257
+ if (value.type !== 'ObjectExpression') {
258
+ return [{ styleName, property, conditions, value, location }];
259
+ }
260
+ return children(value, 'properties').flatMap((condition) => {
261
+ const name = keyName(condition) ?? '[computed]';
262
+ const conditionValue = child(condition, 'value');
263
+ return conditionValue === undefined
264
+ ? []
265
+ : flatten(styleName, property, conditionValue, [...conditions, name], condition.loc);
266
+ });
267
+ }
268
+ function styleDeclarations(createCall) {
269
+ const [namespaces] = children(createCall, 'arguments');
270
+ if (namespaces?.type !== 'ObjectExpression')
271
+ return [];
272
+ return children(namespaces, 'properties').flatMap((namespace) => {
273
+ const styleName = keyName(namespace) ?? '';
274
+ const style = child(namespace, 'value');
275
+ if (style?.type !== 'ObjectExpression')
276
+ return [];
277
+ return children(style, 'properties').flatMap((declaration) => {
278
+ const property = keyName(declaration);
279
+ const value = child(declaration, 'value');
280
+ return property === undefined || value === undefined
281
+ ? []
282
+ : flatten(styleName, property, value, [], declaration.loc);
283
+ });
284
+ });
285
+ }
286
+ function styleRule(check) {
287
+ return {
288
+ meta: { type: 'problem' },
289
+ create(context) {
290
+ return {
291
+ CallExpression(node) {
292
+ if (!isStylexCreate(node))
293
+ return;
294
+ for (const problem of check(styleDeclarations(node), context)) {
295
+ context.report({ loc: problem.location, message: problem.message });
296
+ }
297
+ }
298
+ };
299
+ }
300
+ };
301
+ }
302
+ function rule(create) {
303
+ return { meta: { type: 'problem' }, create };
304
+ }
305
+ const noComments = rule((context) => ({
306
+ Program() {
307
+ for (const comment of context.sourceCode.getAllComments()) {
308
+ if (comment.type === 'Shebang')
309
+ continue;
310
+ context.report({ loc: comment.loc, message: NO_COMMENTS_MESSAGE });
311
+ }
312
+ }
313
+ }));
314
+ const noPointerCursor = styleRule((declarations) => declarations
315
+ .filter((d) => d.property === 'cursor' && stringValue(d.value) === 'pointer')
316
+ .map((d) => ({ location: d.location, message: NO_POINTER_CURSOR_MESSAGE })));
317
+ const useTokens = styleRule((declarations, context) => {
318
+ if (inFile(context, TOKENS_FILE) || inFile(context, APP_THEME_FILE))
319
+ return [];
320
+ return declarations.flatMap((d) => {
321
+ const raw = stringValue(d.value);
322
+ const isColorProperty = /color$/i.test(d.property) ||
323
+ d.property === 'fill' ||
324
+ d.property === 'stroke';
325
+ if ((isColorProperty &&
326
+ isRawLiteral(d.value) &&
327
+ !COLOR_KEYWORDS.has(raw ?? '')) ||
328
+ (raw !== undefined && RAW_COLOR.test(raw))) {
329
+ return [{ location: d.location, message: USE_COLOR_TOKEN_MESSAGE }];
330
+ }
331
+ if (FONT_PROPERTIES.has(d.property) &&
332
+ isRawLiteral(d.value) &&
333
+ raw !== 'inherit') {
334
+ return [{ location: d.location, message: USE_FONT_TOKEN_MESSAGE }];
335
+ }
336
+ return [];
337
+ });
338
+ });
339
+ const noInteractiveTransition = styleRule((declarations) => {
340
+ const interactiveStyles = new Set(declarations
341
+ .filter((d) => d.conditions.some((condition) => INTERACTIVE_CONDITIONS.has(condition)))
342
+ .map((d) => d.styleName));
343
+ return declarations
344
+ .filter((d) => interactiveStyles.has(d.styleName) &&
345
+ /^(transition|animation)(Duration|TimingFunction)$/.test(d.property) &&
346
+ !(d.value.type === 'MemberExpression' &&
347
+ child(d.value, 'object')?.['name'] === 'motion'))
348
+ .map((d) => ({
349
+ location: d.location,
350
+ message: NO_INTERACTIVE_TRANSITION_MESSAGE
351
+ }));
352
+ });
353
+ const hoverUsesHighlight = styleRule((declarations) => declarations
354
+ .filter((d) => (d.property === 'backgroundColor' || d.property === 'background') &&
355
+ (d.conditions.some((condition) => INTERACTIVE_CONDITIONS.has(condition)) ||
356
+ (SELECTED_STYLE_NAME.test(d.styleName) &&
357
+ d.conditions.length === 0)) &&
358
+ !isMember(d.value, 'colors', 'transparentHover') &&
359
+ !(!SELECTED_STYLE_NAME.test(d.styleName) && isButtonStateToken(d.value)) &&
360
+ !(d.value.type === 'Literal' && d.value['value'] === null))
361
+ .map((d) => ({
362
+ location: d.location,
363
+ message: HOVER_USES_HIGHLIGHT_MESSAGE
364
+ })));
365
+ const scrollViewOnly = styleRule((declarations, context) => {
366
+ if (inFile(context, SCROLL_AREA_FILE))
367
+ return [];
368
+ return declarations
369
+ .filter((d) => SCROLLING_OVERFLOW_PROPERTIES.has(d.property) &&
370
+ ['auto', 'scroll'].includes(stringValue(d.value) ?? ''))
371
+ .map((d) => ({ location: d.location, message: SCROLL_VIEW_ONLY_MESSAGE }));
372
+ });
373
+ const selectableOnly = styleRule((declarations, context) => {
374
+ if (inFile(context, SELECTABLE_FILE))
375
+ return [];
376
+ return declarations
377
+ .filter((d) => /^(Webkit)?[uU]serSelect$/.test(d.property))
378
+ .map((d) => ({ location: d.location, message: SELECTABLE_ONLY_MESSAGE }));
379
+ });
380
+ const useBreakpoints = rule((context) => ({
381
+ Literal(node) {
382
+ if (inFile(context, TOKENS_FILE) || !context.filename.includes('/src/')) {
383
+ return;
384
+ }
385
+ const value = stringValue(node);
386
+ if (value?.trimStart().startsWith('@media') === true &&
387
+ !value.includes('prefers-color-scheme')) {
388
+ context.report({ loc: node.loc, message: USE_BREAKPOINTS_MESSAGE });
389
+ }
390
+ }
391
+ }));
392
+ const darkModeInTokens = rule((context) => ({
393
+ Literal(node) {
394
+ if (inFile(context, TOKENS_FILE) || !context.filename.includes('/src/')) {
395
+ return;
396
+ }
397
+ if (stringValue(node)?.includes('prefers-color-scheme')) {
398
+ context.report({ loc: node.loc, message: DARK_MODE_IN_TOKENS_MESSAGE });
399
+ }
400
+ }
401
+ }));
402
+ function returnedValues(body) {
403
+ if (body.type !== 'BlockStatement')
404
+ return [body];
405
+ const found = [];
406
+ const visit = (node) => {
407
+ if (/Function/.test(node.type))
408
+ return;
409
+ if (node.type === 'ReturnStatement') {
410
+ const argument = child(node, 'argument');
411
+ if (argument !== undefined)
412
+ found.push(argument);
413
+ return;
414
+ }
415
+ for (const [key, value] of Object.entries(node)) {
416
+ if (key === 'parent')
417
+ continue;
418
+ if (Array.isArray(value))
419
+ value.filter(isNode).forEach(visit);
420
+ else if (isNode(value))
421
+ visit(value);
422
+ }
423
+ };
424
+ children(body, 'body').forEach(visit);
425
+ return found;
426
+ }
427
+ function possibleClassNameValues(node) {
428
+ switch (node.type) {
429
+ case 'JSXExpressionContainer':
430
+ case 'ParenthesizedExpression':
431
+ case 'TSAsExpression':
432
+ case 'TSNonNullExpression':
433
+ return possibleValuesOf(node, 'expression');
434
+ case 'ConditionalExpression':
435
+ return [
436
+ ...possibleValuesOf(node, 'consequent'),
437
+ ...possibleValuesOf(node, 'alternate')
438
+ ];
439
+ case 'LogicalExpression':
440
+ return node['operator'] === '&&'
441
+ ? possibleValuesOf(node, 'right')
442
+ : [
443
+ ...possibleValuesOf(node, 'left'),
444
+ ...possibleValuesOf(node, 'right')
445
+ ];
446
+ case 'BinaryExpression':
447
+ return node['operator'] === '+'
448
+ ? [
449
+ ...possibleValuesOf(node, 'left'),
450
+ ...possibleValuesOf(node, 'right')
451
+ ]
452
+ : [];
453
+ case 'ArrowFunctionExpression':
454
+ case 'FunctionExpression': {
455
+ const body = child(node, 'body');
456
+ return body === undefined
457
+ ? []
458
+ : returnedValues(body).flatMap(possibleClassNameValues);
459
+ }
460
+ default:
461
+ return [node];
462
+ }
463
+ }
464
+ function possibleValuesOf(node, key) {
465
+ const inner = child(node, key);
466
+ return inner === undefined ? [] : possibleClassNameValues(inner);
467
+ }
468
+ const noClassNameStrings = rule((context) => ({
469
+ JSXAttribute(node) {
470
+ const name = child(node, 'name');
471
+ const value = child(node, 'value');
472
+ if (name?.['name'] !== 'className' || value === undefined)
473
+ return;
474
+ for (const candidate of possibleClassNameValues(value)) {
475
+ const literal = stringValue(candidate);
476
+ if (candidate.type === 'TemplateLiteral' ||
477
+ (literal !== undefined && literal !== '')) {
478
+ context.report({
479
+ loc: candidate.loc,
480
+ message: NO_CLASSNAME_STRINGS_MESSAGE
481
+ });
482
+ }
483
+ }
484
+ }
485
+ }));
486
+ function isTypedStyleXStyles(type, program) {
487
+ if (type?.type !== 'TSTypeReference')
488
+ return false;
489
+ const typeName = child(type, 'typeName');
490
+ const name = typeName?.['name'];
491
+ if (name === 'Placement')
492
+ return true;
493
+ if (name === 'StyleXStyles') {
494
+ const args = child(type, 'typeArguments') ?? child(type, 'typeParameters');
495
+ const [first] = args === undefined ? [] : children(args, 'params');
496
+ return (first?.type === 'TSTypeLiteral' ||
497
+ first?.type === 'TSIntersectionType' ||
498
+ first?.type === 'TSTypeReference');
499
+ }
500
+ let aliased;
501
+ walk(program, (node) => {
502
+ if (node.type === 'TSTypeAliasDeclaration' &&
503
+ child(node, 'id')?.['name'] === name) {
504
+ aliased = child(node, 'typeAnnotation');
505
+ }
506
+ });
507
+ return aliased !== undefined && isTypedStyleXStyles(aliased, program);
508
+ }
509
+ const typedStyleProp = rule((context) => {
510
+ let program;
511
+ return {
512
+ Program(node) {
513
+ program = node;
514
+ },
515
+ TSPropertySignature(node) {
516
+ if (keyName(node) !== 'style' || program === undefined)
517
+ return;
518
+ const annotation = child(node, 'typeAnnotation');
519
+ const type = annotation === undefined
520
+ ? undefined
521
+ : child(annotation, 'typeAnnotation');
522
+ if (!isTypedStyleXStyles(type, program)) {
523
+ context.report({ loc: node.loc, message: TYPED_STYLE_PROP_MESSAGE });
524
+ }
525
+ }
526
+ };
527
+ });
528
+ const componentFileName = rule((context) => ({
529
+ Program(node) {
530
+ const expected = COMPONENT_FILE.exec(context.filename)?.[2];
531
+ if (expected === undefined)
532
+ return;
533
+ const exportedFunctions = children(node, 'body')
534
+ .filter((statement) => statement.type === 'ExportNamedDeclaration')
535
+ .map((statement) => child(statement, 'declaration'))
536
+ .filter((declaration) => declaration?.type === 'FunctionDeclaration')
537
+ .map((declaration) => declaration === undefined
538
+ ? undefined
539
+ : child(declaration, 'id')?.['name']);
540
+ if (!exportedFunctions.includes(expected)) {
541
+ context.report({ loc: node.loc, message: COMPONENT_FILE_NAME_MESSAGE });
542
+ }
543
+ }
544
+ }));
545
+ const HELPER_DECLARATIONS = new Set([
546
+ 'VariableDeclaration',
547
+ 'FunctionDeclaration'
548
+ ]);
549
+ function isComponentExport(statement) {
550
+ return (statement.type === 'ExportNamedDeclaration' &&
551
+ child(statement, 'declaration')?.type === 'FunctionDeclaration');
552
+ }
553
+ const componentFirst = rule((context) => ({
554
+ Program(node) {
555
+ if (!COMPONENT_FILE.test(context.filename))
556
+ return;
557
+ for (const statement of children(node, 'body')) {
558
+ if (isComponentExport(statement))
559
+ return;
560
+ if (HELPER_DECLARATIONS.has(statement.type)) {
561
+ context.report({ loc: statement.loc, message: COMPONENT_FIRST_MESSAGE });
562
+ }
563
+ }
564
+ }
565
+ }));
566
+ const routesUnderAppLayout = rule((context) => ({
567
+ VariableDeclarator(node) {
568
+ if (!inFile(context, ROUTES_FILE))
569
+ return;
570
+ if (child(node, 'id')?.['name'] !== 'routes')
571
+ return;
572
+ const declared = child(node, 'init');
573
+ const routes = declared?.type === 'CallExpression'
574
+ ? children(declared, 'arguments')[0]
575
+ : declared;
576
+ const topLevel = routes?.type === 'ArrayExpression' ? children(routes, 'elements') : [];
577
+ const allUnderAppLayout = topLevel.length > 0 &&
578
+ topLevel.every((route) => children(route, 'properties').some((property) => keyName(property) === 'Component' &&
579
+ child(property, 'value')?.['name'] === 'AppLayout'));
580
+ if (!allUnderAppLayout) {
581
+ context.report({
582
+ loc: node.loc,
583
+ message: ROUTES_UNDER_APP_LAYOUT_MESSAGE
584
+ });
585
+ }
586
+ }
587
+ }));
588
+ const noRouteDataApis = rule((context) => ({
589
+ Property(node) {
590
+ if (inFile(context, ROUTES_FILE) &&
591
+ ROUTE_DATA_KEYS.has(keyName(node) ?? '')) {
592
+ context.report({ loc: node.loc, message: NO_ROUTE_DATA_APIS_MESSAGE });
593
+ }
594
+ }
595
+ }));
596
+ const useLink = rule((context) => ({
597
+ JSXOpeningElement(node) {
598
+ if (child(node, 'name')?.['name'] !== 'a')
599
+ return;
600
+ for (const attribute of children(node, 'attributes')) {
601
+ const value = child(attribute, 'value');
602
+ if (child(attribute, 'name')?.['name'] === 'href' &&
603
+ value !== undefined &&
604
+ stringValue(value)?.startsWith('/') === true) {
605
+ context.report({ loc: attribute.loc, message: USE_LINK_MESSAGE });
606
+ }
607
+ }
608
+ }
609
+ }));
610
+ function isTypeOnlyImport(node) {
611
+ const specifiers = children(node, 'specifiers');
612
+ return (node['importKind'] === 'type' ||
613
+ (specifiers.length > 0 &&
614
+ specifiers.every((specifier) => specifier['importKind'] === 'type')));
615
+ }
616
+ const storesInApp = rule((context) => ({
617
+ ImportDeclaration(node) {
618
+ const source = stringValue(child(node, 'source') ?? node) ?? '';
619
+ if (STORE_IMPORT.test(source) &&
620
+ !MAY_IMPORT_STORES.test(context.filename) &&
621
+ !isTypeOnlyImport(node)) {
622
+ context.report({ loc: node.loc, message: STORES_IN_APP_MESSAGE });
623
+ }
624
+ }
625
+ }));
626
+ const UNSUBSCRIBED_READS = new Set(['state', 'models', 'stores', 'getSnapshot']);
627
+ const SRC_FILE = /(^|\/)src\//;
628
+ const VIEW_FILE = /(^|\/)src\/(components|screens|layouts)\/.*\.tsx$/;
629
+ const MODEL_FILE = /(^|\/)src\/models\/[A-Z][A-Za-z]*\.ts$/;
630
+ const MAY_USE_MODEL = /(^|\/)src\/(screens|layouts)\//;
631
+ function isPublicModelMember(member) {
632
+ if (keyName(member) === 'actions')
633
+ return false;
634
+ if (member.type === 'MethodDefinition') {
635
+ return member['kind'] === 'get' || keyName(member) === 'onPageOpen';
636
+ }
637
+ if (member.type !== 'PropertyDefinition')
638
+ return false;
639
+ const value = child(member, 'value');
640
+ return (value?.type === 'CallExpression' &&
641
+ child(value, 'callee')?.['name'] === 'command');
642
+ }
643
+ const modelMembers = rule((context) => ({
644
+ ClassBody(node) {
645
+ if (!MODEL_FILE.test(context.filename))
646
+ return;
647
+ for (const member of children(node, 'body')) {
648
+ if (!CLASS_MEMBERS.has(member.type))
649
+ continue;
650
+ if (member['kind'] === 'constructor')
651
+ continue;
652
+ const access = member['accessibility'];
653
+ const isPrivateGetter = access === 'private' && member['kind'] === 'get';
654
+ const isUnexpectedPublic = access !== 'private' && !isPublicModelMember(member);
655
+ if (isPrivateGetter || isUnexpectedPublic) {
656
+ context.report({ loc: member.loc, message: MODEL_MEMBERS_MESSAGE });
657
+ }
658
+ }
659
+ }
660
+ }));
661
+ const IMPURE_CALLS = new Set([
662
+ 'Date.now',
663
+ 'Math.random',
664
+ 'crypto.randomUUID',
665
+ 'performance.now',
666
+ 'fetch'
667
+ ]);
668
+ const SKIPPED_KEYS = new Set(['parent', 'loc', 'range']);
669
+ function memberPath(node) {
670
+ if (node?.type === 'ThisExpression')
671
+ return 'this';
672
+ if (node?.type === 'Identifier')
673
+ return String(node['name']);
674
+ if (node?.type !== 'MemberExpression' || node['computed'] === true) {
675
+ return undefined;
676
+ }
677
+ const object = memberPath(child(node, 'object'));
678
+ const property = child(node, 'property');
679
+ return object === undefined || property?.type !== 'Identifier'
680
+ ? undefined
681
+ : `${object}.${String(property['name'])}`;
682
+ }
683
+ function childNodes(node) {
684
+ return Object.entries(node).flatMap(([key, value]) => {
685
+ if (SKIPPED_KEYS.has(key))
686
+ return [];
687
+ if (isNode(value))
688
+ return [value];
689
+ return Array.isArray(value) ? value.filter(isNode) : [];
690
+ });
691
+ }
692
+ function impurityOfCall(callee) {
693
+ if (callee === 'this.set')
694
+ return 'calls this.set()';
695
+ if (callee.startsWith('this.app.'))
696
+ return `calls ${callee}()`;
697
+ if (/^this\.stores\.\w+\.\w+$/.test(callee)) {
698
+ return `calls ${callee}(), a store method`;
699
+ }
700
+ return IMPURE_CALLS.has(callee) ? `reads ${callee}()` : undefined;
701
+ }
702
+ function impuritiesIn(body, members, seen) {
703
+ if (body === undefined)
704
+ return [];
705
+ const found = [];
706
+ const visit = (node) => {
707
+ if (node.type === 'CallExpression') {
708
+ const callee = memberPath(child(node, 'callee')) ?? '';
709
+ const what = impurityOfCall(callee);
710
+ if (what !== undefined)
711
+ found.push({ node, what });
712
+ const helper = /^this\.\w+$/.test(callee)
713
+ ? members.helpers.get(callee.slice(5))
714
+ : undefined;
715
+ if (helper !== undefined && !seen.has(callee)) {
716
+ seen.add(callee);
717
+ found.push(...impuritiesIn(helper, members, seen));
718
+ }
719
+ }
720
+ if (node.type === 'NewExpression' &&
721
+ memberPath(child(node, 'callee')) === 'Date') {
722
+ found.push({ node, what: 'reads new Date()' });
723
+ }
724
+ if (node.type === 'AwaitExpression')
725
+ found.push({ node, what: 'awaits' });
726
+ if (node.type === 'AssignmentExpression' &&
727
+ (memberPath(child(node, 'left')) ?? '').startsWith('this.')) {
728
+ found.push({ node, what: 'assigns to this' });
729
+ }
730
+ const path = memberPath(node);
731
+ if (node.type === 'MemberExpression' &&
732
+ path !== undefined &&
733
+ /^this\.\w+$/.test(path) &&
734
+ members.fields.has(path.slice(5))) {
735
+ found.push({ node, what: `reads the private field ${path}` });
736
+ }
737
+ for (const next of childNodes(node))
738
+ visit(next);
739
+ };
740
+ visit(body);
741
+ return found;
742
+ }
743
+ function modelMembersOf(members) {
744
+ const helpers = new Map();
745
+ const fields = new Set();
746
+ for (const member of members) {
747
+ const name = keyName(member);
748
+ if (name === undefined || member['accessibility'] !== 'private')
749
+ continue;
750
+ const value = child(member, 'value');
751
+ const isFunction = value?.type === 'FunctionExpression' ||
752
+ value?.type === 'ArrowFunctionExpression';
753
+ if (isFunction && value !== undefined)
754
+ helpers.set(name, value);
755
+ else if (member.type === 'PropertyDefinition')
756
+ fields.add(name);
757
+ }
758
+ return { helpers, fields };
759
+ }
760
+ const pureGetters = rule((context) => ({
761
+ ClassBody(node) {
762
+ if (!MODEL_FILE.test(context.filename))
763
+ return;
764
+ const members = children(node, 'body');
765
+ const known = modelMembersOf(members);
766
+ for (const member of members) {
767
+ if (member.type !== 'MethodDefinition' || member['kind'] !== 'get') {
768
+ continue;
769
+ }
770
+ for (const { node: found, what } of impuritiesIn(child(member, 'value'), known, new Set())) {
771
+ context.report({
772
+ loc: found.loc,
773
+ message: `${PURE_GETTERS_MESSAGE} This one ${what}.`
774
+ });
775
+ }
776
+ }
777
+ }
778
+ }));
779
+ const ACTIONS_FOLDER = /(^|\/)src\/actions\//;
780
+ const ACTIONS_FILE = /(^|\/)src\/actions\/[a-z][A-Za-z]*\.ts$/;
781
+ const ACTION_BUILDERS = new Set(['action', 'defineActions']);
782
+ const VALUES_ACTIONS_MAY_NOT_IMPORT = /\/src\/(models|stores|components)\//;
783
+ const actionsWhere = rule((context) => ({
784
+ CallExpression(node) {
785
+ const callee = String(child(node, 'callee')?.['name'] ?? '');
786
+ if (!ACTION_BUILDERS.has(callee))
787
+ return;
788
+ if (ACTIONS_FOLDER.test(context.filename))
789
+ return;
790
+ context.report({ loc: node.loc, message: ACTIONS_WHERE_MESSAGE });
791
+ }
792
+ }));
793
+ const pureActions = rule((context) => {
794
+ if (!ACTIONS_FILE.test(context.filename))
795
+ return {};
796
+ const report = (node, what) => {
797
+ context.report({
798
+ loc: node.loc,
799
+ message: `${PURE_ACTIONS_MESSAGE} This one ${what}.`
800
+ });
801
+ };
802
+ return {
803
+ ImportDeclaration(node) {
804
+ const source = stringValue(child(node, 'source') ?? node) ?? '';
805
+ if (VALUES_ACTIONS_MAY_NOT_IMPORT.test(source) &&
806
+ !isTypeOnlyImport(node)) {
807
+ report(node, `imports a value from ${source}`);
808
+ }
809
+ },
810
+ CallExpression(node) {
811
+ const callee = memberPath(child(node, 'callee')) ?? '';
812
+ if (IMPURE_CALLS.has(callee))
813
+ report(node, `reads ${callee}()`);
814
+ },
815
+ NewExpression(node) {
816
+ if (memberPath(child(node, 'callee')) === 'Date') {
817
+ report(node, 'reads new Date()');
818
+ }
819
+ },
820
+ AwaitExpression(node) {
821
+ report(node, 'awaits');
822
+ },
823
+ ThisExpression(node) {
824
+ report(node, 'uses this');
825
+ }
826
+ };
827
+ });
828
+ const modelByName = rule((context) => {
829
+ const models = new Set();
830
+ return {
831
+ VariableDeclarator(node) {
832
+ const id = child(node, 'id');
833
+ const init = child(node, 'init');
834
+ const isUseModel = init?.type === 'CallExpression' &&
835
+ child(init, 'callee')?.['name'] === 'useModel';
836
+ if (isUseModel && id?.type === 'Identifier')
837
+ models.add(String(id['name']));
838
+ const readsModel = isUseModel ||
839
+ (init?.type === 'Identifier' && models.has(String(init['name'])));
840
+ if (readsModel && id?.type === 'ObjectPattern') {
841
+ context.report({ loc: node.loc, message: MODEL_BY_NAME_MESSAGE });
842
+ }
843
+ }
844
+ };
845
+ });
846
+ const useModelWhere = rule((context) => ({
847
+ CallExpression(node) {
848
+ if (child(node, 'callee')?.['name'] !== 'useModel')
849
+ return;
850
+ if (MAY_USE_MODEL.test(context.filename))
851
+ return;
852
+ context.report({ loc: node.loc, message: USE_MODEL_WHERE_MESSAGE });
853
+ }
854
+ }));
855
+ const stateViaHooks = rule((context) => ({
856
+ MemberExpression(node) {
857
+ if (!VIEW_FILE.test(context.filename))
858
+ return;
859
+ const property = child(node, 'property');
860
+ if (node['computed'] !== true &&
861
+ UNSUBSCRIBED_READS.has(String(property?.['name'] ?? ''))) {
862
+ context.report({ loc: node.loc, message: STATE_VIA_HOOKS_MESSAGE });
863
+ }
864
+ }
865
+ }));
866
+ function returnsObjectOfMethods(fn) {
867
+ const body = child(fn, 'body');
868
+ const returned = [];
869
+ if (body?.type === 'ObjectExpression')
870
+ returned.push(body);
871
+ if (body?.type === 'BlockStatement') {
872
+ for (const statement of children(body, 'body')) {
873
+ const argument = child(statement, 'argument');
874
+ if (statement.type === 'ReturnStatement' && argument !== undefined) {
875
+ returned.push(argument);
876
+ }
877
+ }
878
+ }
879
+ return returned.some((value) => value.type === 'ObjectExpression' &&
880
+ children(value, 'properties').some((property) => {
881
+ const propertyValue = child(property, 'value');
882
+ return (property['method'] === true ||
883
+ propertyValue?.type === 'ArrowFunctionExpression' ||
884
+ propertyValue?.type === 'FunctionExpression');
885
+ }));
886
+ }
887
+ const CLASS_MEMBERS = new Set([
888
+ 'MethodDefinition',
889
+ 'PropertyDefinition',
890
+ 'AccessorProperty',
891
+ 'TSAbstractMethodDefinition',
892
+ 'TSAbstractPropertyDefinition'
893
+ ]);
894
+ const explicitAccess = rule((context) => ({
895
+ ClassBody(node) {
896
+ for (const member of children(node, 'body')) {
897
+ if (!CLASS_MEMBERS.has(member.type))
898
+ continue;
899
+ if (member['kind'] === 'constructor')
900
+ continue;
901
+ const isHashPrivate = child(member, 'key')?.type === 'PrivateIdentifier';
902
+ if (isHashPrivate || typeof member['accessibility'] !== 'string') {
903
+ context.report({ loc: member.loc, message: EXPLICIT_ACCESS_MESSAGE });
904
+ }
905
+ }
906
+ }
907
+ }));
908
+ const classesNotFactories = rule((context) => {
909
+ const check = (node) => {
910
+ if (SRC_FILE.test(context.filename) && returnsObjectOfMethods(node)) {
911
+ context.report({ loc: node.loc, message: CLASSES_NOT_FACTORIES_MESSAGE });
912
+ }
913
+ };
914
+ return {
915
+ FunctionDeclaration: check,
916
+ FunctionExpression: check,
917
+ ArrowFunctionExpression: check
918
+ };
919
+ });
920
+ const devOnlySignIn = rule((context) => ({
921
+ MemberExpression(node) {
922
+ const object = child(node, 'object');
923
+ if (object?.type === 'MetaProperty' &&
924
+ child(node, 'property')?.['name'] === 'env' &&
925
+ SERVER_FILE.test(context.filename)) {
926
+ context.report({ loc: node.loc, message: DEV_ONLY_SIGN_IN_MESSAGE });
927
+ }
928
+ }
929
+ }));
930
+ function attributeNamed(element, name) {
931
+ return children(element, 'attributes').find((attribute) => child(attribute, 'name')?.['name'] === name);
932
+ }
933
+ function elementName(element) {
934
+ const name = child(element, 'name');
935
+ return name?.type === 'JSXIdentifier' ? String(name['name']) : undefined;
936
+ }
937
+ const usePrimitives = rule((context) => ({
938
+ JSXOpeningElement(node) {
939
+ if (!SRC_FILE.test(context.filename))
940
+ return;
941
+ if (PRIMITIVE_ELEMENTS.has(elementName(node) ?? '')) {
942
+ context.report({ loc: node.loc, message: USE_PRIMITIVES_MESSAGE });
943
+ }
944
+ }
945
+ }));
946
+ const noWidgetRoles = rule((context) => ({
947
+ JSXAttribute(node) {
948
+ if (!SRC_FILE.test(context.filename))
949
+ return;
950
+ if (child(node, 'name')?.['name'] !== 'role')
951
+ return;
952
+ const value = child(node, 'value');
953
+ const role = value === undefined ? undefined : stringValue(value);
954
+ if (role !== undefined && WIDGET_ROLES.has(role)) {
955
+ context.report({ loc: node.loc, message: NO_WIDGET_ROLES_MESSAGE });
956
+ }
957
+ }
958
+ }));
959
+ const noClickOnStatic = rule((context) => ({
960
+ JSXOpeningElement(node) {
961
+ if (!SRC_FILE.test(context.filename))
962
+ return;
963
+ if (!STATIC_ELEMENTS.has(elementName(node) ?? ''))
964
+ return;
965
+ if (attributeNamed(node, 'role') !== undefined)
966
+ return;
967
+ for (const handler of CLICK_HANDLERS) {
968
+ const attribute = attributeNamed(node, handler);
969
+ if (attribute !== undefined) {
970
+ context.report({
971
+ loc: attribute.loc,
972
+ message: NO_CLICK_ON_STATIC_MESSAGE
973
+ });
974
+ }
975
+ }
976
+ }
977
+ }));
978
+ const primitiveNames = rule((context) => ({
979
+ Program(node) {
980
+ if (!COMPONENT_FILE.test(context.filename))
981
+ return;
982
+ for (const statement of children(node, 'body')) {
983
+ if (statement.type !== 'ExportNamedDeclaration')
984
+ continue;
985
+ const declaration = child(statement, 'declaration');
986
+ const name = String(child(declaration ?? statement, 'id')?.['name'] ?? '');
987
+ if (PRIMITIVES.has(name)) {
988
+ context.report({
989
+ loc: statement.loc,
990
+ message: PRIMITIVE_NAMES_MESSAGE.replaceAll('{name}', name)
991
+ });
992
+ }
993
+ }
994
+ }
995
+ }));
996
+ function isTokenOf(groups, node) {
997
+ if (node.type === 'MemberExpression') {
998
+ return groups.has(String(child(node, 'object')?.['name'] ?? ''));
999
+ }
1000
+ if (node.type === 'TemplateLiteral') {
1001
+ const expressions = children(node, 'expressions');
1002
+ return (expressions.length > 0 &&
1003
+ expressions.every((expression) => isTokenOf(groups, expression)));
1004
+ }
1005
+ return false;
1006
+ }
1007
+ function isNeutralValue(node) {
1008
+ if (node.type !== 'Literal')
1009
+ return false;
1010
+ const value = node['value'];
1011
+ return (value === null || value === 0 || value === 'auto' || value === 'inherit');
1012
+ }
1013
+ function checksSizes(context) {
1014
+ const [options] = context.options;
1015
+ return (typeof options === 'object' &&
1016
+ options !== null &&
1017
+ Reflect.get(options, 'sizes') === true);
1018
+ }
1019
+ const SIZES_OPTION = {
1020
+ type: 'problem',
1021
+ schema: [
1022
+ {
1023
+ type: 'object',
1024
+ properties: { sizes: { type: 'boolean' } },
1025
+ additionalProperties: false
1026
+ }
1027
+ ]
1028
+ };
1029
+ const checkSpacing = styleRule((declarations, context) => {
1030
+ if (!SRC_FILE.test(context.filename))
1031
+ return [];
1032
+ const sizes = checksSizes(context);
1033
+ return declarations.flatMap((d) => {
1034
+ if (SPACING_PROPERTY.test(d.property)) {
1035
+ if (isNeutralValue(d.value) || isTokenOf(SPACING_TOKEN_GROUPS, d.value)) {
1036
+ return [];
1037
+ }
1038
+ return [{ location: d.location, message: USE_SPACE_TOKENS_MESSAGE }];
1039
+ }
1040
+ if (sizes && SIZE_PROPERTIES.has(d.property)) {
1041
+ const raw = stringValue(d.value);
1042
+ if (isNeutralValue(d.value) ||
1043
+ isTokenOf(SIZE_TOKEN_GROUPS, d.value) ||
1044
+ (raw !== undefined && SIZE_KEYWORDS.test(raw))) {
1045
+ return [];
1046
+ }
1047
+ return [{ location: d.location, message: USE_SIZE_TOKENS_MESSAGE }];
1048
+ }
1049
+ return [];
1050
+ });
1051
+ });
1052
+ function isDomElement(element) {
1053
+ const name = element === undefined ? undefined : child(element, 'openingElement');
1054
+ const tag = name === undefined ? undefined : elementName(name);
1055
+ return tag !== undefined && /^[a-z]/.test(tag);
1056
+ }
1057
+ function isLooseText(node) {
1058
+ if (node.type === 'JSXText')
1059
+ return String(node['value']).trim() !== '';
1060
+ if (node.type !== 'JSXExpressionContainer')
1061
+ return false;
1062
+ const expression = child(node, 'expression');
1063
+ return (expression !== undefined &&
1064
+ (stringValue(expression) !== undefined ||
1065
+ expression.type === 'TemplateLiteral'));
1066
+ }
1067
+ const textViaText = rule((context) => {
1068
+ if (!SRC_FILE.test(context.filename))
1069
+ return {};
1070
+ return {
1071
+ CallExpression(node) {
1072
+ if (!isStylexCreate(node))
1073
+ return;
1074
+ for (const d of styleDeclarations(node)) {
1075
+ if (TEXT_PROPERTIES.has(d.property)) {
1076
+ context.report({ loc: d.location, message: TEXT_VIA_TEXT_MESSAGE });
1077
+ }
1078
+ }
1079
+ },
1080
+ JSXOpeningElement(node) {
1081
+ if (TEXT_ELEMENTS.has(elementName(node) ?? '')) {
1082
+ context.report({ loc: node.loc, message: TEXT_ELEMENTS_MESSAGE });
1083
+ }
1084
+ },
1085
+ JSXElement(node) {
1086
+ if (!isDomElement(node))
1087
+ return;
1088
+ for (const inner of children(node, 'children')) {
1089
+ if (isLooseText(inner)) {
1090
+ context.report({ loc: inner.loc, message: LOOSE_TEXT_MESSAGE });
1091
+ }
1092
+ }
1093
+ }
1094
+ };
1095
+ });
1096
+ const composeInScreens = rule((context) => {
1097
+ if (!SCREEN_FILE.test(context.filename))
1098
+ return {};
1099
+ return {
1100
+ ImportDeclaration(node) {
1101
+ const source = stringValue(child(node, 'source') ?? node) ?? '';
1102
+ if (STYLE_SOURCES.has(source)) {
1103
+ context.report({ loc: node.loc, message: COMPOSE_IN_SCREENS_MESSAGE });
1104
+ }
1105
+ },
1106
+ JSXOpeningElement(node) {
1107
+ if (/^[a-z]/.test(elementName(node) ?? '')) {
1108
+ context.report({ loc: node.loc, message: COMPOSE_IN_SCREENS_MESSAGE });
1109
+ }
1110
+ }
1111
+ };
1112
+ });
1113
+ const noHoverInApp = styleRule((declarations, context) => {
1114
+ if (!SRC_FILE.test(context.filename))
1115
+ return [];
1116
+ return declarations
1117
+ .filter((d) => d.conditions.some((condition) => POINTER_CONDITIONS.has(condition)))
1118
+ .map((d) => ({ location: d.location, message: NO_HOVER_IN_APP_MESSAGE }));
1119
+ });
1120
+ export default {
1121
+ meta: { name: 'pattern' },
1122
+ rules: {
1123
+ 'no-comments': noComments,
1124
+ 'no-pointer-cursor': noPointerCursor,
1125
+ 'use-tokens': useTokens,
1126
+ 'no-interactive-transition': noInteractiveTransition,
1127
+ 'hover-uses-highlight': hoverUsesHighlight,
1128
+ 'scroll-view-only': scrollViewOnly,
1129
+ 'selectable-only': selectableOnly,
1130
+ 'dark-mode-in-tokens': darkModeInTokens,
1131
+ 'use-breakpoints': useBreakpoints,
1132
+ 'no-classname-strings': noClassNameStrings,
1133
+ 'typed-style-prop': typedStyleProp,
1134
+ 'component-file-name': componentFileName,
1135
+ 'component-first': componentFirst,
1136
+ 'routes-under-app-layout': routesUnderAppLayout,
1137
+ 'no-route-data-apis': noRouteDataApis,
1138
+ 'use-link': useLink,
1139
+ 'stores-in-app': storesInApp,
1140
+ 'state-via-hooks': stateViaHooks,
1141
+ 'model-members': modelMembers,
1142
+ 'pure-getters': pureGetters,
1143
+ 'actions-where': actionsWhere,
1144
+ 'pure-actions': pureActions,
1145
+ 'use-model-where': useModelWhere,
1146
+ 'model-by-name': modelByName,
1147
+ 'classes-not-factories': classesNotFactories,
1148
+ 'explicit-access': explicitAccess,
1149
+ 'dev-only-sign-in': devOnlySignIn,
1150
+ 'use-primitives': usePrimitives,
1151
+ 'no-widget-roles': noWidgetRoles,
1152
+ 'no-click-on-static': noClickOnStatic,
1153
+ 'primitive-names': primitiveNames,
1154
+ 'use-space-tokens': { ...checkSpacing, meta: SIZES_OPTION },
1155
+ 'text-via-text': textViaText,
1156
+ 'compose-in-screens': composeInScreens,
1157
+ 'no-hover-in-app': noHoverInApp
1158
+ }
1159
+ };