@lariocpt/flawless-react 0.0.0-stage → 0.3.0

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 (143) hide show
  1. package/.agents/skills/flawless-a11y/SKILL.md +113 -0
  2. package/.agents/skills/flawless-api/SKILL.md +159 -0
  3. package/.agents/skills/flawless-api-fixtures/SKILL.md +144 -0
  4. package/.agents/skills/flawless-e2e/SKILL.md +155 -0
  5. package/.agents/skills/flawless-expo/SKILL.md +283 -0
  6. package/.agents/skills/flawless-findings/SKILL.md +116 -0
  7. package/.agents/skills/flawless-load/SKILL.md +112 -0
  8. package/.agents/skills/flawless-nextjs/SKILL.md +198 -0
  9. package/.agents/skills/flawless-perf-harness/SKILL.md +158 -0
  10. package/.agents/skills/flawless-perf-static/SKILL.md +131 -0
  11. package/.agents/skills/flawless-react/SKILL.md +403 -0
  12. package/.agents/skills/flawless-react-rules/SKILL.md +96 -0
  13. package/.agents/skills/flawless-regen/SKILL.md +135 -0
  14. package/.agents/skills/flawless-unit-browser/SKILL.md +178 -0
  15. package/LICENSE +21 -0
  16. package/README.md +608 -2
  17. package/bin/flawless-react +4 -0
  18. package/package.json +67 -4
  19. package/scripts/postinstall.d.mts +46 -0
  20. package/scripts/postinstall.mjs +127 -0
  21. package/src/analyse/authShims.ts +220 -0
  22. package/src/analyse/bootability.ts +257 -0
  23. package/src/analyse/childBridge.ts +302 -0
  24. package/src/analyse/childrenIntoSlot.ts +66 -0
  25. package/src/analyse/compoundFamily.ts +100 -0
  26. package/src/analyse/dataExports.ts +200 -0
  27. package/src/analyse/extractChildAttrs.ts +112 -0
  28. package/src/analyse/extractDataDeps.ts +857 -0
  29. package/src/analyse/extractDisclosures.ts +186 -0
  30. package/src/analyse/extractFetchCalls.ts +81 -0
  31. package/src/analyse/extractFlows.ts +239 -0
  32. package/src/analyse/extractGuards.ts +270 -0
  33. package/src/analyse/extractHooks.ts +237 -0
  34. package/src/analyse/extractInteractions.ts +1421 -0
  35. package/src/analyse/extractLogging.ts +45 -0
  36. package/src/analyse/extractNativeA11y.ts +138 -0
  37. package/src/analyse/extractPredicates.ts +784 -0
  38. package/src/analyse/extractPropUsage.ts +162 -0
  39. package/src/analyse/extractProps.ts +1212 -0
  40. package/src/analyse/extractRenderMode.ts +165 -0
  41. package/src/analyse/extractRootTag.ts +117 -0
  42. package/src/analyse/extractState.ts +361 -0
  43. package/src/analyse/extractStaticText.ts +212 -0
  44. package/src/analyse/extractVariants.ts +186 -0
  45. package/src/analyse/harvestApiEndpoints.ts +284 -0
  46. package/src/analyse/hostProps.ts +185 -0
  47. package/src/analyse/hostTag.ts +72 -0
  48. package/src/analyse/htmlContentModel.ts +94 -0
  49. package/src/analyse/index.ts +744 -0
  50. package/src/analyse/mountPathCalls.ts +442 -0
  51. package/src/analyse/nativeContentModel.ts +129 -0
  52. package/src/analyse/nativeDialect.ts +83 -0
  53. package/src/analyse/nativeProps.ts +339 -0
  54. package/src/analyse/ownReturns.ts +31 -0
  55. package/src/analyse/parseFile.ts +919 -0
  56. package/src/analyse/pos.ts +8 -0
  57. package/src/analyse/providerWraps.ts +1977 -0
  58. package/src/analyse/pureModule.ts +88 -0
  59. package/src/analyse/reexportTable.ts +148 -0
  60. package/src/analyse/refs.ts +195 -0
  61. package/src/analyse/renderPath.ts +441 -0
  62. package/src/analyse/resolveDepReturns.ts +370 -0
  63. package/src/analyse/resolveIndexKeys.ts +98 -0
  64. package/src/analyse/resolveModule.ts +431 -0
  65. package/src/analyse/resolveTagRootTags.ts +103 -0
  66. package/src/analyse/serverActionReturns.ts +213 -0
  67. package/src/analyse/sourceCache.ts +263 -0
  68. package/src/analyse/spreadSiblings.ts +98 -0
  69. package/src/analyse/walkChildren.ts +98 -0
  70. package/src/analyse/zodSchema.ts +424 -0
  71. package/src/bin.ts +23 -0
  72. package/src/changed.ts +221 -0
  73. package/src/cli.ts +560 -0
  74. package/src/cliFlags.ts +281 -0
  75. package/src/core.ts +1229 -0
  76. package/src/docLine.ts +122 -0
  77. package/src/emitPaths.ts +51 -0
  78. package/src/expo/detect.ts +184 -0
  79. package/src/expo/refusal.ts +62 -0
  80. package/src/expo/routes.ts +145 -0
  81. package/src/index.ts +95 -0
  82. package/src/mcp/analysisTools.ts +720 -0
  83. package/src/mcp/infoTools.ts +135 -0
  84. package/src/mcp/protocol.ts +130 -0
  85. package/src/mcp/run.ts +269 -0
  86. package/src/mcp/runTools.ts +362 -0
  87. package/src/mcp/server.ts +279 -0
  88. package/src/mcp/spec.ts +86 -0
  89. package/src/mcp/support.ts +268 -0
  90. package/src/mcp/tools.ts +75 -0
  91. package/src/nextjs/apiRoutes.ts +116 -0
  92. package/src/nextjs/detect.ts +397 -0
  93. package/src/nextjs/emit.ts +106 -0
  94. package/src/nextjs/liveSpec.ts +79 -0
  95. package/src/nextjs/mswHandlers.ts +55 -0
  96. package/src/nextjs/providerWrapper.ts +603 -0
  97. package/src/nextjs/routes.ts +86 -0
  98. package/src/nextjs/swagger.ts +32 -0
  99. package/src/packageJson.ts +414 -0
  100. package/src/platform.ts +18 -0
  101. package/src/prompt.ts +132 -0
  102. package/src/reactCompiler.ts +165 -0
  103. package/src/reporters/findings.ts +156 -0
  104. package/src/reporters/report.ts +463 -0
  105. package/src/runner/runTests.ts +402 -0
  106. package/src/scaffold/adoptStyle.ts +181 -0
  107. package/src/scaffold/index.ts +253 -0
  108. package/src/scaffold/openapi.ts +183 -0
  109. package/src/scaffold/templates.ts +2379 -0
  110. package/src/scaffold-tests/index.ts +463 -0
  111. package/src/scaffold-tests/mockValues.ts +208 -0
  112. package/src/scaffold-tests/mocks.ts +794 -0
  113. package/src/scaffold-tests/nativeIndex.ts +52 -0
  114. package/src/scaffold-tests/nativeTemplates.ts +1528 -0
  115. package/src/scaffold-tests/postDispatch.ts +152 -0
  116. package/src/scaffold-tests/registry.ts +443 -0
  117. package/src/scaffold-tests/selectors.ts +493 -0
  118. package/src/scaffold-tests/selectorsNative.ts +273 -0
  119. package/src/scaffold-tests/stamp.ts +79 -0
  120. package/src/scaffold-tests/templates.ts +3578 -0
  121. package/src/skills/cli.ts +248 -0
  122. package/src/skills/compat.ts +166 -0
  123. package/src/skills/data.ts +192 -0
  124. package/src/skills/md.d.ts +7 -0
  125. package/src/skills/menu.ts +55 -0
  126. package/src/skills/spec.ts +227 -0
  127. package/src/suiteScripts.ts +110 -0
  128. package/src/tasks/a11yNative.ts +281 -0
  129. package/src/tasks/a11yScan.ts +67 -0
  130. package/src/tasks/biomeRunner.ts +301 -0
  131. package/src/tasks/biomeSweep.ts +166 -0
  132. package/src/tasks/e2eExpo.ts +436 -0
  133. package/src/tasks/e2eRoutes.ts +756 -0
  134. package/src/tasks/eslintReactRules.ts +67 -0
  135. package/src/tasks/index.ts +24 -0
  136. package/src/tasks/loadExpo.ts +273 -0
  137. package/src/tasks/loadPipes.ts +750 -0
  138. package/src/tasks/perfHarness.ts +145 -0
  139. package/src/tasks/staticPerfAnalysis.ts +510 -0
  140. package/src/tasks/types.ts +1 -0
  141. package/src/tasks/unitAndBrowser.ts +462 -0
  142. package/src/types.ts +1419 -0
  143. package/src/version.ts +12 -0
