@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,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
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-new-map`.
|
|
2
|
+
import { makeAutoObservable, observable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class GalleryStore {
|
|
5
|
+
// The fix: mutations are tracked.
|
|
6
|
+
entries = observable.map<string, Row>()
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// The LEGAL pattern the rule must not break: a private cache explicitly
|
|
14
|
+
// annotated non-observable. Nothing renders from it, so a plain Map is right.
|
|
15
|
+
export class MainPageStore {
|
|
16
|
+
private _jobInfoCache = new Map<string, JobInfo>()
|
|
17
|
+
jobs: Job[] = []
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
makeAutoObservable(this, { _jobInfoCache: false })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Several exempted fields at once.
|
|
25
|
+
export class MetricsStore {
|
|
26
|
+
private counters = new Map<string, number>()
|
|
27
|
+
private gauges = new Map<string, number>()
|
|
28
|
+
total = 0
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
makeAutoObservable(this, { counters: false, gauges: false })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Not a store: a plain service class.
|
|
36
|
+
export class MetricsService {
|
|
37
|
+
private counters = new Map<string, number>()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Module-level constant, not a store field.
|
|
41
|
+
export const REGISTRY = new Map<string, string>()
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-new-map`.
|
|
2
|
+
import { makeAutoObservable, observable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class GalleryStore {
|
|
5
|
+
// The fix: mutations are tracked.
|
|
6
|
+
entries = observable.map<string, Row>()
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// The LEGAL pattern the rule must not break: a private cache explicitly
|
|
14
|
+
// annotated non-observable. Nothing renders from it, so a plain Map is right.
|
|
15
|
+
export class MainPageStore {
|
|
16
|
+
private _jobInfoCache = new Map<string, JobInfo>()
|
|
17
|
+
jobs: Job[] = []
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
makeAutoObservable(this, { _jobInfoCache: false })
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Several exempted fields at once.
|
|
25
|
+
export class MetricsStore {
|
|
26
|
+
private counters = new Map<string, number>()
|
|
27
|
+
private gauges = new Map<string, number>()
|
|
28
|
+
total = 0
|
|
29
|
+
|
|
30
|
+
constructor() {
|
|
31
|
+
makeAutoObservable(this, { counters: false, gauges: false })
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Not a store: a plain service class.
|
|
36
|
+
export class MetricsService {
|
|
37
|
+
private counters = new Map<string, number>()
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Module-level constant, not a store field.
|
|
41
|
+
export const REGISTRY = new Map<string, string>()
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Every runInAction call in this file MUST be reported by `store-no-runinaction`.
|
|
2
|
+
import { makeAutoObservable, runInAction } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class GitStore {
|
|
5
|
+
loading = false
|
|
6
|
+
branch: string | null = null
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async refresh(dir: string): Promise<void> {
|
|
13
|
+
runInAction(() => {
|
|
14
|
+
this.loading = true
|
|
15
|
+
})
|
|
16
|
+
const branch = await readBranch(dir)
|
|
17
|
+
runInAction(() => {
|
|
18
|
+
this.branch = branch
|
|
19
|
+
this.loading = false
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Nested inside a callback — still inside the store class.
|
|
24
|
+
subscribe(): void {
|
|
25
|
+
onChange(() => {
|
|
26
|
+
runInAction(() => {
|
|
27
|
+
this.branch = null
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The generic-argument spelling of the annotation. A `pattern:
|
|
34
|
+
// makeAutoObservable($$$)` does not match a call carrying explicit type
|
|
35
|
+
// arguments, which is how SiteCraft's project.store.ts — 7 real call sites —
|
|
36
|
+
// went missing from the first version of this rule.
|
|
37
|
+
export class ProjectStore {
|
|
38
|
+
private _buildTimer: number | null = null
|
|
39
|
+
projects: string[] = []
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
makeAutoObservable<this, "_buildTimer">(this, { _buildTimer: false })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async load(): Promise<void> {
|
|
46
|
+
const list = await fetchProjects()
|
|
47
|
+
runInAction(() => {
|
|
48
|
+
this.projects = list
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// `makeObservable` is the other spelling of the same thing.
|
|
54
|
+
export class SessionStore {
|
|
55
|
+
items: string[] = []
|
|
56
|
+
|
|
57
|
+
constructor() {
|
|
58
|
+
makeObservable(this, { items: observable })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async pull(): Promise<void> {
|
|
62
|
+
const items = await fetchItems()
|
|
63
|
+
runInAction(() => {
|
|
64
|
+
this.items = items
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
// Every runInAction call in this file MUST be reported by `store-no-runinaction`.
|
|
2
|
+
import { makeAutoObservable, runInAction } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class GitStore {
|
|
5
|
+
loading = false
|
|
6
|
+
branch: string | null = null
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
async refresh(dir: string): Promise<void> {
|
|
13
|
+
runInAction(() => {
|
|
14
|
+
this.loading = true
|
|
15
|
+
})
|
|
16
|
+
const branch = await readBranch(dir)
|
|
17
|
+
runInAction(() => {
|
|
18
|
+
this.branch = branch
|
|
19
|
+
this.loading = false
|
|
20
|
+
})
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Nested inside a callback — still inside the store class.
|
|
24
|
+
subscribe(): void {
|
|
25
|
+
onChange(() => {
|
|
26
|
+
runInAction(() => {
|
|
27
|
+
this.branch = null
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// The generic-argument spelling of the annotation. A `pattern:
|
|
34
|
+
// makeAutoObservable($$$)` does not match a call carrying explicit type
|
|
35
|
+
// arguments, which is how SiteCraft's project.store.ts — 7 real call sites —
|
|
36
|
+
// went missing from the first version of this rule.
|
|
37
|
+
export class ProjectStore {
|
|
38
|
+
private _buildTimer: number | null = null
|
|
39
|
+
projects: string[] = []
|
|
40
|
+
|
|
41
|
+
constructor() {
|
|
42
|
+
makeAutoObservable<this, "_buildTimer">(this, { _buildTimer: false })
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async load(): Promise<void> {
|
|
46
|
+
const list = await fetchProjects()
|
|
47
|
+
runInAction(() => {
|
|
48
|
+
this.projects = list
|
|
49
|
+
})
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// `makeObservable` is the other spelling of the same thing.
|
|
54
|
+
export class SessionStore {
|
|
55
|
+
items: string[] = []
|
|
56
|
+
|
|
57
|
+
constructor() {
|
|
58
|
+
makeObservable(this, { items: observable })
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async pull(): Promise<void> {
|
|
62
|
+
const items = await fetchItems()
|
|
63
|
+
runInAction(() => {
|
|
64
|
+
this.items = items
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-no-runinaction`.
|
|
2
|
+
import { flow, makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
// The fix: a flow. Every yield point resumes inside an action, so there is
|
|
5
|
+
// nothing left for runInAction to wrap.
|
|
6
|
+
export class GitStore {
|
|
7
|
+
loading = false
|
|
8
|
+
branch: string | null = null
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
makeAutoObservable(this)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
refresh = flow(function* (this: GitStore, dir: string) {
|
|
15
|
+
this.loading = true
|
|
16
|
+
this.branch = yield readBranch(dir)
|
|
17
|
+
this.loading = false
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A plain class with no MobX annotation is not a store. `runInAction` here
|
|
22
|
+
// would be pointless rather than banned, and this rule is about stores.
|
|
23
|
+
export class PlainCache {
|
|
24
|
+
entries: string[] = []
|
|
25
|
+
|
|
26
|
+
add(entry: string): void {
|
|
27
|
+
this.entries.push(entry)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A module-level helper that is not inside any store class.
|
|
32
|
+
export function describeBranch(branch: string): string {
|
|
33
|
+
return `on ${branch}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Importing the symbol is not calling it. The report's "66 runInAction in
|
|
37
|
+
// SiteCraft" counted 7 import lines exactly like this one; the real call-site
|
|
38
|
+
// count is 59.
|
|
39
|
+
export { runInAction } from "mobx"
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-no-runinaction`.
|
|
2
|
+
import { flow, makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
// The fix: a flow. Every yield point resumes inside an action, so there is
|
|
5
|
+
// nothing left for runInAction to wrap.
|
|
6
|
+
export class GitStore {
|
|
7
|
+
loading = false
|
|
8
|
+
branch: string | null = null
|
|
9
|
+
|
|
10
|
+
constructor() {
|
|
11
|
+
makeAutoObservable(this)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
refresh = flow(function* (this: GitStore, dir: string) {
|
|
15
|
+
this.loading = true
|
|
16
|
+
this.branch = yield readBranch(dir)
|
|
17
|
+
this.loading = false
|
|
18
|
+
})
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// A plain class with no MobX annotation is not a store. `runInAction` here
|
|
22
|
+
// would be pointless rather than banned, and this rule is about stores.
|
|
23
|
+
export class PlainCache {
|
|
24
|
+
entries: string[] = []
|
|
25
|
+
|
|
26
|
+
add(entry: string): void {
|
|
27
|
+
this.entries.push(entry)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// A module-level helper that is not inside any store class.
|
|
32
|
+
export function describeBranch(branch: string): string {
|
|
33
|
+
return `on ${branch}`
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// Importing the symbol is not calling it. The report's "66 runInAction in
|
|
37
|
+
// SiteCraft" counted 7 import lines exactly like this one; the real call-site
|
|
38
|
+
// count is 59.
|
|
39
|
+
export { runInAction } from "mobx"
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Every setInterval call here MUST be reported by `store-no-setinterval-poll`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class LiveDataStore {
|
|
5
|
+
rows: Row[] = []
|
|
6
|
+
private timer: ReturnType<typeof setInterval> | undefined
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
start(): void {
|
|
13
|
+
this.timer = setInterval(() => void this.refresh(), POLL_MS)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
startSynthetic(sink: Sink): void {
|
|
17
|
+
setInterval(() => sink.onRow(this.emitSyntheticRow()), SSE_TICK_MS)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Every setInterval call here MUST be reported by `store-no-setinterval-poll`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class LiveDataStore {
|
|
5
|
+
rows: Row[] = []
|
|
6
|
+
private timer: ReturnType<typeof setInterval> | undefined
|
|
7
|
+
|
|
8
|
+
constructor() {
|
|
9
|
+
makeAutoObservable(this)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
start(): void {
|
|
13
|
+
this.timer = setInterval(() => void this.refresh(), POLL_MS)
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
startSynthetic(sink: Sink): void {
|
|
17
|
+
setInterval(() => sink.onRow(this.emitSyntheticRow()), SSE_TICK_MS)
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-no-setinterval-poll`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class LiveDataStore {
|
|
5
|
+
rows: Row[] = []
|
|
6
|
+
|
|
7
|
+
// A TYPE position, not a call. The commonest way `setInterval` appears in a
|
|
8
|
+
// store field declaration, and it must stay silent.
|
|
9
|
+
private timer: ReturnType<typeof setInterval> | undefined
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
makeAutoObservable(this)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// The fix: the Query object owns the disposer, the in-flight dedup and the
|
|
16
|
+
// visibility handling.
|
|
17
|
+
readonly rowsQuery = new Query(() => fetchRows(), { refetchInterval: POLL_MS })
|
|
18
|
+
|
|
19
|
+
stop(): void {
|
|
20
|
+
clearInterval(this.timer)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Not a store: a plain scheduler class.
|
|
25
|
+
export class Ticker {
|
|
26
|
+
start(): void {
|
|
27
|
+
setInterval(() => this.tick(), 1000)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `store-no-setinterval-poll`.
|
|
2
|
+
import { makeAutoObservable } from "mobx"
|
|
3
|
+
|
|
4
|
+
export class LiveDataStore {
|
|
5
|
+
rows: Row[] = []
|
|
6
|
+
|
|
7
|
+
// A TYPE position, not a call. The commonest way `setInterval` appears in a
|
|
8
|
+
// store field declaration, and it must stay silent.
|
|
9
|
+
private timer: ReturnType<typeof setInterval> | undefined
|
|
10
|
+
|
|
11
|
+
constructor() {
|
|
12
|
+
makeAutoObservable(this)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// The fix: the Query object owns the disposer, the in-flight dedup and the
|
|
16
|
+
// visibility handling.
|
|
17
|
+
readonly rowsQuery = new Query(() => fetchRows(), { refetchInterval: POLL_MS })
|
|
18
|
+
|
|
19
|
+
stop(): void {
|
|
20
|
+
clearInterval(this.timer)
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Not a store: a plain scheduler class.
|
|
25
|
+
export class Ticker {
|
|
26
|
+
start(): void {
|
|
27
|
+
setInterval(() => this.tick(), 1000)
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Every match chain here MUST be reported by `ts-pattern-dangling-match`.
|
|
2
|
+
import { match } from "ts-pattern"
|
|
3
|
+
|
|
4
|
+
// A real observatory chain with its terminator deleted: evaluates to a builder
|
|
5
|
+
// object, which is truthy, so the caller keeps compiling and every arm is dead.
|
|
6
|
+
export function label(r: Resource<string>): string {
|
|
7
|
+
return match(r)
|
|
8
|
+
.with({ kind: "idle" }, () => "idle")
|
|
9
|
+
.with({ kind: "ready" }, ({ value }) => value)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// The hard case: a dangling chain nested inside a callback of an OUTER
|
|
13
|
+
// terminated chain. The spine-walk must not let the outer terminator mask it.
|
|
14
|
+
export function label3(r: Resource<Item[]>): string {
|
|
15
|
+
return match(r)
|
|
16
|
+
.with({ kind: "ready" }, ({ value }) =>
|
|
17
|
+
value.map((it) => match(it).with({ t: "a" }, () => "A")).join(","),
|
|
18
|
+
)
|
|
19
|
+
.otherwise(() => "")
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `ts-pattern-dangling-match`.
|
|
2
|
+
import { match } from "ts-pattern"
|
|
3
|
+
|
|
4
|
+
export function label(r: Resource<string>): string {
|
|
5
|
+
return match(r)
|
|
6
|
+
.with({ kind: "idle" }, () => "idle")
|
|
7
|
+
.with({ kind: "ready" }, ({ value }) => value)
|
|
8
|
+
.exhaustive()
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
// `.otherwise()` and `.run()` terminate a chain too. Whether `.exhaustive()`
|
|
12
|
+
// should be PREFERRED over `.otherwise()` is a separate question the owner has
|
|
13
|
+
// deferred (decision #4); this rule is only about a chain that ends nowhere.
|
|
14
|
+
export function label2(r: Resource<string>): string {
|
|
15
|
+
return match(r).with({ kind: "idle" }, () => "idle").otherwise(() => "?")
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function label4(r: Resource<string>): string {
|
|
19
|
+
return match(r).with({ kind: "idle" }, () => "idle").run()
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// Per-arm comments are why the terminator is matched structurally rather than
|
|
23
|
+
// with a surface pattern: `smart` strictness compares comment nodes too.
|
|
24
|
+
export function label5(r: Resource<string>): string {
|
|
25
|
+
return match(r)
|
|
26
|
+
// the boring case
|
|
27
|
+
.with({ kind: "idle" }, () => "idle")
|
|
28
|
+
// the interesting one
|
|
29
|
+
.with({ kind: "ready" }, ({ value }) => value)
|
|
30
|
+
.exhaustive()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// String.prototype.match is not ts-pattern and must never be confused for it.
|
|
34
|
+
export const m = "abc".match(/b/)
|
|
35
|
+
export const m2 = someText.match(pattern)?.[0]
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Every import in this file MUST be reported by `ui-imports-app-store`.
|
|
2
|
+
// Path matters: the rule is scoped to `**/src/ui/**`, so this fixture has to
|
|
3
|
+
// live under `src/ui/` for the rule to see it at all.
|
|
4
|
+
|
|
5
|
+
import { galleryStore } from "../../stores/galleryStore"
|
|
6
|
+
import { GalleryStore } from "../../stores/galleryStore"
|
|
7
|
+
import { rootStore } from "../../store/rootStore"
|
|
8
|
+
import { useStore } from "../../stores/StoreContext"
|
|
9
|
+
import photoStore from "../../stores/photoStore"
|
|
10
|
+
|
|
11
|
+
export const Panel = ({ label }: { label: string }) => (
|
|
12
|
+
<div>{label}{galleryStore.n}{GalleryStore.name}{rootStore.n}{useStore.name}{photoStore.n}</div>
|
|
13
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `ui-imports-app-store`.
|
|
2
|
+
|
|
3
|
+
// X7 (report §2): a reusable UI component typed against the view-model
|
|
4
|
+
// interface the store implements is exactly what the mobx-models view-model
|
|
5
|
+
// pattern prescribes. `import type` erases at build time — the component
|
|
6
|
+
// depends on a shape, not on an app store — so it must not trip the rule that
|
|
7
|
+
// bans importing the store itself.
|
|
8
|
+
import type { IGalleryModel } from "../../stores/galleryStore"
|
|
9
|
+
import type { RootStore } from "../../store/rootStore"
|
|
10
|
+
import type Photo from "../../stores/photo"
|
|
11
|
+
import type * as StoreTypes from "../../stores/types"
|
|
12
|
+
import { type IUiModel, type ITabModel } from "../../stores/uiStore"
|
|
13
|
+
|
|
14
|
+
// Ordinary UI-package dependencies.
|
|
15
|
+
import { cn } from "../../lib/cn"
|
|
16
|
+
|
|
17
|
+
export const Panel = ({
|
|
18
|
+
model,
|
|
19
|
+
ui,
|
|
20
|
+
}: {
|
|
21
|
+
model: IGalleryModel
|
|
22
|
+
ui: IUiModel
|
|
23
|
+
}) => <div className={cn("panel")}>{model.title} {ui.tab}</div>
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// BAD: a view that decides. Hook, match, ternary — all three belong upstairs.
|
|
2
|
+
import { useState } from "react";
|
|
3
|
+
import { match } from "ts-pattern";
|
|
4
|
+
|
|
5
|
+
export function StatusBadge(props: { readonly status: "ok" | "bad"; readonly label: string }) {
|
|
6
|
+
const [open, setOpen] = useState(false);
|
|
7
|
+
const tone = match(props.status)
|
|
8
|
+
.with("ok", () => "green")
|
|
9
|
+
.with("bad", () => "red")
|
|
10
|
+
.exhaustive();
|
|
11
|
+
return (
|
|
12
|
+
<span onClick={() => setOpen(true)} style={{ color: tone }}>
|
|
13
|
+
{open ? props.label : ""}
|
|
14
|
+
</span>
|
|
15
|
+
);
|
|
16
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
// GOOD: props in, JSX out. The tone was decided upstairs; useTheme is exempt.
|
|
2
|
+
import { useTheme } from "./theme";
|
|
3
|
+
|
|
4
|
+
export function StatusBadge(props: { readonly tone: string; readonly label: string }) {
|
|
5
|
+
const theme = useTheme();
|
|
6
|
+
return <span style={{ color: props.tone, fontFamily: theme.font }}>{props.label}</span>;
|
|
7
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// Every import in this file MUST be reported by `view-imports-store`.
|
|
2
|
+
// A `*.view.tsx` is props in, JSX out — it may not reach a store by any route,
|
|
3
|
+
// not even the global rootStore that a screen is allowed to import.
|
|
4
|
+
|
|
5
|
+
import { rootStore } from "../../stores/rootStore"
|
|
6
|
+
import { galleryStore } from "../../stores/galleryStore"
|
|
7
|
+
import { GalleryStore } from "../../stores/galleryStore"
|
|
8
|
+
import { useStore } from "../../stores/StoreContext"
|
|
9
|
+
import { SESSION_KEY } from "../../stores/keys"
|
|
10
|
+
|
|
11
|
+
export const GalleryHeaderView = ({ title }: { title: string }) => (
|
|
12
|
+
<h1 data-k={SESSION_KEY}>{title}{rootStore.n}{galleryStore.n}{GalleryStore.name}{useStore.name}</h1>
|
|
13
|
+
)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `view-imports-store`.
|
|
2
|
+
|
|
3
|
+
// A view is typed against the model interface it renders (C40). The type
|
|
4
|
+
// vanishes at build time, so it is a contract, not a store dependency.
|
|
5
|
+
import type { IGalleryModel } from "../../stores/galleryStore"
|
|
6
|
+
import { type IUiModel } from "../../stores/uiStore"
|
|
7
|
+
|
|
8
|
+
// Everything else a view is allowed to touch: shared UI primitives and pure
|
|
9
|
+
// formatting helpers.
|
|
10
|
+
import { Badge } from "../../components/Badge/Badge"
|
|
11
|
+
import { formatBytes } from "../../lib/format"
|
|
12
|
+
|
|
13
|
+
export const GalleryHeaderView = ({
|
|
14
|
+
model,
|
|
15
|
+
ui,
|
|
16
|
+
}: {
|
|
17
|
+
model: IGalleryModel
|
|
18
|
+
ui: IUiModel
|
|
19
|
+
}) => (
|
|
20
|
+
<h1>
|
|
21
|
+
{model.title} <Badge label={formatBytes(model.size)} /> {ui.tab}
|
|
22
|
+
</h1>
|
|
23
|
+
)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Every call here MUST be reported by `zod-optional-nullable`.
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
export const A = z.object({ name: z.string().optional() })
|
|
4
|
+
export const B = z.object({ age: z.number().nullable() })
|
|
5
|
+
export const C = z.object({ bio: z.string().nullish() })
|
|
6
|
+
// ADDED BY THE WAVE-2 UPGRADE: the old `z.$T().optional()` matched only a
|
|
7
|
+
// DIRECT call on `z`, so a chained builder and a named schema both escaped.
|
|
8
|
+
export const D = z.object({ name: z.string().min(1).optional() })
|
|
9
|
+
export const E = UserSchema.optional()
|
|
10
|
+
export const F = z.object({ tag: z.enum(["a", "b"]).trim().nullable() })
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// Every call here MUST be reported by `zod-optional-nullable-tsx`.
|
|
2
|
+
// The .tsx twin of bad.ts, placed where this rule actually needed to see:
|
|
3
|
+
// a validation schema sitting right next to the component that uses it.
|
|
4
|
+
import { z } from "zod"
|
|
5
|
+
|
|
6
|
+
export const A = z.object({ name: z.string().optional() })
|
|
7
|
+
export const B = z.object({ age: z.number().nullable() })
|
|
8
|
+
export const C = z.object({ bio: z.string().nullish() })
|
|
9
|
+
// ADDED BY THE WAVE-2 UPGRADE: the old `z.$T().optional()` matched only a
|
|
10
|
+
// DIRECT call on `z`, so a chained builder and a named schema both escaped.
|
|
11
|
+
export const D = z.object({ name: z.string().min(1).optional() })
|
|
12
|
+
export const E = UserSchema.optional()
|
|
13
|
+
export const F = z.object({ tag: z.enum(["a", "b"]).trim().nullable() })
|
|
14
|
+
|
|
15
|
+
export const ProfileForm = () => {
|
|
16
|
+
return <form aria-label="profile">{A.shape.name.description}</form>
|
|
17
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `zod-optional-nullable`.
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
// The fix: model the absence as a variant, so "missing" and "present" are
|
|
5
|
+
// distinguishable states rather than one field that might be undefined.
|
|
6
|
+
export const User = z.discriminatedUnion("status", [
|
|
7
|
+
z.object({ status: z.literal("anonymous") }),
|
|
8
|
+
z.object({ status: z.literal("named"), name: z.string() }),
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
export const Plain = z.object({ name: z.string(), age: z.number() })
|
|
12
|
+
|
|
13
|
+
// `.optional()` on something that is not a zod schema — the receiver anchor is
|
|
14
|
+
// what keeps an unrelated builder with the same method name out of this rule.
|
|
15
|
+
export const q = queryBuilder.optional()
|
|
16
|
+
export const p = protoField.nullable()
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// NOTHING in this file may be reported by `zod-optional-nullable-tsx`.
|
|
2
|
+
import { z } from "zod"
|
|
3
|
+
|
|
4
|
+
// The fix: model the absence as a variant, so "missing" and "present" are
|
|
5
|
+
// distinguishable states rather than one field that might be undefined.
|
|
6
|
+
export const User = z.discriminatedUnion("status", [
|
|
7
|
+
z.object({ status: z.literal("anonymous") }),
|
|
8
|
+
z.object({ status: z.literal("named"), name: z.string() }),
|
|
9
|
+
])
|
|
10
|
+
|
|
11
|
+
export const Plain = z.object({ name: z.string(), age: z.number() })
|
|
12
|
+
|
|
13
|
+
// `.optional()` on something that is not a zod schema — the receiver anchor is
|
|
14
|
+
// what keeps an unrelated builder with the same method name out of this rule.
|
|
15
|
+
export const q = queryBuilder.optional()
|
|
16
|
+
export const p = protoField.nullable()
|
|
17
|
+
|
|
18
|
+
export const ProfileForm = () => {
|
|
19
|
+
return <form aria-label="profile">{Plain.shape.name.description}</form>
|
|
20
|
+
}
|