sygnal 5.3.6 → 5.4.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 +53 -38
- package/dist/astro/client.cjs.js +18 -8106
- package/dist/astro/client.cjs.js.map +1 -0
- package/dist/astro/client.mjs +17 -8105
- package/dist/astro/client.mjs.map +1 -0
- package/dist/astro/index.cjs.js +768 -9
- package/dist/astro/index.cjs.js.map +1 -0
- package/dist/astro/index.mjs +768 -9
- package/dist/astro/index.mjs.map +1 -0
- package/dist/astro/server.cjs.js +4 -1
- package/dist/astro/server.cjs.js.map +1 -0
- package/dist/astro/server.mjs +4 -1
- package/dist/astro/server.mjs.map +1 -0
- package/dist/diagnostics.cjs.js +1297 -0
- package/dist/diagnostics.cjs.js.map +1 -0
- package/dist/diagnostics.esm.js +1267 -0
- package/dist/diagnostics.esm.js.map +1 -0
- package/dist/index.cjs.js +2981 -1386
- package/dist/index.cjs.js.map +1 -0
- package/dist/index.d.ts +848 -111
- package/dist/index.esm.js +2997 -1411
- package/dist/index.esm.js.map +1 -0
- package/dist/jsx-dev-runtime.cjs.js +15 -9
- package/dist/jsx-dev-runtime.cjs.js.map +1 -0
- package/dist/jsx-dev-runtime.esm.js +15 -9
- package/dist/jsx-dev-runtime.esm.js.map +1 -0
- package/dist/jsx-runtime.cjs.js +15 -9
- package/dist/jsx-runtime.cjs.js.map +1 -0
- package/dist/jsx-runtime.esm.js +15 -9
- package/dist/jsx-runtime.esm.js.map +1 -0
- package/dist/jsx.cjs.js +15 -9
- package/dist/jsx.cjs.js.map +1 -0
- package/dist/jsx.esm.js +15 -9
- package/dist/jsx.esm.js.map +1 -0
- package/dist/sygnal.min.js +2 -1
- package/dist/sygnal.min.js.map +1 -0
- package/dist/vike/+config.cjs.js +1 -0
- package/dist/vike/+config.cjs.js.map +1 -0
- package/dist/vike/+config.js +1 -0
- package/dist/vike/+config.js.map +1 -0
- package/dist/vike/ClientOnly.cjs.js +1 -0
- package/dist/vike/ClientOnly.cjs.js.map +1 -0
- package/dist/vike/ClientOnly.mjs +1 -0
- package/dist/vike/ClientOnly.mjs.map +1 -0
- package/dist/vike/onRenderClient.cjs.js +1 -0
- package/dist/vike/onRenderClient.cjs.js.map +1 -0
- package/dist/vike/onRenderClient.mjs +1 -0
- package/dist/vike/onRenderClient.mjs.map +1 -0
- package/dist/vike/onRenderHtml.cjs.js +1 -0
- package/dist/vike/onRenderHtml.cjs.js.map +1 -0
- package/dist/vike/onRenderHtml.mjs +1 -0
- package/dist/vike/onRenderHtml.mjs.map +1 -0
- package/dist/vite/plugin.cjs.js +680 -47
- package/dist/vite/plugin.cjs.js.map +1 -0
- package/dist/vite/plugin.mjs +680 -47
- package/dist/vite/plugin.mjs.map +1 -0
- package/llms.txt +250 -0
- package/package.json +15 -6
- package/src/astro/client.ts +13 -2
- package/src/astro/index.d.ts +24 -1
- package/src/astro/index.ts +40 -9
- package/src/astro/server.ts +3 -1
- package/src/collection.ts +5 -2
- package/src/component.ts +375 -330
- package/src/cycle/dom/MainDOMSource.ts +4 -0
- package/src/cycle/dom/classNameModule.ts +74 -0
- package/src/cycle/dom/controlledInputModule.ts +33 -0
- package/src/cycle/dom/enrichEventStream.ts +9 -3
- package/src/cycle/dom/makeDOMDriver.ts +1 -2
- package/src/cycle/dom/mockDOMSource.ts +62 -8
- package/src/cycle/dom/modules.ts +5 -1
- package/src/cycle/state/Collection.ts +13 -3
- package/src/cycle/state/StateSource.ts +1 -1
- package/src/cycle/state/pickCombine.ts +35 -1
- package/src/cycle/state/withState.ts +1 -1
- package/src/extra/devtools.ts +8 -0
- package/src/extra/diagnostics/checks/collections.ts +67 -0
- package/src/extra/diagnostics/checks/dom.ts +241 -0
- package/src/extra/diagnostics/checks/events.ts +68 -0
- package/src/extra/diagnostics/checks/index.ts +109 -0
- package/src/extra/diagnostics/checks/inspect.ts +319 -0
- package/src/extra/diagnostics/checks/props.ts +51 -0
- package/src/extra/diagnostics/checks/public.d.ts +211 -0
- package/src/extra/diagnostics/checks/rxjsHints.ts +88 -0
- package/src/extra/diagnostics/checks/shared.ts +119 -0
- package/src/extra/diagnostics/checks/state.ts +55 -0
- package/src/extra/diagnostics/checks/strict.ts +112 -0
- package/src/extra/diagnostics/checks/wiring.ts +65 -0
- package/src/extra/diagnostics/codes.ts +232 -0
- package/src/extra/diagnostics/index.ts +328 -0
- package/src/extra/diagnostics/legacy.ts +59 -0
- package/src/extra/driverFactories.ts +106 -58
- package/src/extra/eventDriver.ts +4 -0
- package/src/extra/flatten.ts +75 -0
- package/src/extra/reducers.ts +12 -5
- package/src/extra/run.ts +11 -0
- package/src/extra/testing.ts +890 -180
- package/src/extra/xstreamExtras.ts +269 -0
- package/src/index.d.ts +428 -17
- package/src/index.ts +7 -6
- package/src/pragma/index.ts +14 -9
- package/src/switchable.ts +10 -8
- package/src/vike/types.ts +21 -4
- package/src/vite/plugin.d.ts +95 -2
- package/src/vite/plugin.ts +730 -55
- package/dist/astro/astro/client.d.ts +0 -5
- package/dist/astro/astro/index.d.ts +0 -16
- package/dist/astro/astro/server.d.ts +0 -12
- package/dist/astro/client.d.ts +0 -5
- package/dist/astro/client.esm.js +0 -4665
- package/dist/astro/collection.d.ts +0 -12
- package/dist/astro/component.d.ts +0 -22
- package/dist/astro/cycle/dom/BodyDOMSource.d.ts +0 -10
- package/dist/astro/cycle/dom/DOMSource.d.ts +0 -11
- package/dist/astro/cycle/dom/DocumentDOMSource.d.ts +0 -12
- package/dist/astro/cycle/dom/ElementFinder.d.ts +0 -8
- package/dist/astro/cycle/dom/EventDelegator.d.ts +0 -34
- package/dist/astro/cycle/dom/IsolateModule.d.ts +0 -23
- package/dist/astro/cycle/dom/MainDOMSource.d.ts +0 -32
- package/dist/astro/cycle/dom/PriorityQueue.d.ts +0 -7
- package/dist/astro/cycle/dom/ScopeChecker.d.ts +0 -9
- package/dist/astro/cycle/dom/SymbolTree.d.ts +0 -9
- package/dist/astro/cycle/dom/VNodeWrapper.d.ts +0 -8
- package/dist/astro/cycle/dom/enrichEventStream.d.ts +0 -24
- package/dist/astro/cycle/dom/fromEvent.d.ts +0 -6
- package/dist/astro/cycle/dom/index.d.ts +0 -8
- package/dist/astro/cycle/dom/isolate.d.ts +0 -9
- package/dist/astro/cycle/dom/makeDOMDriver.d.ts +0 -11
- package/dist/astro/cycle/dom/mockDOMSource.d.ts +0 -17
- package/dist/astro/cycle/dom/modules.d.ts +0 -5
- package/dist/astro/cycle/dom/snabbdom.d.ts +0 -22
- package/dist/astro/cycle/dom/styleModule.d.ts +0 -13
- package/dist/astro/cycle/dom/thunk.d.ts +0 -11
- package/dist/astro/cycle/dom/utils.d.ts +0 -8
- package/dist/astro/cycle/isolate/index.d.ts +0 -42
- package/dist/astro/cycle/run/adapt.d.ts +0 -6
- package/dist/astro/cycle/run/index.d.ts +0 -36
- package/dist/astro/cycle/run/internals.d.ts +0 -8
- package/dist/astro/cycle/run/types.d.ts +0 -49
- package/dist/astro/cycle/state/Collection.d.ts +0 -29
- package/dist/astro/cycle/state/StateSource.d.ts +0 -20
- package/dist/astro/cycle/state/index.d.ts +0 -5
- package/dist/astro/cycle/state/pickCombine.d.ts +0 -3
- package/dist/astro/cycle/state/pickMerge.d.ts +0 -3
- package/dist/astro/cycle/state/types.d.ts +0 -18
- package/dist/astro/cycle/state/withState.d.ts +0 -17
- package/dist/astro/extra/classes.d.ts +0 -7
- package/dist/astro/extra/devtools.d.ts +0 -78
- package/dist/astro/extra/dragDriver.d.ts +0 -20
- package/dist/astro/extra/driverFactories.d.ts +0 -12
- package/dist/astro/extra/eventDriver.d.ts +0 -9
- package/dist/astro/extra/exactState.d.ts +0 -1
- package/dist/astro/extra/hmr.d.ts +0 -17
- package/dist/astro/extra/logDriver.d.ts +0 -2
- package/dist/astro/extra/processDrag.d.ts +0 -19
- package/dist/astro/extra/processForm.d.ts +0 -15
- package/dist/astro/extra/run.d.ts +0 -13
- package/dist/astro/extra/xstreamCompat.d.ts +0 -5
- package/dist/astro/index.d.ts +0 -19
- package/dist/astro/index.esm.js +0 -27
- package/dist/astro/jsx-dev-runtime.d.ts +0 -1
- package/dist/astro/jsx-runtime.d.ts +0 -4
- package/dist/astro/jsx.d.ts +0 -2
- package/dist/astro/pragma/fn.d.ts +0 -7
- package/dist/astro/pragma/index.d.ts +0 -7
- package/dist/astro/pragma/is.d.ts +0 -10
- package/dist/astro/server.d.ts +0 -12
- package/dist/astro/server.esm.js +0 -25
- package/dist/astro/switchable.d.ts +0 -7
- package/dist/collection.d.ts +0 -12
- package/dist/component.d.ts +0 -22
- package/dist/cycle/dom/BodyDOMSource.d.ts +0 -10
- package/dist/cycle/dom/DOMSource.d.ts +0 -11
- package/dist/cycle/dom/DocumentDOMSource.d.ts +0 -12
- package/dist/cycle/dom/ElementFinder.d.ts +0 -8
- package/dist/cycle/dom/EventDelegator.d.ts +0 -34
- package/dist/cycle/dom/IsolateModule.d.ts +0 -23
- package/dist/cycle/dom/MainDOMSource.d.ts +0 -32
- package/dist/cycle/dom/PriorityQueue.d.ts +0 -7
- package/dist/cycle/dom/ScopeChecker.d.ts +0 -9
- package/dist/cycle/dom/SymbolTree.d.ts +0 -9
- package/dist/cycle/dom/VNodeWrapper.d.ts +0 -8
- package/dist/cycle/dom/enrichEventStream.d.ts +0 -24
- package/dist/cycle/dom/fromEvent.d.ts +0 -6
- package/dist/cycle/dom/index.d.ts +0 -8
- package/dist/cycle/dom/isolate.d.ts +0 -9
- package/dist/cycle/dom/makeDOMDriver.d.ts +0 -11
- package/dist/cycle/dom/mockDOMSource.d.ts +0 -17
- package/dist/cycle/dom/modules.d.ts +0 -5
- package/dist/cycle/dom/snabbdom.d.ts +0 -22
- package/dist/cycle/dom/styleModule.d.ts +0 -13
- package/dist/cycle/dom/thunk.d.ts +0 -11
- package/dist/cycle/dom/utils.d.ts +0 -8
- package/dist/cycle/isolate/index.d.ts +0 -42
- package/dist/cycle/run/adapt.d.ts +0 -6
- package/dist/cycle/run/index.d.ts +0 -36
- package/dist/cycle/run/internals.d.ts +0 -8
- package/dist/cycle/run/types.d.ts +0 -49
- package/dist/cycle/state/Collection.d.ts +0 -29
- package/dist/cycle/state/StateSource.d.ts +0 -20
- package/dist/cycle/state/index.d.ts +0 -5
- package/dist/cycle/state/pickCombine.d.ts +0 -3
- package/dist/cycle/state/pickMerge.d.ts +0 -3
- package/dist/cycle/state/types.d.ts +0 -18
- package/dist/cycle/state/withState.d.ts +0 -17
- package/dist/extra/classes.d.ts +0 -7
- package/dist/extra/devtools.d.ts +0 -78
- package/dist/extra/dragDriver.d.ts +0 -20
- package/dist/extra/driverFactories.d.ts +0 -12
- package/dist/extra/eventDriver.d.ts +0 -9
- package/dist/extra/exactState.d.ts +0 -1
- package/dist/extra/hmr.d.ts +0 -17
- package/dist/extra/logDriver.d.ts +0 -2
- package/dist/extra/processDrag.d.ts +0 -19
- package/dist/extra/processForm.d.ts +0 -15
- package/dist/extra/run.d.ts +0 -13
- package/dist/extra/xstreamCompat.d.ts +0 -5
- package/dist/jsx-dev-runtime.d.ts +0 -1
- package/dist/jsx-dev-runtime.js +0 -224
- package/dist/jsx-runtime.d.ts +0 -4
- package/dist/jsx-runtime.js +0 -224
- package/dist/jsx.d.ts +0 -2
- package/dist/pragma/fn.d.ts +0 -7
- package/dist/pragma/index.d.ts +0 -7
- package/dist/pragma/is.d.ts +0 -10
- package/dist/switchable.d.ts +0 -7
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared plumbing for the runtime checks (PLAN-1 workstream 1A).
|
|
3
|
+
*
|
|
4
|
+
* The checks are bundled separately from the main 'sygnal' entry
|
|
5
|
+
* ('sygnal/diagnostics'). They must talk to the SAME diagnostics core
|
|
6
|
+
* instance the components use, so they never import the core's functions
|
|
7
|
+
* directly: `import '../index'` only makes sure the core is loaded (in the
|
|
8
|
+
* dist build that import is rewritten to the external 'sygnal' package, see
|
|
9
|
+
* rollup.config.mjs), and the functions are read from the bridge object the
|
|
10
|
+
* core publishes on globalThis.__SYGNAL_DIAGNOSTICS__.
|
|
11
|
+
*/
|
|
12
|
+
import '../index'
|
|
13
|
+
import type {DiagnosticCheck, DiagnosticDetails, Diagnostic} from '../index'
|
|
14
|
+
|
|
15
|
+
interface CoreBridge {
|
|
16
|
+
registerCheck(check: DiagnosticCheck): () => void
|
|
17
|
+
report(code: string, details: DiagnosticDetails): Diagnostic | undefined
|
|
18
|
+
[key: string]: any
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export const bridge = (): CoreBridge => (globalThis as any).__SYGNAL_DIAGNOSTICS__
|
|
22
|
+
|
|
23
|
+
export const report = (code: string, details: DiagnosticDetails): Diagnostic | undefined =>
|
|
24
|
+
bridge().report(code, details)
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* report() for code that is NOT running inside a core hook (timers, wrapped
|
|
28
|
+
* methods): never throws synchronously; an 'error'-mode DiagnosticError is
|
|
29
|
+
* rethrown asynchronously, like the core does for hooks.
|
|
30
|
+
*/
|
|
31
|
+
export const reportSafely = (code: string, details: DiagnosticDetails): Diagnostic | undefined => {
|
|
32
|
+
try {
|
|
33
|
+
return report(code, details)
|
|
34
|
+
} catch (err) {
|
|
35
|
+
const raise = () => { throw err }
|
|
36
|
+
typeof queueMicrotask === 'function' ? queueMicrotask(raise) : setTimeout(raise)
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** Tunables (test seam: configureChecks()). */
|
|
42
|
+
export const timing = {
|
|
43
|
+
/** delay after a render before DOM checks run (lets nested renders patch) */
|
|
44
|
+
settleMs: 50,
|
|
45
|
+
/** SYG103 escalates to warn after this much render-idle time ... */
|
|
46
|
+
idleMs: 2000,
|
|
47
|
+
/** ... and only once the component has rendered at least this often */
|
|
48
|
+
minRenders: 3,
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Built-in actions Sygnal dispatches itself; never "missing" or "unreachable". */
|
|
52
|
+
export const BUILTIN_ACTIONS = new Set(['BOOTSTRAP', 'INITIALIZE', 'HYDRATE', 'DISPOSE', 'READY'])
|
|
53
|
+
|
|
54
|
+
/** Synthetic/internal actions (`__TEST_ACTION__`, `__NOOP_ACTION__`, ...) and built-ins. */
|
|
55
|
+
export const isInternalAction = (action: string): boolean =>
|
|
56
|
+
typeof action !== 'string' || action.startsWith('__') || BUILTIN_ACTIONS.has(action)
|
|
57
|
+
|
|
58
|
+
let seen = new Set<string>()
|
|
59
|
+
|
|
60
|
+
/** true the first time `key` is seen (dedupes reports, e.g. per component name). */
|
|
61
|
+
export const once = (key: string): boolean => (seen.has(key) ? false : (seen.add(key), true))
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Forget only the once() dedupe set (G-051: renderComponent calls it through the core bridge
|
|
65
|
+
* when an outermost instance starts, so findings are reported again in every test). Unlike
|
|
66
|
+
* resetCheckState() it keeps the checks' registries, e.g. inspect()'s live records.
|
|
67
|
+
*/
|
|
68
|
+
export const resetOnce = (): void => { seen = new Set() }
|
|
69
|
+
{
|
|
70
|
+
const core = bridge()
|
|
71
|
+
if (core) core.resetOnce = resetOnce
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const resetters: Array<() => void> = []
|
|
75
|
+
export const onReset = (fn: () => void): void => { resetters.push(fn) }
|
|
76
|
+
|
|
77
|
+
/** Clear every check's dedupe/registry state (tests). */
|
|
78
|
+
export function resetCheckState(): void {
|
|
79
|
+
seen = new Set()
|
|
80
|
+
resetters.forEach(fn => fn())
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export const nameOf = (component: any): string => (component && component.name) || 'component'
|
|
84
|
+
|
|
85
|
+
export const isPlainObject = (value: any): boolean =>
|
|
86
|
+
!!value && typeof value === 'object' && !Array.isArray(value) &&
|
|
87
|
+
(Object.getPrototypeOf(value) === Object.prototype || Object.getPrototypeOf(value) === null)
|
|
88
|
+
|
|
89
|
+
/** Levenshtein distance (small strings only). */
|
|
90
|
+
function distance(a: string, b: string): number {
|
|
91
|
+
const row = Array.from({length: b.length + 1}, (_, i) => i)
|
|
92
|
+
for (let i = 1; i <= a.length; i++) {
|
|
93
|
+
let prev = row[0]
|
|
94
|
+
row[0] = i
|
|
95
|
+
for (let j = 1; j <= b.length; j++) {
|
|
96
|
+
const tmp = row[j]
|
|
97
|
+
row[j] = Math.min(row[j] + 1, row[j - 1] + 1, prev + (a[i - 1] === b[j - 1] ? 0 : 1))
|
|
98
|
+
prev = tmp
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return row[b.length]
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Closest candidate to `name` (case-insensitive match or edit distance <= 2), if any. */
|
|
105
|
+
export function suggest(name: string, candidates: Iterable<string>): string | undefined {
|
|
106
|
+
let best: string | undefined
|
|
107
|
+
let bestScore = Infinity
|
|
108
|
+
for (const c of candidates) {
|
|
109
|
+
if (c === name) continue
|
|
110
|
+
const score = c.toLowerCase() === name.toLowerCase() ? 0 : distance(c, name)
|
|
111
|
+
if (score < bestScore) { best = c; bestScore = score }
|
|
112
|
+
}
|
|
113
|
+
return bestScore <= Math.min(2, Math.floor(name.length / 3)) ? best : undefined
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export const didYouMean = (name: string, candidates: Iterable<string>): string => {
|
|
117
|
+
const s = suggest(name, candidates)
|
|
118
|
+
return s ? ` (did you mean '${s}'?)` : ''
|
|
119
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG201 — STATE reducer dropped keys that existed in the previous state (warn)
|
|
3
|
+
* SYG202 — STATE reducer returned undefined (warn)
|
|
4
|
+
*
|
|
5
|
+
* Mechanism: onReducer(component, action, prevState, nextState). Reported once
|
|
6
|
+
* per action per component name. Not reported for:
|
|
7
|
+
* - synthetic actions (`__*`) and INITIALIZE/HYDRATE (whole-state replacement)
|
|
8
|
+
* - keys that are calculated fields (they are re-derived after the reducer)
|
|
9
|
+
* - keys whose previous value was undefined
|
|
10
|
+
* The set()/toggle() helpers always spread the previous state, so they can't
|
|
11
|
+
* drop keys and never trigger SYG201.
|
|
12
|
+
*/
|
|
13
|
+
import type {DiagnosticCheck} from '../index'
|
|
14
|
+
import {report, once, nameOf, isPlainObject} from './shared'
|
|
15
|
+
|
|
16
|
+
const SKIP = new Set(['INITIALIZE', 'HYDRATE'])
|
|
17
|
+
|
|
18
|
+
export const stateCheck: DiagnosticCheck = {
|
|
19
|
+
id: 'state',
|
|
20
|
+
|
|
21
|
+
onReducer(component, action, prevState, nextState) {
|
|
22
|
+
if (typeof action !== 'string' || action.startsWith('__') || SKIP.has(action)) return
|
|
23
|
+
const name = nameOf(component)
|
|
24
|
+
|
|
25
|
+
if (nextState === undefined) {
|
|
26
|
+
if (!once(`SYG202:${name}:${action}`)) return
|
|
27
|
+
// Returning undefined from a Collection item's reducer is the documented way to remove
|
|
28
|
+
// the item, so only the root (where it wipes the whole app state) is a warning.
|
|
29
|
+
const sub = !!component?.isSubComponent
|
|
30
|
+
report('SYG202', {
|
|
31
|
+
component,
|
|
32
|
+
severity: sub ? 'info' : 'warn',
|
|
33
|
+
message: `The STATE reducer for '${action}' returned undefined` +
|
|
34
|
+
(sub ? ' (this removes the item if the component is a Collection item)' : ''),
|
|
35
|
+
fix: `Return the new state, e.g. (state, data) => ({ ...state, ... }), or return ABORT to leave the state unchanged`,
|
|
36
|
+
data: {action},
|
|
37
|
+
})
|
|
38
|
+
return
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
if (!isPlainObject(prevState) || !isPlainObject(nextState)) return
|
|
42
|
+
const calculated: Set<string> | null = component?._calculatedFieldNames || null
|
|
43
|
+
const dropped = Object.keys(prevState).filter(key =>
|
|
44
|
+
!(key in nextState) && prevState[key] !== undefined && !(calculated && calculated.has(key)))
|
|
45
|
+
if (dropped.length === 0 || !once(`SYG201:${name}:${action}`)) return
|
|
46
|
+
|
|
47
|
+
const list = dropped.map(k => `'${k}'`).join(', ')
|
|
48
|
+
report('SYG201', {
|
|
49
|
+
component,
|
|
50
|
+
message: `The STATE reducer for '${action}' returned a state without ${list}, which ${dropped.length === 1 ? 'was' : 'were'} in the previous state (likely a missing ...state spread)`,
|
|
51
|
+
fix: `Spread the previous state: (state, data) => ({ ...state, ... }). If removing ${dropped.length === 1 ? 'the key' : 'these keys'} is intended, ignore this`,
|
|
52
|
+
data: {action, droppedKeys: dropped},
|
|
53
|
+
})
|
|
54
|
+
},
|
|
55
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strict mode: canonical-form checks (PLAN-1 workstream 2A,
|
|
3
|
+
* dev-plans/PLAN-1-canonical-forms.md). Runtime half; sygnal-check --strict
|
|
4
|
+
* implements every rule statically.
|
|
5
|
+
*
|
|
6
|
+
* Off by default. Turn it on with any of:
|
|
7
|
+
* - configureStrict(true) (this entry)
|
|
8
|
+
* - renderComponent(C, { strict: true }) (tests)
|
|
9
|
+
* - globalThis.__SYGNAL_STRICT__ = true (e.g. injected by a dev tool)
|
|
10
|
+
* and diagnostics must be on (strict reports go through the same core).
|
|
11
|
+
* The flag lives on the core bridge object, so it is shared by every copy of
|
|
12
|
+
* this entry and by renderComponent.
|
|
13
|
+
*
|
|
14
|
+
* | Code | Rule (canonical form) | Runtime mechanism |
|
|
15
|
+
* |--------|------------------------------------------|-----------------------------------------|
|
|
16
|
+
* | SYG501 | C1 destructure the view's first argument | onModel: component.view.length > 1 |
|
|
17
|
+
* | SYG502 | C3 return ABORT for "no change" | onReducer: returned the previous state |
|
|
18
|
+
* | | | object (undefined stays SYG202) |
|
|
19
|
+
* | SYG504 | C5 object form, no 'ACTION | SINK' keys | onModel: raw model keys containing '|' |
|
|
20
|
+
* | SYG503, SYG505, SYG506, SYG507: static only (sygnal-check --strict); the
|
|
21
|
+
* runtime can't tell emit() from { EVENTS }, a side effect from a pure
|
|
22
|
+
* reducer, or see CHILD.select() arguments without a core hook.
|
|
23
|
+
*
|
|
24
|
+
* Every finding is reported once per component name (+ action / key).
|
|
25
|
+
* The SYG5xx severities are registered here (registerCodes) and passed
|
|
26
|
+
* explicitly, so the main bundle doesn't carry them (D29).
|
|
27
|
+
*/
|
|
28
|
+
import type {DiagnosticCheck} from '../index'
|
|
29
|
+
import {CODE_TITLES, STRICT_CODE_SEVERITY, registerCodes} from '../codes'
|
|
30
|
+
import type {DiagnosticSeverity} from '../codes'
|
|
31
|
+
import {bridge, report, once, nameOf, isInternalAction} from './shared'
|
|
32
|
+
|
|
33
|
+
registerCodes(Object.keys(STRICT_CODE_SEVERITY).map(code =>
|
|
34
|
+
[code, STRICT_CODE_SEVERITY[code], CODE_TITLES[code]] as [string, DiagnosticSeverity, string]))
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Turn runtime strict checks on (true), off (false), or back to the default
|
|
38
|
+
* (undefined → globalThis.__SYGNAL_STRICT__ === true).
|
|
39
|
+
*/
|
|
40
|
+
export function configureStrict(on?: boolean): void {
|
|
41
|
+
const core = bridge()
|
|
42
|
+
if (core) core.strict = on
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** Whether runtime strict checks are on. */
|
|
46
|
+
export function isStrictEnabled(): boolean {
|
|
47
|
+
const flag = bridge()?.strict
|
|
48
|
+
return flag === undefined ? (globalThis as any).__SYGNAL_STRICT__ === true : flag === true
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const strictReport = (code: string, details: Parameters<typeof report>[1]) =>
|
|
52
|
+
report(code, {severity: STRICT_CODE_SEVERITY[code], ...details})
|
|
53
|
+
|
|
54
|
+
export const strictCheck: DiagnosticCheck = {
|
|
55
|
+
id: 'strict',
|
|
56
|
+
|
|
57
|
+
onModel(component) {
|
|
58
|
+
if (!isStrictEnabled() || !component) return
|
|
59
|
+
const name = nameOf(component)
|
|
60
|
+
|
|
61
|
+
// SYG501 — view(props, state, context, peers): positional use of the 2nd+ args.
|
|
62
|
+
// (A default value or a rest parameter stops Function.length, so this can
|
|
63
|
+
// miss `(props, state = {})`, but it never flags a single-argument view.)
|
|
64
|
+
const view = component.view
|
|
65
|
+
if (typeof view === 'function' && view.length > 1 && !view.__sygnalLazy && once(`SYG501:${name}`)) {
|
|
66
|
+
const extra = ['state', 'context', 'peers'].slice(0, view.length - 1)
|
|
67
|
+
strictReport('SYG501', {
|
|
68
|
+
component,
|
|
69
|
+
message: `The view takes ${view.length} positional arguments (props, ${extra.join(', ')})`,
|
|
70
|
+
fix: `Destructure the first argument instead: function ${name}({ ${extra.join(', ')}, ...props })`,
|
|
71
|
+
data: {arity: view.length},
|
|
72
|
+
})
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// SYG504 — 'ACTION | SINK' shorthand keys
|
|
76
|
+
const model = component.model
|
|
77
|
+
if (!model || typeof model !== 'object') return
|
|
78
|
+
for (const key of Object.keys(model)) {
|
|
79
|
+
if (!key.includes('|')) continue
|
|
80
|
+
const [action, sink] = key.split('|').map(s => s.trim())
|
|
81
|
+
if (!action || !sink || !once(`SYG504:${name}:${key}`)) continue
|
|
82
|
+
const rewrite = sink === 'EVENTS'
|
|
83
|
+
? `${action}: { EVENTS: event('TYPE', (state, data) => payload) }`
|
|
84
|
+
: `${action}: { ${sink}: (state, data, next) => ... }`
|
|
85
|
+
strictReport('SYG504', {
|
|
86
|
+
component,
|
|
87
|
+
message: `Model key '${key}' uses the 'ACTION | SINK' shorthand`,
|
|
88
|
+
fix: `Use the object form: ${rewrite} (merge it into an existing '${action}' entry if there is one)`,
|
|
89
|
+
data: {key, action, sink},
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
|
|
94
|
+
onReducer(component, action, prevState, nextState) {
|
|
95
|
+
if (!isStrictEnabled() || isInternalAction(action)) return // incl. INITIALIZE/HYDRATE/DISPOSE
|
|
96
|
+
// undefined is SYG202 (state check, every mode); only the identical object here.
|
|
97
|
+
if (!prevState || typeof prevState !== 'object' || nextState === undefined) return
|
|
98
|
+
let same = nextState === prevState
|
|
99
|
+
if (!same && typeof component?.addCalculated === 'function') {
|
|
100
|
+
// with calculated fields the reducer saw addCalculated(prev) (memoized: same object)
|
|
101
|
+
try { same = component.addCalculated(prevState) === nextState } catch (_) {}
|
|
102
|
+
}
|
|
103
|
+
const name = nameOf(component)
|
|
104
|
+
if (!same || !once(`SYG502:${name}:${action}`)) return
|
|
105
|
+
strictReport('SYG502', {
|
|
106
|
+
component,
|
|
107
|
+
message: `The STATE reducer for '${action}' returned the unchanged state object`,
|
|
108
|
+
fix: `Return ABORT for "no change": ${action}: (state, data) => cond ? { ...state, ... } : ABORT`,
|
|
109
|
+
data: {action},
|
|
110
|
+
})
|
|
111
|
+
},
|
|
112
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SYG101 — intent action has no model entry (warn)
|
|
3
|
+
* SYG102 — model entry has no intent trigger (info; it may still be reached
|
|
4
|
+
* with next(), which is only known at call time)
|
|
5
|
+
*
|
|
6
|
+
* Mechanism: onIntent records the intent's action names per component;
|
|
7
|
+
* onModel (always called right after, once per instance) compares them with
|
|
8
|
+
* the normalized model map (shorthand already expanded by the core).
|
|
9
|
+
* Synthetic actions (`__*`, e.g. renderComponent's `__TEST_ACTION__`) and the
|
|
10
|
+
* built-ins BOOTSTRAP/INITIALIZE/HYDRATE/DISPOSE/READY are never reported.
|
|
11
|
+
* Under renderComponent, intent streams injected for model actions (so that
|
|
12
|
+
* simulateAction can dispatch them by their real names) are listed on the
|
|
13
|
+
* intent object's non-enumerable `__sygnalTestActions` property. They are not
|
|
14
|
+
* user intent actions (never reported as SYG101), and SYG102 is not reported
|
|
15
|
+
* for those model actions (the test dispatches them with simulateAction).
|
|
16
|
+
*/
|
|
17
|
+
import type {DiagnosticCheck} from '../index'
|
|
18
|
+
import {report, once, isInternalAction, nameOf, didYouMean} from './shared'
|
|
19
|
+
|
|
20
|
+
const intentActions = new WeakMap<object, string[]>()
|
|
21
|
+
|
|
22
|
+
export const wiringCheck: DiagnosticCheck = {
|
|
23
|
+
id: 'wiring',
|
|
24
|
+
|
|
25
|
+
onIntent(component, actionNames) {
|
|
26
|
+
intentActions.set(component, actionNames || [])
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
onModel(component, modelMap) {
|
|
30
|
+
const name = nameOf(component)
|
|
31
|
+
const intent$ = component && component.intent$
|
|
32
|
+
const injected = new Set<string>(
|
|
33
|
+
(intent$ && typeof intent$ === 'object' && intent$.__sygnalTestActions) || [])
|
|
34
|
+
const actions = (intentActions.get(component) || []).filter(a => !injected.has(a))
|
|
35
|
+
intentActions.delete(component)
|
|
36
|
+
const modelActions = Object.keys(modelMap || {})
|
|
37
|
+
|
|
38
|
+
for (const action of actions) {
|
|
39
|
+
if (isInternalAction(action) || modelActions.includes(action)) continue
|
|
40
|
+
if (!once(`SYG101:${name}:${action}`)) continue
|
|
41
|
+
report('SYG101', {
|
|
42
|
+
component,
|
|
43
|
+
message: `Intent action '${action}' has no model entry, so it never does anything${didYouMean(action, modelActions)}`,
|
|
44
|
+
fix: `Add '${action}' to ${name}.model, or remove it from ${name}.intent`,
|
|
45
|
+
data: {action, modelActions},
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// A single-stream intent (intent returns one action$ stream) has no
|
|
50
|
+
// action names to compare against.
|
|
51
|
+
if (intent$ && typeof intent$.addListener === 'function') return
|
|
52
|
+
|
|
53
|
+
const hmr = ([] as string[]).concat(component?.hmrActions || [])
|
|
54
|
+
for (const action of modelActions) {
|
|
55
|
+
if (isInternalAction(action) || actions.includes(action) || hmr.includes(action) || injected.has(action)) continue
|
|
56
|
+
if (!once(`SYG102:${name}:${action}`)) continue
|
|
57
|
+
report('SYG102', {
|
|
58
|
+
component,
|
|
59
|
+
message: `Model entry '${action}' has no intent action with that name${didYouMean(action, actions)}`,
|
|
60
|
+
fix: `Add '${action}' to ${name}.intent, or remove the model entry. If it is only dispatched with next('${action}'), ignore this`,
|
|
61
|
+
data: {action, intentActions: actions},
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
}
|
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sygnal diagnostic code registry.
|
|
3
|
+
*
|
|
4
|
+
* Every diagnostic Sygnal reports has a stable code (`SYG123`), a default
|
|
5
|
+
* severity, a short title, and a docs anchor (`docsSlug`) on
|
|
6
|
+
* https://sygnal.js.org/reference/errors (base URL = the `site` field of
|
|
7
|
+
* docs/astro.config.mjs).
|
|
8
|
+
*
|
|
9
|
+
* The registry is stored as two parallel tables so the published bundle only
|
|
10
|
+
* carries what the runtime needs:
|
|
11
|
+
* - CODE_SEVERITY: code → default severity (used by report(); in the bundle)
|
|
12
|
+
* - CODE_TITLES: code → title (docs/inspect/static-checker metadata; pure
|
|
13
|
+
* object literal, tree-shaken unless something imports it)
|
|
14
|
+
* getCodeInfo(code) / listCodes() assemble the full
|
|
15
|
+
* { code, severity, title, docsSlug } records. To add a code, add it to BOTH
|
|
16
|
+
* tables below (inside your reserved range), or call registerCodes() from a
|
|
17
|
+
* workstream's own module. docsSlug is always the lower-cased code
|
|
18
|
+
* ('SYG101' → https://sygnal.js.org/reference/errors#syg101).
|
|
19
|
+
*
|
|
20
|
+
* (Comments are attached to type-only declarations so the TypeScript emit
|
|
21
|
+
* drops them — keeps them out of the published bundle and the size gate.)
|
|
22
|
+
*
|
|
23
|
+
* Severity:
|
|
24
|
+
* - 'error' — throws (or is rethrown, enriched) at the call site
|
|
25
|
+
* - 'warn' — console + collected (when diagnostics mode is 'warn')
|
|
26
|
+
* - 'info' — collected only; surfaced through inspect / test helpers
|
|
27
|
+
*
|
|
28
|
+
* Ranges (see dev-plans/PLAN-1.md §2):
|
|
29
|
+
* SYG1xx Wiring: intent <-> model <-> view <-> events
|
|
30
|
+
* SYG2xx State & reducers
|
|
31
|
+
* SYG3xx Streams & intent construction (incl. xstream hints)
|
|
32
|
+
* SYG4xx Collections, Switchable, sub-components, context
|
|
33
|
+
* SYG5xx Strict mode / canonical-form violations
|
|
34
|
+
* SYG6xx Drivers & run configuration
|
|
35
|
+
* SYG9xx Internal invariants
|
|
36
|
+
*
|
|
37
|
+
* ---------------------------------------------------------------------------
|
|
38
|
+
* RESERVED RANGES (PLAN-1 workstream allocation — add entries only inside the
|
|
39
|
+
* range your workstream owns; changes outside go through the coordinator):
|
|
40
|
+
*
|
|
41
|
+
* 0B (diagnostics core) SYG900 (check-failure invariant)
|
|
42
|
+
* 1A (runtime checks) SYG101-105, SYG110, SYG201, SYG202, SYG301,
|
|
43
|
+
* SYG401 (pre-seeded below), plus SYG106-199
|
|
44
|
+
* and SYG302-399 for new checks
|
|
45
|
+
* 1E (message retrofit) SYG206-299, SYG402-499, SYG601-699, SYG901-999
|
|
46
|
+
* 2A (strict mode) SYG500-599
|
|
47
|
+
* unallocated SYG100, SYG200, SYG203-205, SYG300, SYG400,
|
|
48
|
+
* SYG600 (coordinator assigns on request)
|
|
49
|
+
* ---------------------------------------------------------------------------
|
|
50
|
+
*/
|
|
51
|
+
export type DiagnosticSeverity = 'error' | 'warn' | 'info'
|
|
52
|
+
|
|
53
|
+
export interface DiagnosticCodeInfo {
|
|
54
|
+
code: string
|
|
55
|
+
severity: DiagnosticSeverity
|
|
56
|
+
title: string
|
|
57
|
+
// anchor on the errors reference page, e.g. 'syg101' → /reference/errors#syg101
|
|
58
|
+
docsSlug: string
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export const DOCS_BASE_URL = 'https://sygnal.js.org'
|
|
62
|
+
|
|
63
|
+
export const CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
64
|
+
SYG101: 'warn',
|
|
65
|
+
SYG102: 'info',
|
|
66
|
+
SYG103: 'info',
|
|
67
|
+
SYG104: 'warn',
|
|
68
|
+
SYG105: 'info',
|
|
69
|
+
SYG106: 'warn',
|
|
70
|
+
SYG110: 'warn',
|
|
71
|
+
SYG111: 'warn',
|
|
72
|
+
SYG201: 'warn',
|
|
73
|
+
SYG202: 'warn',
|
|
74
|
+
SYG301: 'error',
|
|
75
|
+
SYG401: 'warn',
|
|
76
|
+
SYG900: 'warn',
|
|
77
|
+
// ---- 1E retrofit codes (SYG206-299, 402-499, 601-699, 901-999) ----
|
|
78
|
+
SYG206: 'error',
|
|
79
|
+
SYG207: 'warn',
|
|
80
|
+
SYG208: 'warn',
|
|
81
|
+
SYG209: 'error',
|
|
82
|
+
SYG210: 'warn',
|
|
83
|
+
SYG211: 'error',
|
|
84
|
+
SYG212: 'error',
|
|
85
|
+
SYG213: 'warn',
|
|
86
|
+
SYG214: 'error',
|
|
87
|
+
SYG215: 'error',
|
|
88
|
+
SYG216: 'error',
|
|
89
|
+
SYG217: 'warn',
|
|
90
|
+
SYG218: 'error',
|
|
91
|
+
SYG219: 'warn',
|
|
92
|
+
SYG220: 'warn',
|
|
93
|
+
SYG402: 'error',
|
|
94
|
+
SYG403: 'error',
|
|
95
|
+
SYG404: 'error',
|
|
96
|
+
SYG405: 'warn',
|
|
97
|
+
SYG406: 'error',
|
|
98
|
+
SYG407: 'error',
|
|
99
|
+
SYG408: 'error',
|
|
100
|
+
SYG409: 'warn',
|
|
101
|
+
SYG410: 'error',
|
|
102
|
+
SYG411: 'error',
|
|
103
|
+
SYG412: 'error',
|
|
104
|
+
SYG413: 'error',
|
|
105
|
+
SYG414: 'error',
|
|
106
|
+
SYG415: 'error',
|
|
107
|
+
SYG416: 'error',
|
|
108
|
+
SYG417: 'warn',
|
|
109
|
+
SYG418: 'error',
|
|
110
|
+
SYG419: 'error',
|
|
111
|
+
SYG420: 'error',
|
|
112
|
+
SYG601: 'error',
|
|
113
|
+
SYG602: 'error',
|
|
114
|
+
SYG603: 'error',
|
|
115
|
+
SYG604: 'error',
|
|
116
|
+
SYG605: 'error',
|
|
117
|
+
SYG606: 'error',
|
|
118
|
+
SYG607: 'error',
|
|
119
|
+
SYG901: 'error',
|
|
120
|
+
SYG902: 'error',
|
|
121
|
+
SYG903: 'error',
|
|
122
|
+
// ---- end 1E retrofit codes ----
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const CODE_TITLES: Record<string, string> = {
|
|
126
|
+
// SYG1xx wiring (1A)
|
|
127
|
+
SYG101: 'Intent action has no model entry',
|
|
128
|
+
SYG102: 'Model entry is unreachable',
|
|
129
|
+
SYG103: 'Intent selector matched no rendered element',
|
|
130
|
+
SYG104: 'Intent selector crosses an isolation boundary',
|
|
131
|
+
SYG105: 'EVENTS type selected but never emitted, or emitted but never selected',
|
|
132
|
+
SYG106: 'Parent prop is overwritten by a reserved view argument',
|
|
133
|
+
SYG110: 'Intent selector not present in the component view',
|
|
134
|
+
SYG111: 'Controlled input has no input listener',
|
|
135
|
+
// SYG2xx state & reducers (1A)
|
|
136
|
+
SYG201: 'STATE reducer dropped keys from the previous state',
|
|
137
|
+
SYG202: 'STATE reducer returned undefined',
|
|
138
|
+
// SYG3xx streams & intent construction (1A)
|
|
139
|
+
SYG301: 'RxJS operator used on an xstream stream',
|
|
140
|
+
// SYG4xx collections / switchable / sub-components / context (1A)
|
|
141
|
+
SYG401: "Collection 'from' field is missing or not an array",
|
|
142
|
+
// SYG5xx strict mode / canonical forms (2A; dev-plans/PLAN-1-canonical-forms.md)
|
|
143
|
+
SYG501: 'View uses positional arguments',
|
|
144
|
+
SYG502: 'STATE reducer signals no change without ABORT',
|
|
145
|
+
SYG503: 'Side effect in a STATE reducer that returns ABORT',
|
|
146
|
+
SYG504: "'ACTION | SINK' shorthand model key",
|
|
147
|
+
SYG505: 'Non-canonical EVENTS emit',
|
|
148
|
+
SYG506: 'CHILD.select() with a string component name',
|
|
149
|
+
SYG507: 'Prop drilled through more than 2 component levels',
|
|
150
|
+
// SYG9xx internal (0B)
|
|
151
|
+
SYG900: 'A diagnostics check threw',
|
|
152
|
+
// ---- 1E retrofit codes (SYG206-299, 402-499, 601-699, 901-999) ----
|
|
153
|
+
SYG206: 'Invalid calculated field definition',
|
|
154
|
+
SYG207: 'Calculated field shadows an initialState key',
|
|
155
|
+
SYG208: 'Calculated field depends on an unknown key',
|
|
156
|
+
SYG209: 'Circular calculated field dependency',
|
|
157
|
+
SYG210: 'INITIALIZE used with a sink other than STATE',
|
|
158
|
+
SYG211: 'Invalid model shorthand entry',
|
|
159
|
+
SYG212: 'Model entry is not a function or an object of sinks',
|
|
160
|
+
SYG213: 'Duplicate model entry for the same action and sink',
|
|
161
|
+
SYG214: 'EFFECT handler threw',
|
|
162
|
+
SYG215: 'next() delay is not a number',
|
|
163
|
+
SYG216: 'Reducer threw',
|
|
164
|
+
SYG217: 'Driver-sink reducer returned undefined',
|
|
165
|
+
SYG218: 'Reducer returned an unsupported type',
|
|
166
|
+
SYG219: 'EFFECT handler returned a value',
|
|
167
|
+
SYG220: 'Calculated field threw',
|
|
168
|
+
SYG402: 'Context is not an object',
|
|
169
|
+
SYG403: 'Invalid context entry',
|
|
170
|
+
SYG404: 'Context stream errored',
|
|
171
|
+
SYG405: 'Sub-component has initialState without isolatedState',
|
|
172
|
+
SYG406: 'View threw',
|
|
173
|
+
SYG407: 'onError handler threw',
|
|
174
|
+
SYG408: 'Sub-component failed to instantiate',
|
|
175
|
+
SYG409: 'Child tried to update a calculated field',
|
|
176
|
+
SYG410: "Invalid sub-component 'state' prop",
|
|
177
|
+
SYG411: "Collection 'of' is missing or not a component",
|
|
178
|
+
SYG412: "Invalid Collection 'from' prop",
|
|
179
|
+
SYG413: 'Unnamed component factory not found',
|
|
180
|
+
SYG414: 'Component not found',
|
|
181
|
+
SYG415: "Switchable 'of' is missing or invalid",
|
|
182
|
+
SYG416: "Switchable 'current' is missing or invalid",
|
|
183
|
+
SYG417: 'Portal target not found',
|
|
184
|
+
SYG418: 'Invalid Collection sort prop',
|
|
185
|
+
SYG419: "Invalid switchable() 'name$' parameter",
|
|
186
|
+
SYG420: 'JSX tag is undefined',
|
|
187
|
+
SYG601: 'Missing or invalid sources',
|
|
188
|
+
SYG602: 'Intent is not a function',
|
|
189
|
+
SYG603: 'Intent returned an invalid value',
|
|
190
|
+
SYG604: 'Invalid hmrActions',
|
|
191
|
+
SYG605: "Intent action name contains '|'",
|
|
192
|
+
SYG606: 'calculated is not an object',
|
|
193
|
+
SYG607: 'State source not found',
|
|
194
|
+
SYG901: 'Sub-component sink stream errored',
|
|
195
|
+
SYG902: 'EFFECT stream errored',
|
|
196
|
+
SYG903: 'Component factory returned invalid sinks',
|
|
197
|
+
// ---- end 1E retrofit codes ----
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 2A: strict-mode (SYG5xx) default severities. Kept OUT of CODE_SEVERITY so the
|
|
201
|
+
// main bundle doesn't carry them (D29: zero bytes); the 'sygnal/diagnostics'
|
|
202
|
+
// entry registers them (registerCodes) and its reports pass the severity
|
|
203
|
+
// explicitly. sygnal-check keeps the same table in sygnal-check/src/codes.js.
|
|
204
|
+
export const STRICT_CODE_SEVERITY: Record<string, DiagnosticSeverity> = {
|
|
205
|
+
SYG501: 'warn',
|
|
206
|
+
SYG502: 'warn',
|
|
207
|
+
SYG503: 'warn',
|
|
208
|
+
SYG504: 'warn',
|
|
209
|
+
SYG505: 'warn',
|
|
210
|
+
SYG506: 'warn',
|
|
211
|
+
SYG507: 'info',
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export function getCodeInfo(code: string): DiagnosticCodeInfo | undefined {
|
|
215
|
+
const severity = CODE_SEVERITY[code]
|
|
216
|
+
return severity && { code, severity, title: CODE_TITLES[code] || '', docsSlug: code.toLowerCase() }
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export function listCodes(): DiagnosticCodeInfo[] {
|
|
220
|
+
return Object.keys(CODE_SEVERITY).map(c => getCodeInfo(c)!)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
export function registerCodes(entries: Array<[string, DiagnosticSeverity, string]>): void {
|
|
224
|
+
for (const [code, severity, title] of entries) {
|
|
225
|
+
CODE_SEVERITY[code] = severity
|
|
226
|
+
CODE_TITLES[code] = title
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
export function docsUrlFor(code: string): string {
|
|
231
|
+
return `${DOCS_BASE_URL}/reference/errors#${code.toLowerCase()}`
|
|
232
|
+
}
|