@@ -0,0 +1,339 @@
1
+ import type { PropsShape, PropValue } from "../types.ts";
2
+
3
+ /**
4
+ * A curated, closed table of React Native host elements — the peer of
5
+ * hostProps.ts, and deliberately NOT a model of React Native.
6
+ *
7
+ * Keyed by SPECIFIER then by module-side export name, because that is the only
8
+ * evidence there is: `<View>` is a host element because it came from
9
+ * "react-native", and `<View>` imported from "./my-ui" is a component that
10
+ * happens to share the name. Keying on the bare tag would make the second one a
11
+ * host element too.
12
+ *
13
+ * `BASE` is empty, and that is the honest answer rather than an oversight.
14
+ * React Native has no universal prop whose synthesised default is both valid and
15
+ * useful. `className` belongs to NativeWind, not to RN — synthesising it would
16
+ * encode a build-tool assumption into the analysis. `style` mocks to `{}`, which
17
+ * is inert. What this table supplies that hostProps.ts also supplies is
18
+ * `acceptsChildren` and `handlers`; the prop SHAPE is genuinely thinner here,
19
+ * and pretending otherwise is how you get an assertion that cannot fail.
20
+ *
21
+ * Three invariants are baked in, each because breaking it produces a *false test
22
+ * result* rather than missing coverage. tests/nativeProps.test.ts pins all three.
23
+ *
24
+ * 1. Never synthesise `value` (except Switch, which has no `defaultValue`).
25
+ * `<TextInput value={x} />` with no `onChangeText` is React Native's
26
+ * controlled-input trap: the input is frozen and `fireEvent.changeText` is a
27
+ * silent no-op that still passes. Strictly worse than the DOM version, where
28
+ * React at least logs a controlled-input warning. Always `defaultValue`.
29
+ * 2. Never synthesise `placeholder` / `testID` / `accessibilityLabel` /
30
+ * `accessibilityHint` / `nativeID`. Those five ARE the entire locator surface
31
+ * of @testing-library/react-native. An empty default makes
32
+ * `getByPlaceholderText("")` throw; a non-empty invented one makes the spec
33
+ * assert a handle the app does not have.
34
+ * 3. Never synthesise `accessibilityRole` / `role`. RNTL resolves role from that
35
+ * prop ALONE, so an invented role makes `getByRole` pass on a fiction. This is
36
+ * the structural analogue of the DOM table's "`type` is always a real literal":
37
+ * the one prop a query resolves against must never be a placeholder.
38
+ */
39
+
40
+ export interface NativeElementProps {
41
+ shape: PropsShape;
42
+ variantOptions?: Record<string, PropValue[]>;
43
+ acceptsChildren: boolean;
44
+ /** Handler props the scaffolder stubs. */
45
+ handlers: string[];
46
+ /** The only element a raw string may legally be rendered inside. */
47
+ rendersText?: true;
48
+ }
49
+
50
+ const NONE: PropsShape = {};
51
+
52
+ function el(
53
+ shape: PropsShape,
54
+ acceptsChildren: boolean,
55
+ handlers: string[],
56
+ extra: Partial<NativeElementProps> = {},
57
+ ): NativeElementProps {
58
+ return { shape, acceptsChildren, handlers, ...extra };
59
+ }
60
+
61
+ const REACT_NATIVE: Record<string, NativeElementProps> = {
62
+ View: el({ ...NONE }, true, []),
63
+ // `onPress` on Text is real and works — RN routes it through the text's own
64
+ // press responder. It is not a Pressable, but it IS drivable.
65
+ Text: el({ ...NONE }, true, ["onPress"], { rendersText: true }),
66
+ Pressable: el({ disabled: false }, true, ["onPress", "onLongPress"]),
67
+ TouchableOpacity: el({ disabled: false }, true, ["onPress", "onLongPress"]),
68
+ TouchableHighlight: el({ disabled: false }, true, ["onPress", "onLongPress"]),
69
+ TouchableWithoutFeedback: el({ ...NONE }, true, ["onPress", "onLongPress"]),
70
+ // defaultValue, never value — invariant 1.
71
+ TextInput: el({ defaultValue: "", editable: true }, false, [
72
+ "onChangeText",
73
+ "onFocus",
74
+ "onBlur",
75
+ "onSubmitEditing",
76
+ ]),
77
+ ScrollView: el({ ...NONE }, true, []),
78
+ FlatList: el({ ...NONE }, false, ["onRefresh"]),
79
+ SectionList: el({ ...NONE }, false, ["onRefresh"]),
80
+ KeyboardAvoidingView: el({ ...NONE }, true, []),
81
+ ActivityIndicator: el({ animating: true, size: "small" }, false, [], {
82
+ variantOptions: { size: ["small", "large"] },
83
+ }),
84
+ RefreshControl: el({ refreshing: false }, false, ["onRefresh"]),
85
+ // The one place `value` appears, and it is forced: RN's Switch has no
86
+ // defaultValue, so value + the mandatory onValueChange is the only
87
+ // representable uncontrolled state. tests/nativeProps.test.ts allows it BY NAME
88
+ // with this reason, rather than weakening invariant 1 for everything.
89
+ Switch: el({ value: false }, false, ["onValueChange"]),
90
+ Image: el({ ...NONE }, false, []),
91
+ ImageBackground: el({ ...NONE }, true, []),
92
+ Modal: el({ visible: true }, true, ["onRequestClose"]),
93
+ // RN's own Button is the ONLY element in this table that sets an
94
+ // accessibilityRole unconditionally in its own source. See NATIVE_TAG_TO_ROLE.
95
+ Button: el({ title: "Button", disabled: false }, false, ["onPress"]),
96
+ SafeAreaView: el({ ...NONE }, true, []),
97
+ StatusBar: el({ ...NONE }, false, []),
98
+ };
99
+
100
+ const SAFE_AREA_CONTEXT: Record<string, NativeElementProps> = {
101
+ SafeAreaView: el({ ...NONE }, true, []),
102
+ SafeAreaProvider: el({ ...NONE }, true, []),
103
+ };
104
+
105
+ /**
106
+ * @expo/vector-icons exports one component per glyph family. They render an
107
+ * icon and no text, which is what makes an icon-only Pressable unaddressable —
108
+ * so they have to be in the table for that to be PROVABLE rather than guessed
109
+ * from the tag name. Exported for the native a11y task, whose icon rule needs
110
+ * "is this a vector icon" answered from the same table the label harvest uses.
111
+ */
112
+ export const VECTOR_ICON_FAMILIES = [
113
+ "Ionicons",
114
+ "MaterialIcons",
115
+ "MaterialCommunityIcons",
116
+ "FontAwesome",
117
+ "FontAwesome5",
118
+ "FontAwesome6",
119
+ "Feather",
120
+ "AntDesign",
121
+ "Entypo",
122
+ "EvilIcons",
123
+ "Foundation",
124
+ "Octicons",
125
+ "SimpleLineIcons",
126
+ "Zocial",
127
+ ] as const;
128
+
129
+ const VECTOR_ICONS: Record<string, NativeElementProps> = Object.fromEntries(
130
+ VECTOR_ICON_FAMILIES.map((name) => [name, el({ name: "help-circle", size: 24 }, false, [])]),
131
+ );
132
+
133
+ const TABLE: Readonly<Record<string, Readonly<Record<string, NativeElementProps>>>> = {
134
+ "react-native": REACT_NATIVE,
135
+ "react-native-safe-area-context": SAFE_AREA_CONTEXT,
136
+ "@expo/vector-icons": VECTOR_ICONS,
137
+ };
138
+
139
+ export const NATIVE_ELEMENTS = TABLE;
140
+
141
+ /**
142
+ * Tag -> implicit accessibilityRole. **One row, and pinned at one row.**
143
+ *
144
+ * @testing-library/react-native's `getRole()` returns the explicit
145
+ * `role`/`accessibilityRole` prop, else "text" for Text elements, else "none".
146
+ * There is no Pressable -> button mapping anywhere in the library. So a fuller
147
+ * table here would not improve queries — it would emit `getByRole("button")`
148
+ * against elements that carry no role, which matches nothing. On the dogfood app
149
+ * that is 82 confidently wrong queries.
150
+ *
151
+ * A row may be added only when the element's own React Native source sets the
152
+ * role UNCONDITIONALLY. RN's `<Button>` does; Pressable, Touchable* and TextInput
153
+ * do not. tests/nativeProps.test.ts asserts the size of this table, because
154
+ * "it is nearly empty" is a claim that decays silently.
155
+ */
156
+ export const NATIVE_TAG_TO_ROLE: Readonly<Record<string, string>> = { Button: "button" };
157
+
158
+ /** Elements that render no text, so an element containing only these has no accessible name. */
159
+ export const TEXTLESS_ELEMENTS: ReadonlySet<string> = new Set([
160
+ ...VECTOR_ICON_FAMILIES,
161
+ "ActivityIndicator",
162
+ "Image",
163
+ "Switch",
164
+ "RefreshControl",
165
+ "StatusBar",
166
+ ]);
167
+
168
+ /**
169
+ * The values React Native accepts for `accessibilityRole`, copied from the
170
+ * `AccessibilityRole` union in react-native 0.81.5's own
171
+ * Libraries/Components/View/ViewAccessibility.js.
172
+ *
173
+ * Copied rather than derived at runtime, for the same reason the element table
174
+ * above is closed: this repo does not depend on react-native, the analysed app
175
+ * may not have it installed when the sweep runs, and a finding set that varies
176
+ * with the target's install state is a nondeterministic report. A closed copy
177
+ * can only go stale, and a membership + size pin in
178
+ * tests/nativeElementModel.test.ts makes staleness a red test at the next RN
179
+ * bump rather than a silent drift.
180
+ *
181
+ * Why the rule exists at all: RN widens the union with `| string` (both the
182
+ * Flow source and the shipped .d.ts), so a typo'd role type-checks clean and
183
+ * then announces NOTHING on either platform. tsc will never catch it; this is
184
+ * the one place that can.
185
+ */
186
+ export const VALID_ACCESSIBILITY_ROLES: ReadonlySet<string> = new Set([
187
+ "none",
188
+ "button",
189
+ "dropdownlist",
190
+ "togglebutton",
191
+ "link",
192
+ "search",
193
+ "image",
194
+ "keyboardkey",
195
+ "text",
196
+ "adjustable",
197
+ "imagebutton",
198
+ "header",
199
+ "summary",
200
+ "alert",
201
+ "checkbox",
202
+ "combobox",
203
+ "menu",
204
+ "menubar",
205
+ "menuitem",
206
+ "progressbar",
207
+ "radio",
208
+ "radiogroup",
209
+ "scrollbar",
210
+ "spinbutton",
211
+ "switch",
212
+ "tab",
213
+ "tabbar",
214
+ "tablist",
215
+ "timer",
216
+ "list",
217
+ "toolbar",
218
+ "grid",
219
+ "pager",
220
+ "scrollview",
221
+ "horizontalscrollview",
222
+ "viewgroup",
223
+ "webview",
224
+ "drawerlayout",
225
+ "slidingdrawer",
226
+ "iconmenu",
227
+ ]);
228
+
229
+ /**
230
+ * The values React Native accepts for the web-style `role` prop (RN 0.71+) —
231
+ * the `Role` union from the same ViewAccessibility.js, which is a DIFFERENT
232
+ * domain than `accessibilityRole` ("heading" is a Role, "header" is an
233
+ * AccessibilityRole). The two sets are deliberately separate so the invalid-role
234
+ * rule validates each attribute against its own union rather than the blur of
235
+ * both.
236
+ */
237
+ export const VALID_ROLES: ReadonlySet<string> = new Set([
238
+ "alert",
239
+ "alertdialog",
240
+ "application",
241
+ "article",
242
+ "banner",
243
+ "button",
244
+ "cell",
245
+ "checkbox",
246
+ "columnheader",
247
+ "combobox",
248
+ "complementary",
249
+ "contentinfo",
250
+ "definition",
251
+ "dialog",
252
+ "directory",
253
+ "document",
254
+ "feed",
255
+ "figure",
256
+ "form",
257
+ "grid",
258
+ "group",
259
+ "heading",
260
+ "img",
261
+ "link",
262
+ "list",
263
+ "listitem",
264
+ "log",
265
+ "main",
266
+ "marquee",
267
+ "math",
268
+ "menu",
269
+ "menubar",
270
+ "menuitem",
271
+ "meter",
272
+ "navigation",
273
+ "none",
274
+ "note",
275
+ "option",
276
+ "presentation",
277
+ "progressbar",
278
+ "radio",
279
+ "radiogroup",
280
+ "region",
281
+ "row",
282
+ "rowgroup",
283
+ "rowheader",
284
+ "scrollbar",
285
+ "searchbox",
286
+ "separator",
287
+ "slider",
288
+ "spinbutton",
289
+ "status",
290
+ "summary",
291
+ "switch",
292
+ "tab",
293
+ "table",
294
+ "tablist",
295
+ "tabpanel",
296
+ "term",
297
+ "timer",
298
+ "toolbar",
299
+ "tooltip",
300
+ "tree",
301
+ "treegrid",
302
+ "treeitem",
303
+ ]);
304
+
305
+ /**
306
+ * The facts for a tag, or undefined when the specifier/name pair is not in the
307
+ * table — which means "this is a component", exactly the state every uppercase
308
+ * tag is in today. Never a guess from the name.
309
+ */
310
+ export function nativeElementFor(
311
+ specifier: string,
312
+ exportedName: string,
313
+ ): NativeElementProps | undefined {
314
+ const known = TABLE[specifier]?.[exportedName];
315
+ if (!known) return undefined;
316
+ return { ...known, shape: { ...known.shape } };
317
+ }
318
+
319
+ /** Every element name in the table, for the tests and the ambiguity census. */
320
+ export function nativeElementNames(): string[] {
321
+ return [...new Set(Object.values(TABLE).flatMap((m) => Object.keys(m)))].sort();
322
+ }
323
+
324
+ /**
325
+ * Whether a native element accepts children, by resolved element NAME.
326
+ *
327
+ * For consumers that hold a `ResolvedHostTag` (name + dialect) but not the
328
+ * specifier it came from. Safe because the table's names are unambiguous across
329
+ * specifiers: `SafeAreaView` appears under both "react-native" and
330
+ * "react-native-safe-area-context" with the same answer. A test pins that.
331
+ */
332
+ export function nativeAcceptsChildrenByName(name: string): boolean {
333
+ for (const byName of Object.values(TABLE)) {
334
+ const found = byName[name];
335
+ if (found) return found.acceptsChildren;
336
+ }
337
+ // Unknown native-dialect tag: a component, and components take children.
338
+ return true;
339
+ }
@@ -0,0 +1,31 @@
1
+ import ts from "typescript";
2
+
3
+ /**
4
+ * The block's own return statements — a nested function's returns are its own.
5
+ *
6
+ * Shared by the render-mode detector (parseFile.ts) and the server-action
7
+ * return extractor (serverActionReturns.ts): both ask "what does THIS function
8
+ * yield", and both would be lied to by a closure's returns. Accessors and
9
+ * constructors are function boundaries too — a getter's return is not the
10
+ * enclosing function's.
11
+ */
12
+ export function ownReturns(body: ts.Block): ts.ReturnStatement[] {
13
+ const out: ts.ReturnStatement[] = [];
14
+ const visit = (n: ts.Node): void => {
15
+ if (
16
+ ts.isArrowFunction(n) ||
17
+ ts.isFunctionExpression(n) ||
18
+ ts.isFunctionDeclaration(n) ||
19
+ ts.isMethodDeclaration(n) ||
20
+ ts.isGetAccessorDeclaration(n) ||
21
+ ts.isSetAccessorDeclaration(n) ||
22
+ ts.isConstructorDeclaration(n)
23
+ ) {
24
+ return;
25
+ }
26
+ if (ts.isReturnStatement(n)) out.push(n);
27
+ ts.forEachChild(n, visit);
28
+ };
29
+ ts.forEachChild(body, visit);
30
+ return out;
31
+ }