@agentvibes/guardrails 0.1.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.
- package/README.md +239 -0
- package/candidates/README.md +30 -0
- package/candidates/r10-as-cast.yml +24 -0
- package/candidates/r1d-scattered-numeric-const-in-component.yml +13 -0
- package/candidates/r2-helper-fn-in-tsx.yml +22 -0
- package/candidates/r3-hooks-outside-layout.yml +21 -0
- package/candidates/r6-jsx-comment-smell.yml +14 -0
- package/candidates/r7-raw-hsl-outside-tokens.yml +17 -0
- package/candidates/r8-inline-style-prop.yml +13 -0
- package/candidates/r9-optional-store-arg.yml +24 -0
- package/configs/biome.json +96 -0
- package/configs/tsconfig.base.json +21 -0
- package/dist/astGrep.d.ts +16 -0
- package/dist/astGrep.js +48 -0
- package/dist/astGrep.js.map +1 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +138 -0
- package/dist/cli.js.map +1 -0
- package/dist/deploy.d.ts +1 -0
- package/dist/deploy.js +34 -0
- package/dist/deploy.js.map +1 -0
- package/dist/doctor.d.ts +6 -0
- package/dist/doctor.js +102 -0
- package/dist/doctor.js.map +1 -0
- package/dist/fileWalk.d.ts +2 -0
- package/dist/fileWalk.js +45 -0
- package/dist/fileWalk.js.map +1 -0
- package/dist/findings.d.ts +12 -0
- package/dist/findings.js +8 -0
- package/dist/findings.js.map +1 -0
- package/dist/gateRed.d.ts +1 -0
- package/dist/gateRed.js +93 -0
- package/dist/gateRed.js.map +1 -0
- package/dist/gitDiff.d.ts +18 -0
- package/dist/gitDiff.js +90 -0
- package/dist/gitDiff.js.map +1 -0
- package/dist/hookPostedit.d.ts +1 -0
- package/dist/hookPostedit.js +117 -0
- package/dist/hookPostedit.js.map +1 -0
- package/dist/hookStop.d.ts +1 -0
- package/dist/hookStop.js +56 -0
- package/dist/hookStop.js.map +1 -0
- package/dist/hookStopTest.d.ts +1 -0
- package/dist/hookStopTest.js +71 -0
- package/dist/hookStopTest.js.map +1 -0
- package/dist/init.d.ts +24 -0
- package/dist/init.js +172 -0
- package/dist/init.js.map +1 -0
- package/dist/initTest.d.ts +1 -0
- package/dist/initTest.js +88 -0
- package/dist/initTest.js.map +1 -0
- package/dist/iterate.d.ts +9 -0
- package/dist/iterate.js +79 -0
- package/dist/iterate.js.map +1 -0
- package/dist/iterateTest.d.ts +1 -0
- package/dist/iterateTest.js +91 -0
- package/dist/iterateTest.js.map +1 -0
- package/dist/leakPatterns.d.ts +5 -0
- package/dist/leakPatterns.js +22 -0
- package/dist/leakPatterns.js.map +1 -0
- package/dist/leaks.d.ts +8 -0
- package/dist/leaks.js +211 -0
- package/dist/leaks.js.map +1 -0
- package/dist/leaksRed.d.ts +1 -0
- package/dist/leaksRed.js +62 -0
- package/dist/leaksRed.js.map +1 -0
- package/dist/metrics.d.ts +10 -0
- package/dist/metrics.js +113 -0
- package/dist/metrics.js.map +1 -0
- package/dist/metricsAggregate.d.ts +5 -0
- package/dist/metricsAggregate.js +78 -0
- package/dist/metricsAggregate.js.map +1 -0
- package/dist/metricsBaseline.d.ts +21 -0
- package/dist/metricsBaseline.js +88 -0
- package/dist/metricsBaseline.js.map +1 -0
- package/dist/metricsCollect.d.ts +8 -0
- package/dist/metricsCollect.js +295 -0
- package/dist/metricsCollect.js.map +1 -0
- package/dist/metricsCycle.d.ts +1 -0
- package/dist/metricsCycle.js +79 -0
- package/dist/metricsCycle.js.map +1 -0
- package/dist/metricsTypes.d.ts +102 -0
- package/dist/metricsTypes.js +2 -0
- package/dist/metricsTypes.js.map +1 -0
- package/dist/packagePaths.d.ts +7 -0
- package/dist/packagePaths.js +11 -0
- package/dist/packagePaths.js.map +1 -0
- package/dist/pluginApi.d.ts +24 -0
- package/dist/pluginApi.js +12 -0
- package/dist/pluginApi.js.map +1 -0
- package/dist/pluginResolve.d.ts +6 -0
- package/dist/pluginResolve.js +48 -0
- package/dist/pluginResolve.js.map +1 -0
- package/dist/screenScope.d.ts +21 -0
- package/dist/screenScope.js +81 -0
- package/dist/screenScope.js.map +1 -0
- package/dist/screenScopeTest.d.ts +1 -0
- package/dist/screenScopeTest.js +116 -0
- package/dist/screenScopeTest.js.map +1 -0
- package/dist/severity.d.ts +9 -0
- package/dist/severity.js +62 -0
- package/dist/severity.js.map +1 -0
- package/dist/severityTest.d.ts +1 -0
- package/dist/severityTest.js +63 -0
- package/dist/severityTest.js.map +1 -0
- package/dist/sourceLines.d.ts +9 -0
- package/dist/sourceLines.js +52 -0
- package/dist/sourceLines.js.map +1 -0
- package/dist/structure.d.ts +2 -0
- package/dist/structure.js +98 -0
- package/dist/structure.js.map +1 -0
- package/dist/testRules.d.ts +1 -0
- package/dist/testRules.js +285 -0
- package/dist/testRules.js.map +1 -0
- package/dist/textGrep.d.ts +2 -0
- package/dist/textGrep.js +52 -0
- package/dist/textGrep.js.map +1 -0
- package/dist/tomlTable.d.ts +2 -0
- package/dist/tomlTable.js +26 -0
- package/dist/tomlTable.js.map +1 -0
- package/dist/toolResolve.d.ts +18 -0
- package/dist/toolResolve.js +50 -0
- package/dist/toolResolve.js.map +1 -0
- package/dist/verify.d.ts +3 -0
- package/dist/verify.js +49 -0
- package/dist/verify.js.map +1 -0
- package/dist/verifyDiff.d.ts +18 -0
- package/dist/verifyDiff.js +126 -0
- package/dist/verifyDiff.js.map +1 -0
- package/dist/workspaces.d.ts +6 -0
- package/dist/workspaces.js +84 -0
- package/dist/workspaces.js.map +1 -0
- package/mise.toml +9 -0
- package/package.json +62 -0
- package/rules/__fixtures__/as-any-escape/bad.ts +8 -0
- package/rules/__fixtures__/as-any-escape/good.ts +14 -0
- package/rules/__fixtures__/catch-empty/bad.ts +7 -0
- package/rules/__fixtures__/catch-empty/good.ts +14 -0
- package/rules/__fixtures__/direct-store-import/bad.tsx +23 -0
- package/rules/__fixtures__/direct-store-import/good.tsx +34 -0
- package/rules/__fixtures__/direct-store-import/showcase/ignored.tsx +9 -0
- package/rules/__fixtures__/discriminator-ternary/bad.tsx +10 -0
- package/rules/__fixtures__/discriminator-ternary/good.tsx +23 -0
- package/rules/__fixtures__/hardcoded-url-in-component/components/bad.tsx +8 -0
- package/rules/__fixtures__/hardcoded-url-in-component/components/good.tsx +14 -0
- package/rules/__fixtures__/inline-map-row/bad.tsx +72 -0
- package/rules/__fixtures__/inline-map-row/good.tsx +51 -0
- package/rules/__fixtures__/instanceof-map-set/bad.ts +7 -0
- package/rules/__fixtures__/instanceof-map-set/good.ts +8 -0
- package/rules/__fixtures__/literal-union-in-component/components/bad.ts +8 -0
- package/rules/__fixtures__/literal-union-in-component/components/good.ts +23 -0
- package/rules/__fixtures__/missing-observer/bad.tsx +66 -0
- package/rules/__fixtures__/missing-observer/good.tsx +69 -0
- package/rules/__fixtures__/no-local-kit-clone/bad.ts +30 -0
- package/rules/__fixtures__/no-local-kit-clone/bad.tsx +30 -0
- package/rules/__fixtures__/no-local-kit-clone/good.ts +31 -0
- package/rules/__fixtures__/no-local-kit-clone/good.tsx +31 -0
- package/rules/__fixtures__/non-exhaustive-match/bad.ts +8 -0
- package/rules/__fixtures__/non-exhaustive-match/good.ts +14 -0
- package/rules/__fixtures__/page-store-bare-usememo/bad.ts +15 -0
- package/rules/__fixtures__/page-store-bare-usememo/bad.tsx +13 -0
- package/rules/__fixtures__/page-store-bare-usememo/good.ts +17 -0
- package/rules/__fixtures__/page-store-bare-usememo/good.tsx +14 -0
- package/rules/__fixtures__/screen-file-styling/bad.screen.tsx +10 -0
- package/rules/__fixtures__/screen-file-styling/good.screen.tsx +8 -0
- package/rules/__fixtures__/state-loading-boolean-shape/bad.ts +36 -0
- package/rules/__fixtures__/state-loading-boolean-shape/bad.tsx +36 -0
- package/rules/__fixtures__/state-loading-boolean-shape/good.ts +44 -0
- package/rules/__fixtures__/state-loading-boolean-shape/good.tsx +44 -0
- package/rules/__fixtures__/store-async-method/bad.ts +37 -0
- package/rules/__fixtures__/store-async-method/bad.tsx +37 -0
- package/rules/__fixtures__/store-async-method/good.ts +73 -0
- package/rules/__fixtures__/store-async-method/good.tsx +73 -0
- package/rules/__fixtures__/store-context-provider/bad.ts +16 -0
- package/rules/__fixtures__/store-context-provider/bad.tsx +24 -0
- package/rules/__fixtures__/store-context-provider/good.ts +13 -0
- package/rules/__fixtures__/store-context-provider/good.tsx +11 -0
- package/rules/__fixtures__/store-delegation-getter/bad.ts +30 -0
- package/rules/__fixtures__/store-delegation-getter/bad.tsx +30 -0
- package/rules/__fixtures__/store-delegation-getter/good.ts +65 -0
- package/rules/__fixtures__/store-delegation-getter/good.tsx +65 -0
- package/rules/__fixtures__/store-new-map/bad.ts +22 -0
- package/rules/__fixtures__/store-new-map/bad.tsx +22 -0
- package/rules/__fixtures__/store-new-map/good.ts +41 -0
- package/rules/__fixtures__/store-new-map/good.tsx +41 -0
- package/rules/__fixtures__/store-no-runinaction/bad.ts +67 -0
- package/rules/__fixtures__/store-no-runinaction/bad.tsx +67 -0
- package/rules/__fixtures__/store-no-runinaction/good.ts +39 -0
- package/rules/__fixtures__/store-no-runinaction/good.tsx +39 -0
- package/rules/__fixtures__/store-no-setinterval-poll/bad.ts +19 -0
- package/rules/__fixtures__/store-no-setinterval-poll/bad.tsx +19 -0
- package/rules/__fixtures__/store-no-setinterval-poll/good.ts +29 -0
- package/rules/__fixtures__/store-no-setinterval-poll/good.tsx +29 -0
- package/rules/__fixtures__/ts-pattern-dangling-match/bad.ts +20 -0
- package/rules/__fixtures__/ts-pattern-dangling-match/good.ts +35 -0
- package/rules/__fixtures__/ui-imports-app-store/src/ui/bad.tsx +13 -0
- package/rules/__fixtures__/ui-imports-app-store/src/ui/good.tsx +23 -0
- package/rules/__fixtures__/view-file-logic/bad.view.tsx +16 -0
- package/rules/__fixtures__/view-file-logic/good.view.tsx +7 -0
- package/rules/__fixtures__/view-imports-store/bad.view.tsx +13 -0
- package/rules/__fixtures__/view-imports-store/good.view.tsx +23 -0
- package/rules/__fixtures__/zod-optional-nullable/bad.ts +10 -0
- package/rules/__fixtures__/zod-optional-nullable/bad.tsx +17 -0
- package/rules/__fixtures__/zod-optional-nullable/good.ts +16 -0
- package/rules/__fixtures__/zod-optional-nullable/good.tsx +20 -0
- package/rules/as-any-escape-tsx.yml +53 -0
- package/rules/as-any-escape.yml +29 -0
- package/rules/catch-empty-tsx.yml +55 -0
- package/rules/catch-empty.yml +31 -0
- package/rules/classname-not-composed.yml +28 -0
- package/rules/classname-visual-identity.yml +34 -0
- package/rules/demo-mode-by-default-ts.yml +76 -0
- package/rules/demo-mode-by-default.yml +54 -0
- package/rules/direct-store-import.yml +118 -0
- package/rules/discriminator-ternary.yml +70 -0
- package/rules/hardcoded-url-in-component.yml +44 -0
- package/rules/inline-map-row.yml +107 -0
- package/rules/instanceof-map-set-tsx.yml +46 -0
- package/rules/instanceof-map-set.yml +22 -0
- package/rules/json-roundtrip.yml +9 -0
- package/rules/jsx-cond-and.yml +16 -0
- package/rules/jsx-ternary.yml +35 -0
- package/rules/kind-if-without-match.yml +13 -0
- package/rules/literal-union-in-component.yml +71 -0
- package/rules/match-bool-to-null.yml +15 -0
- package/rules/missing-observer.yml +189 -0
- package/rules/mobx-effect-observable-dep.yml +23 -0
- package/rules/mobx-effect-store-write.yml +21 -0
- package/rules/mobx-usestate-from-store.yml +20 -0
- package/rules/no-local-kit-clone-tsx.yml +104 -0
- package/rules/no-local-kit-clone.yml +93 -0
- package/rules/non-exhaustive-match.yml +34 -0
- package/rules/page-store-bare-usememo-tsx.yml +47 -0
- package/rules/page-store-bare-usememo.yml +43 -0
- package/rules/screen-file-styling.yml +29 -0
- package/rules/silent-default-return.yml +17 -0
- package/rules/state-loading-boolean-shape-tsx.yml +102 -0
- package/rules/state-loading-boolean-shape.yml +91 -0
- package/rules/store-async-method-tsx.yml +108 -0
- package/rules/store-async-method.yml +98 -0
- package/rules/store-context-provider-tsx.yml +68 -0
- package/rules/store-context-provider.yml +78 -0
- package/rules/store-delegation-getter-tsx.yml +104 -0
- package/rules/store-delegation-getter.yml +93 -0
- package/rules/store-new-map-tsx.yml +84 -0
- package/rules/store-new-map.yml +73 -0
- package/rules/store-no-runinaction-tsx.yml +71 -0
- package/rules/store-no-runinaction.yml +60 -0
- package/rules/store-no-setinterval-poll-tsx.yml +62 -0
- package/rules/store-no-setinterval-poll.yml +51 -0
- package/rules/ts-pattern-dangling-match-tsx.yml +41 -0
- package/rules/ts-pattern-dangling-match.yml +63 -0
- package/rules/ui-imports-app-store.yml +49 -0
- package/rules/view-file-logic.yml +53 -0
- package/rules/view-imports-store.yml +64 -0
- package/rules/zod-optional-nullable-tsx.yml +56 -0
- package/rules/zod-optional-nullable.yml +29 -0
- package/sgconfig.yml +8 -0
- package/structure/rules/component-decl.yml +48 -0
- package/structure/sgconfig.yml +7 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
// GOOD: the screen picks the model and composes views; styling lives in them.
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import { rootStore } from "./rootStore";
|
|
4
|
+
import { GalleryGrid } from "./galleryGrid.view";
|
|
5
|
+
|
|
6
|
+
export const GalleryScreen = observer(function GalleryScreen() {
|
|
7
|
+
return <GalleryGrid photos={rootStore.gallery.photos} />;
|
|
8
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Every type here MUST be reported by `state-loading-boolean-shape`.
|
|
2
|
+
|
|
3
|
+
// The canonical boolean soup: loading + optional payload + optional error.
|
|
4
|
+
export interface FetchState {
|
|
5
|
+
loading: boolean
|
|
6
|
+
data?: User[]
|
|
7
|
+
error?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Multi-flag, no payload — the shape gallerykit's ImageLoadState really has.
|
|
11
|
+
export interface ImageLoadState {
|
|
12
|
+
isLoading: boolean
|
|
13
|
+
isLoaded: boolean
|
|
14
|
+
isError: boolean
|
|
15
|
+
error: Event | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Null-union rather than optional; same set of impossible states.
|
|
19
|
+
export interface AdapterState {
|
|
20
|
+
loading: boolean
|
|
21
|
+
data: Rows | null
|
|
22
|
+
error: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// A type alias rather than an interface.
|
|
26
|
+
export type PlaygroundState = {
|
|
27
|
+
isFetching: boolean
|
|
28
|
+
result?: QueryResult
|
|
29
|
+
error?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `pending` is the same axis under another name.
|
|
33
|
+
export interface SaveState {
|
|
34
|
+
pending: boolean
|
|
35
|
+
succeeded: boolean
|
|
36
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
// Every type here MUST be reported by `state-loading-boolean-shape`.
|
|
2
|
+
|
|
3
|
+
// The canonical boolean soup: loading + optional payload + optional error.
|
|
4
|
+
export interface FetchState {
|
|
5
|
+
loading: boolean
|
|
6
|
+
data?: User[]
|
|
7
|
+
error?: string
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
// Multi-flag, no payload — the shape gallerykit's ImageLoadState really has.
|
|
11
|
+
export interface ImageLoadState {
|
|
12
|
+
isLoading: boolean
|
|
13
|
+
isLoaded: boolean
|
|
14
|
+
isError: boolean
|
|
15
|
+
error: Event | null
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Null-union rather than optional; same set of impossible states.
|
|
19
|
+
export interface AdapterState {
|
|
20
|
+
loading: boolean
|
|
21
|
+
data: Rows | null
|
|
22
|
+
error: string | null
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// A type alias rather than an interface.
|
|
26
|
+
export type PlaygroundState = {
|
|
27
|
+
isFetching: boolean
|
|
28
|
+
result?: QueryResult
|
|
29
|
+
error?: string
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// `pending` is the same axis under another name.
|
|
33
|
+
export interface SaveState {
|
|
34
|
+
pending: boolean
|
|
35
|
+
succeeded: boolean
|
|
36
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `state-loading-boolean-shape`.
|
|
2
|
+
|
|
3
|
+
// The fix: one discriminant, payload only in the branch that has one.
|
|
4
|
+
export type Resource<T> =
|
|
5
|
+
| { status: "idle" }
|
|
6
|
+
| { status: "loading" }
|
|
7
|
+
| { status: "ready"; data: T }
|
|
8
|
+
| { status: "error"; message: string }
|
|
9
|
+
|
|
10
|
+
// Independent axes done RIGHT — this is SWR, not boolean soup: "has data" and
|
|
11
|
+
// "a fetch is in flight" genuinely are orthogonal (report §14).
|
|
12
|
+
export type QueryState<T> =
|
|
13
|
+
| { status: "pending"; fetchActivity: "idle" | "fetching" }
|
|
14
|
+
| { status: "success"; data: T; fetchActivity: "idle" | "fetching" }
|
|
15
|
+
| { status: "error"; error: string; fetchActivity: "idle" | "fetching" }
|
|
16
|
+
|
|
17
|
+
// A LONE loading flag on a presentational component's props. One boolean
|
|
18
|
+
// describing one thing, no payload, no second outcome field — there is no
|
|
19
|
+
// invalid state to make unrepresentable. 71 raw `loading: boolean` sites exist
|
|
20
|
+
// park-wide and most look like this; flagging them would be noise.
|
|
21
|
+
export interface LoadMoreButtonProps {
|
|
22
|
+
loading?: boolean
|
|
23
|
+
onPress: () => void
|
|
24
|
+
label: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface InfiniteScrollProps {
|
|
28
|
+
loading?: boolean
|
|
29
|
+
hasMore: boolean
|
|
30
|
+
onLoadMore: () => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// A payload with no loading flag at all.
|
|
34
|
+
export interface UserResponse {
|
|
35
|
+
data?: User
|
|
36
|
+
error?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Booleans that are not about async outcome.
|
|
40
|
+
export interface EditorFlags {
|
|
41
|
+
loading: boolean
|
|
42
|
+
readOnly: boolean
|
|
43
|
+
wrapLines: boolean
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `state-loading-boolean-shape`.
|
|
2
|
+
|
|
3
|
+
// The fix: one discriminant, payload only in the branch that has one.
|
|
4
|
+
export type Resource<T> =
|
|
5
|
+
| { status: "idle" }
|
|
6
|
+
| { status: "loading" }
|
|
7
|
+
| { status: "ready"; data: T }
|
|
8
|
+
| { status: "error"; message: string }
|
|
9
|
+
|
|
10
|
+
// Independent axes done RIGHT — this is SWR, not boolean soup: "has data" and
|
|
11
|
+
// "a fetch is in flight" genuinely are orthogonal (report §14).
|
|
12
|
+
export type QueryState<T> =
|
|
13
|
+
| { status: "pending"; fetchActivity: "idle" | "fetching" }
|
|
14
|
+
| { status: "success"; data: T; fetchActivity: "idle" | "fetching" }
|
|
15
|
+
| { status: "error"; error: string; fetchActivity: "idle" | "fetching" }
|
|
16
|
+
|
|
17
|
+
// A LONE loading flag on a presentational component's props. One boolean
|
|
18
|
+
// describing one thing, no payload, no second outcome field — there is no
|
|
19
|
+
// invalid state to make unrepresentable. 71 raw `loading: boolean` sites exist
|
|
20
|
+
// park-wide and most look like this; flagging them would be noise.
|
|
21
|
+
export interface LoadMoreButtonProps {
|
|
22
|
+
loading?: boolean
|
|
23
|
+
onPress: () => void
|
|
24
|
+
label: string
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface InfiniteScrollProps {
|
|
28
|
+
loading?: boolean
|
|
29
|
+
hasMore: boolean
|
|
30
|
+
onLoadMore: () => void
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// A payload with no loading flag at all.
|
|
34
|
+
export interface UserResponse {
|
|
35
|
+
data?: User
|
|
36
|
+
error?: string
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Booleans that are not about async outcome.
|
|
40
|
+
export interface EditorFlags {
|
|
41
|
+
loading: boolean
|
|
42
|
+
readOnly: boolean
|
|
43
|
+
wrapLines: boolean
|
|
44
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Every async method here MUST be reported by `store-async-method`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class ProjectStore {
|
|
5
|
+
projects: string[] = []
|
|
6
|
+
current: string | null = null
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// The action ends at the await; the assignment after it is unbatched.
|
|
13
|
+
async load(): Promise<void> {
|
|
14
|
+
const list = await fetchProjects()
|
|
15
|
+
this.projects = list
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Two awaits, two holes.
|
|
19
|
+
async open(id: string): Promise<void> {
|
|
20
|
+
this.current = await resolveId(id)
|
|
21
|
+
this.projects = await fetchProjects()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private async refreshQuietly(): Promise<void> {
|
|
25
|
+
this.projects = await fetchProjects()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class SessionStore {
|
|
30
|
+
token = ""
|
|
31
|
+
constructor() {
|
|
32
|
+
makeObservable(this, { token: observable })
|
|
33
|
+
}
|
|
34
|
+
async authenticate(): Promise<void> {
|
|
35
|
+
this.token = await getToken()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// Every async method here MUST be reported by `store-async-method`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class ProjectStore {
|
|
5
|
+
projects: string[] = []
|
|
6
|
+
current: string | null = null
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// The action ends at the await; the assignment after it is unbatched.
|
|
13
|
+
async load(): Promise<void> {
|
|
14
|
+
const list = await fetchProjects()
|
|
15
|
+
this.projects = list
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// Two awaits, two holes.
|
|
19
|
+
async open(id: string): Promise<void> {
|
|
20
|
+
this.current = await resolveId(id)
|
|
21
|
+
this.projects = await fetchProjects()
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
private async refreshQuietly(): Promise<void> {
|
|
25
|
+
this.projects = await fetchProjects()
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export class SessionStore {
|
|
30
|
+
token = ""
|
|
31
|
+
constructor() {
|
|
32
|
+
makeObservable(this, { token: observable })
|
|
33
|
+
}
|
|
34
|
+
async authenticate(): Promise<void> {
|
|
35
|
+
this.token = await getToken()
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-async-method`.
|
|
2
|
+
import { flow, makeAutoObservable, runInAction } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class ProjectStore {
|
|
5
|
+
projects: string[] = []
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
makeAutoObservable(this)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// The fix: a flow resumes inside an action at every yield.
|
|
12
|
+
load = flow(function* (this: ProjectStore) {
|
|
13
|
+
this.projects = yield fetchProjects()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// Already reported by `store-no-runinaction` at error tier, and the fix is
|
|
17
|
+
// the same conversion. Reporting it twice would read as two problems, so this
|
|
18
|
+
// rule stands down when runInAction is present.
|
|
19
|
+
async loadLegacy(): Promise<void> {
|
|
20
|
+
const list = await fetchProjects()
|
|
21
|
+
runInAction(() => {
|
|
22
|
+
this.projects = list
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Not async.
|
|
27
|
+
reset(): void {
|
|
28
|
+
this.projects = []
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// A plain class with no MobX annotation is not a store; async methods on it
|
|
33
|
+
// have no action semantics to lose.
|
|
34
|
+
export class ApiClient {
|
|
35
|
+
async get(url: string): Promise<Response> {
|
|
36
|
+
return fetch(url)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── the false-positive class this rule was narrowed to exclude ─────────
|
|
41
|
+
// Private async helpers that do pure I/O and never touch an observable field.
|
|
42
|
+
// There is no action coverage to lose, so there is nothing to convert. Real:
|
|
43
|
+
// SiteCraft's walkDir / collectAllFiles / uploadFile — 10 of the rule's 12
|
|
44
|
+
// hits in that repo before the mutation requirement was added.
|
|
45
|
+
export class ContentStore {
|
|
46
|
+
files: string[] = []
|
|
47
|
+
|
|
48
|
+
constructor() {
|
|
49
|
+
makeAutoObservable(this)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private async walkDir(fs: FileSystem, dir: string, items: Meta[]): Promise<void> {
|
|
53
|
+
let entries: string[]
|
|
54
|
+
try {
|
|
55
|
+
entries = await fs.readdir(dir)
|
|
56
|
+
} catch {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
for (const e of entries) items.push(await fs.stat(e))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async collectAllFiles(fs: FileSystem, dir: string): Promise<Record<string, string>> {
|
|
63
|
+
const result: Record<string, string> = {}
|
|
64
|
+
await this.walkFs(fs, dir, result)
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async uploadFile(targetDir: string, name: string, data: Uint8Array): Promise<string> {
|
|
69
|
+
const path = `${targetDir}/${name}`
|
|
70
|
+
await this.fs.writeFile(path, data)
|
|
71
|
+
return path
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-async-method`.
|
|
2
|
+
import { flow, makeAutoObservable, runInAction } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class ProjectStore {
|
|
5
|
+
projects: string[] = []
|
|
6
|
+
|
|
7
|
+
constructor() {
|
|
8
|
+
makeAutoObservable(this)
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// The fix: a flow resumes inside an action at every yield.
|
|
12
|
+
load = flow(function* (this: ProjectStore) {
|
|
13
|
+
this.projects = yield fetchProjects()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// Already reported by `store-no-runinaction` at error tier, and the fix is
|
|
17
|
+
// the same conversion. Reporting it twice would read as two problems, so this
|
|
18
|
+
// rule stands down when runInAction is present.
|
|
19
|
+
async loadLegacy(): Promise<void> {
|
|
20
|
+
const list = await fetchProjects()
|
|
21
|
+
runInAction(() => {
|
|
22
|
+
this.projects = list
|
|
23
|
+
})
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Not async.
|
|
27
|
+
reset(): void {
|
|
28
|
+
this.projects = []
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// A plain class with no MobX annotation is not a store; async methods on it
|
|
33
|
+
// have no action semantics to lose.
|
|
34
|
+
export class ApiClient {
|
|
35
|
+
async get(url: string): Promise<Response> {
|
|
36
|
+
return fetch(url)
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// ── the false-positive class this rule was narrowed to exclude ─────────
|
|
41
|
+
// Private async helpers that do pure I/O and never touch an observable field.
|
|
42
|
+
// There is no action coverage to lose, so there is nothing to convert. Real:
|
|
43
|
+
// SiteCraft's walkDir / collectAllFiles / uploadFile — 10 of the rule's 12
|
|
44
|
+
// hits in that repo before the mutation requirement was added.
|
|
45
|
+
export class ContentStore {
|
|
46
|
+
files: string[] = []
|
|
47
|
+
|
|
48
|
+
constructor() {
|
|
49
|
+
makeAutoObservable(this)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
private async walkDir(fs: FileSystem, dir: string, items: Meta[]): Promise<void> {
|
|
53
|
+
let entries: string[]
|
|
54
|
+
try {
|
|
55
|
+
entries = await fs.readdir(dir)
|
|
56
|
+
} catch {
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
for (const e of entries) items.push(await fs.stat(e))
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
private async collectAllFiles(fs: FileSystem, dir: string): Promise<Record<string, string>> {
|
|
63
|
+
const result: Record<string, string> = {}
|
|
64
|
+
await this.walkFs(fs, dir, result)
|
|
65
|
+
return result
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async uploadFile(targetDir: string, name: string, data: Uint8Array): Promise<string> {
|
|
69
|
+
const path = `${targetDir}/${name}`
|
|
70
|
+
await this.fs.writeFile(path, data)
|
|
71
|
+
return path
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// BAD: the whole context channel D1 removes — provider, hook, null-check.
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
type RootStore = { readonly ready: boolean };
|
|
5
|
+
|
|
6
|
+
export const StoreContext = createContext<RootStore | null>(null);
|
|
7
|
+
|
|
8
|
+
export const useStore = (): RootStore => {
|
|
9
|
+
const store = useContext(StoreContext);
|
|
10
|
+
if (store === null) throw new Error("StoreProvider missing");
|
|
11
|
+
return store;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function readReady(): boolean {
|
|
15
|
+
return useStore().ready;
|
|
16
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
// BAD: provider + hook, the shape every store-bearing app in the park still has.
|
|
2
|
+
import { createContext, useContext } from "react";
|
|
3
|
+
|
|
4
|
+
type RootStore = { readonly ready: boolean };
|
|
5
|
+
|
|
6
|
+
const StoreContext = createContext<RootStore | null>(null);
|
|
7
|
+
|
|
8
|
+
export function useStore(): RootStore {
|
|
9
|
+
const store = useContext(StoreContext);
|
|
10
|
+
if (store === null) throw new Error("StoreProvider missing");
|
|
11
|
+
return store;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export function App(props: { readonly store: RootStore }) {
|
|
15
|
+
return (
|
|
16
|
+
<StoreContext.Provider value={props.store}>
|
|
17
|
+
<ReadyBadge />
|
|
18
|
+
</StoreContext.Provider>
|
|
19
|
+
);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function ReadyBadge() {
|
|
23
|
+
return <span>{String(useStore().ready)}</span>;
|
|
24
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// GOOD: one module-level store, dependencies injected once from the entry file.
|
|
2
|
+
type RootStore = { readonly ready: boolean };
|
|
3
|
+
|
|
4
|
+
let instance: RootStore | undefined;
|
|
5
|
+
|
|
6
|
+
export function initRootStore(deps: RootStore): void {
|
|
7
|
+
instance = deps;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function getRootStore(): RootStore {
|
|
11
|
+
if (instance === undefined) throw new Error("initRootStore() has not run yet");
|
|
12
|
+
return instance;
|
|
13
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// GOOD: the screen imports the singleton directly, the row gets a prop.
|
|
2
|
+
import { observer } from "mobx-react-lite";
|
|
3
|
+
import { rootStore } from "./rootStore";
|
|
4
|
+
|
|
5
|
+
export const AppScreen = observer(function AppScreen() {
|
|
6
|
+
return <ReadyBadge ready={rootStore.ready} />;
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
function ReadyBadge(props: { readonly ready: boolean }) {
|
|
10
|
+
return <span>{String(props.ready)}</span>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Every getter here MUST be reported by `store-delegation-getter`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class AppStore {
|
|
5
|
+
constructor(
|
|
6
|
+
public userStore: UserStore,
|
|
7
|
+
public connectionStore: ConnectionStore,
|
|
8
|
+
) {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A second name for someone else's field.
|
|
13
|
+
get userName(): string {
|
|
14
|
+
return this.userStore.name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get userId(): string {
|
|
18
|
+
return this.userStore.id
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The real shape found in the park (2 sites, metro/byok survey).
|
|
22
|
+
get isConnected(): boolean {
|
|
23
|
+
return this.connectionStore.isConnected
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Single-expression body, same defect.
|
|
27
|
+
get retryCount(): number {
|
|
28
|
+
return this.connectionStore.retries
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// Every getter here MUST be reported by `store-delegation-getter`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class AppStore {
|
|
5
|
+
constructor(
|
|
6
|
+
public userStore: UserStore,
|
|
7
|
+
public connectionStore: ConnectionStore,
|
|
8
|
+
) {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// A second name for someone else's field.
|
|
13
|
+
get userName(): string {
|
|
14
|
+
return this.userStore.name
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get userId(): string {
|
|
18
|
+
return this.userStore.id
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// The real shape found in the park (2 sites, metro/byok survey).
|
|
22
|
+
get isConnected(): boolean {
|
|
23
|
+
return this.connectionStore.isConnected
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
// Single-expression body, same defect.
|
|
27
|
+
get retryCount(): number {
|
|
28
|
+
return this.connectionStore.retries
|
|
29
|
+
}
|
|
30
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-delegation-getter`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
// ── The data-wrapper pattern: 89 of the 91 single-line `return this.X.y`
|
|
5
|
+
// getters measured across byok, metro, SiteCraft and observatory look exactly
|
|
6
|
+
// like this. A model holding a plain record and republishing its fields as
|
|
7
|
+
// observable getters is the sanctioned shape (byok packages/shared/src/models/
|
|
8
|
+
// model.ts, metro apps/web-pmetro/src/stores/domain/station.store.ts).
|
|
9
|
+
export class Model {
|
|
10
|
+
readonly data: ModelRecordData
|
|
11
|
+
|
|
12
|
+
constructor(data: ModelRecordData) {
|
|
13
|
+
this.data = data
|
|
14
|
+
makeAutoObservable(this, { data: false })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get id(): string {
|
|
18
|
+
return this.data.id
|
|
19
|
+
}
|
|
20
|
+
get slug(): string {
|
|
21
|
+
return this.data.slug
|
|
22
|
+
}
|
|
23
|
+
get name(): string {
|
|
24
|
+
return this.data.name
|
|
25
|
+
}
|
|
26
|
+
get backend(): ModelBackend {
|
|
27
|
+
return this.data.backend
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class AppStore {
|
|
32
|
+
constructor(public userStore: UserStore) {
|
|
33
|
+
makeAutoObservable(this)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A real derivation — combines, formats, decides. This is what computeds are
|
|
37
|
+
// for and it is not an alias.
|
|
38
|
+
get greeting(): string {
|
|
39
|
+
return `Hello, ${this.userStore.name}!`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get hasName(): boolean {
|
|
43
|
+
return this.userStore.name.length > 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Computed on the way out, so not a bare forward.
|
|
47
|
+
get displayName(): string {
|
|
48
|
+
const raw = this.userStore.name
|
|
49
|
+
return raw.trim() === "" ? "anonymous" : raw
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Reads its OWN field, not another store's.
|
|
53
|
+
get ready(): boolean {
|
|
54
|
+
return this.loaded
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// KNOWN, DELIBERATE MISS. A rootStore reference conventionally named `root`
|
|
58
|
+
// (WireDrill spells it that way, annotated `{ root: false }`) is a genuine
|
|
59
|
+
// store-to-store alias that this rule does not catch — the name test keys on
|
|
60
|
+
// a `Store` suffix, and widening it to `root` starts pulling `this.data`-shaped
|
|
61
|
+
// names back into scope. Pinned here so the gap stays a decision.
|
|
62
|
+
get rootUser(): User {
|
|
63
|
+
return this.root.user
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-delegation-getter`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
// ── The data-wrapper pattern: 89 of the 91 single-line `return this.X.y`
|
|
5
|
+
// getters measured across byok, metro, SiteCraft and observatory look exactly
|
|
6
|
+
// like this. A model holding a plain record and republishing its fields as
|
|
7
|
+
// observable getters is the sanctioned shape (byok packages/shared/src/models/
|
|
8
|
+
// model.ts, metro apps/web-pmetro/src/stores/domain/station.store.ts).
|
|
9
|
+
export class Model {
|
|
10
|
+
readonly data: ModelRecordData
|
|
11
|
+
|
|
12
|
+
constructor(data: ModelRecordData) {
|
|
13
|
+
this.data = data
|
|
14
|
+
makeAutoObservable(this, { data: false })
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
get id(): string {
|
|
18
|
+
return this.data.id
|
|
19
|
+
}
|
|
20
|
+
get slug(): string {
|
|
21
|
+
return this.data.slug
|
|
22
|
+
}
|
|
23
|
+
get name(): string {
|
|
24
|
+
return this.data.name
|
|
25
|
+
}
|
|
26
|
+
get backend(): ModelBackend {
|
|
27
|
+
return this.data.backend
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export class AppStore {
|
|
32
|
+
constructor(public userStore: UserStore) {
|
|
33
|
+
makeAutoObservable(this)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// A real derivation — combines, formats, decides. This is what computeds are
|
|
37
|
+
// for and it is not an alias.
|
|
38
|
+
get greeting(): string {
|
|
39
|
+
return `Hello, ${this.userStore.name}!`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get hasName(): boolean {
|
|
43
|
+
return this.userStore.name.length > 0
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Computed on the way out, so not a bare forward.
|
|
47
|
+
get displayName(): string {
|
|
48
|
+
const raw = this.userStore.name
|
|
49
|
+
return raw.trim() === "" ? "anonymous" : raw
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Reads its OWN field, not another store's.
|
|
53
|
+
get ready(): boolean {
|
|
54
|
+
return this.loaded
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// KNOWN, DELIBERATE MISS. A rootStore reference conventionally named `root`
|
|
58
|
+
// (WireDrill spells it that way, annotated `{ root: false }`) is a genuine
|
|
59
|
+
// store-to-store alias that this rule does not catch — the name test keys on
|
|
60
|
+
// a `Store` suffix, and widening it to `root` starts pulling `this.data`-shaped
|
|
61
|
+
// names back into scope. Pinned here so the gap stays a decision.
|
|
62
|
+
get rootUser(): User {
|
|
63
|
+
return this.root.user
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// Every field here MUST be reported by `store-new-map`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class GalleryStore {
|
|
5
|
+
// Observable field, non-observable contents: .set() notifies nobody.
|
|
6
|
+
entries = new Map<string, Row>()
|
|
7
|
+
byId = new Map()
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
makeAutoObservable(this)
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class CalendarStore {
|
|
15
|
+
// Annotated, but this field is not the one exempted.
|
|
16
|
+
monthCache = new Map<string, Day[]>()
|
|
17
|
+
private _other = new Map<string, number>()
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
makeAutoObservable(this, { somethingElse: false })
|
|
21
|
+
}
|
|
22
|
+
}
|