@vizejs/plugin-sdk 0.429.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 +224 -0
- package/facts.d.ts +348 -0
- package/fixes.d.ts +9 -0
- package/fixes.js +42 -0
- package/identity.js +10 -0
- package/index.d.ts +75 -0
- package/index.js +101 -0
- package/package.json +69 -0
- package/sandbox-worker.js +64 -0
- package/sandbox.d.ts +55 -0
- package/sandbox.js +328 -0
- package/stages.d.ts +114 -0
- package/stages.js +71 -0
package/README.md
ADDED
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
# @vizejs/plugin-sdk
|
|
2
|
+
|
|
3
|
+
Author synchronous rules, fact providers, native template transforms and compiled
|
|
4
|
+
output hooks. Install `@vizejs/native` **0.429.0 or newer** to execute plugins.
|
|
5
|
+
The optional native peer lets this package install independently for authoring;
|
|
6
|
+
its helpers alone do not compile or lint Vue files. Node 22 or newer is required.
|
|
7
|
+
|
|
8
|
+
## Lint rules and autofixes
|
|
9
|
+
|
|
10
|
+
```js
|
|
11
|
+
import * as native from "@vizejs/native";
|
|
12
|
+
import { definePlugin, applyFixes } from "@vizejs/plugin-sdk";
|
|
13
|
+
|
|
14
|
+
const rule = definePlugin({
|
|
15
|
+
name: "team-rule",
|
|
16
|
+
version: "1.0.0",
|
|
17
|
+
visit: ["ui.bind"],
|
|
18
|
+
demands: [],
|
|
19
|
+
cacheInputs: [],
|
|
20
|
+
rules: {
|
|
21
|
+
check(ctx) {
|
|
22
|
+
for (const node of ctx.nodes) {
|
|
23
|
+
if (node.name === "disabled" && node.value === "true") {
|
|
24
|
+
ctx.report(node, "Use a literal boolean attribute.", "disabled");
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
});
|
|
30
|
+
const output = native.lintWithPlugins(source, [rule], {
|
|
31
|
+
filename: "Button.vue",
|
|
32
|
+
cache: true,
|
|
33
|
+
validateDeterminism: true,
|
|
34
|
+
});
|
|
35
|
+
const fixed = applyFixes(source, output.fixes);
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
A report names a visited L2 node. Rust assigns its authored UTF-8 byte range;
|
|
39
|
+
a fix replaces that whole range. `applyFixes` preserves other source,
|
|
40
|
+
deduplicates identical suggestions and refuses conflicting edits atomically.
|
|
41
|
+
Apply suggestions deliberately, then rerun the compiler and linter.
|
|
42
|
+
|
|
43
|
+
Rules receive immutable batches. `ctx.facts(name)` requires the group in the
|
|
44
|
+
static `demands` list; no undeclared table crosses the boundary. Native groups
|
|
45
|
+
include `templateScopes` and the registered Croquis producers `bindings`,
|
|
46
|
+
`undefined-refs`, `component-usages`, `reactivity`, `provide-inject` and
|
|
47
|
+
`race-conditions` and `unused-bindings`. The latter computes its registered
|
|
48
|
+
`bindings` dependency. The `bindings` and `unused-bindings` spans are authored
|
|
49
|
+
SFC UTF-8 byte ranges, mapped by the shared producer even for reordered split
|
|
50
|
+
scripts. Other primary position fields retain their Croquis analysis frame:
|
|
51
|
+
undefined references/component usage are template-relative; reactivity,
|
|
52
|
+
provide/inject and race positions are script-relative (the merged normal/setup
|
|
53
|
+
script when both exist). Reports and fixes always use the host's SFC ranges. Public component interface pages are `component-signature`,
|
|
54
|
+
`prop-types`, `emit-types`, `slot-types`, `reactivity-classes` and
|
|
55
|
+
`component-references`. Each interface value preserves the producer's schema,
|
|
56
|
+
nullable unknowns and type dependency completeness. The signature's
|
|
57
|
+
`props_complete` separately records whether all property names were enumerated.
|
|
58
|
+
The source-only SDK route has no resolved catalog proof, so it reports false
|
|
59
|
+
without reading imported files from disk. Imported types which this
|
|
60
|
+
analysis cannot resolve remain explicit unknowns. Primary Croquis facts require
|
|
61
|
+
a JavaScript or TypeScript script; other script dialects are refused explicitly.
|
|
62
|
+
|
|
63
|
+
Plugin visits require inline HTML templates. Authored foreign template dialects
|
|
64
|
+
and external template blocks are refused before callbacks; the SDK does not
|
|
65
|
+
pretend their source is HTML or invent source maps for a preprocessing pass.
|
|
66
|
+
|
|
67
|
+
TypeScript infers each native group's exact map keys and deeply readonly values
|
|
68
|
+
from `ctx.facts("prop-types")`, including authored interface order and all emit
|
|
69
|
+
overloads. The package exports `FactGroups`, `FactKey`, `FactValue`, `FactEntry`
|
|
70
|
+
and the individual native contracts. Custom provider groups can use
|
|
71
|
+
`ctx.facts<MyFact>("design/labels")`; unknown groups default to `unknown`.
|
|
72
|
+
|
|
73
|
+
## Fact providers
|
|
74
|
+
|
|
75
|
+
```js
|
|
76
|
+
import { defineFactProvider, definePlugin } from "@vizejs/plugin-sdk";
|
|
77
|
+
const labels = defineFactProvider({
|
|
78
|
+
name: "design",
|
|
79
|
+
version: "1",
|
|
80
|
+
provides: ["design/labels"],
|
|
81
|
+
visit: ["ui.element"],
|
|
82
|
+
demands: ["bindings"],
|
|
83
|
+
cacheInputs: [],
|
|
84
|
+
provide(batch) {
|
|
85
|
+
return { "design/labels": batch.nodes.map((node) => [node.id, node.name]) };
|
|
86
|
+
},
|
|
87
|
+
});
|
|
88
|
+
const consumer = definePlugin({
|
|
89
|
+
name: "labels",
|
|
90
|
+
version: "1",
|
|
91
|
+
demands: ["design/labels"],
|
|
92
|
+
cacheInputs: [],
|
|
93
|
+
rules: {
|
|
94
|
+
check(ctx) {
|
|
95
|
+
const labels = ctx.facts("design/labels");
|
|
96
|
+
},
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
native.lintWithPlugins(source, [consumer], { cache: true, factProviders: [labels] });
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
A provider declares every output group as `provider-name/group`, its required
|
|
103
|
+
facts and node visits. The host computes the dependency closure and refuses
|
|
104
|
+
cycles, collisions, undeclared output and malformed tables before using facts.
|
|
105
|
+
Each table contains `[stringOrNumericKey, jsonValue]` rows with unique keys.
|
|
106
|
+
Provider dependency stamps enter the consumer cache key automatically.
|
|
107
|
+
|
|
108
|
+
## Native template transforms
|
|
109
|
+
|
|
110
|
+
```js
|
|
111
|
+
import { defineTransformPlugin } from "@vizejs/plugin-sdk";
|
|
112
|
+
const design = defineTransformPlugin({
|
|
113
|
+
name: "design-system",
|
|
114
|
+
version: "1",
|
|
115
|
+
cacheInputs: [],
|
|
116
|
+
transform(batch) {
|
|
117
|
+
return batch.nodes
|
|
118
|
+
.filter((node) =>
|
|
119
|
+
node.attrs.some((attr) => attr.name === "class" && attr.value === "legacy-btn"),
|
|
120
|
+
)
|
|
121
|
+
.map((node) => ({
|
|
122
|
+
kind: "replace-static-attribute",
|
|
123
|
+
node: node.id,
|
|
124
|
+
name: "class",
|
|
125
|
+
value: "ds-button",
|
|
126
|
+
}));
|
|
127
|
+
},
|
|
128
|
+
});
|
|
129
|
+
const transformed = native.compileWithTransformPlugins(template, [design], {
|
|
130
|
+
filename: "Button.vue",
|
|
131
|
+
sourceMap: true,
|
|
132
|
+
cache: true,
|
|
133
|
+
cacheDir: ".vize-cache",
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
The hook runs at the native pre-canonical L2 point. It can replace existing
|
|
138
|
+
static HTML attributes from the host's allowed set; dynamic attributes,
|
|
139
|
+
structural edits, template carriers and foreign namespaces refuse. Values are
|
|
140
|
+
semantic text, not entity spelling. Unknown targets or duplicate edits refuse
|
|
141
|
+
the entire response. The compiler applies typed edits to its real arena and
|
|
142
|
+
emits code and authored maps; no guest AST JSON is accepted. The result is a
|
|
143
|
+
module with prefixed identifiers. `plugins` contains per-plugin costs.
|
|
144
|
+
|
|
145
|
+
## Formatter and output hooks
|
|
146
|
+
|
|
147
|
+
```js
|
|
148
|
+
import { defineOutputPlugin } from "@vizejs/plugin-sdk";
|
|
149
|
+
const license = defineOutputPlugin({
|
|
150
|
+
name: "license",
|
|
151
|
+
version: "1",
|
|
152
|
+
family: "output",
|
|
153
|
+
cacheInputs: [],
|
|
154
|
+
output() {
|
|
155
|
+
return [{ placement: "prepend", comment: "Copyright Example" }];
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
const compiled = native.compileWithOutputPlugins(template, [license], options);
|
|
159
|
+
const decorated = native.applyOutputPlugins(transformed.result, [license], { cache: true });
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
`family: "formatter"` returns `{start,end,text}` edits using UTF-8 byte offsets.
|
|
163
|
+
Only whitespace formatting which preserves the parsed program and comments is
|
|
164
|
+
accepted. `family: "output"` appends or prepends ordinary block comments;
|
|
165
|
+
source-map directives and executable additions refuse. The host rebases existing
|
|
166
|
+
map anchors and preserves source content. Each pipeline key includes preceding
|
|
167
|
+
output. Function-mode results carry `preamble` separately from `code`.
|
|
168
|
+
|
|
169
|
+
## Determinism, caches and costs
|
|
170
|
+
|
|
171
|
+
Declare every captured configuration or ambient input in `cacheInputs`, even
|
|
172
|
+
when the list is empty. Names beginning `@vize/` are host-reserved. Source,
|
|
173
|
+
filename, native build, SDK runtime bytes, plugin version/code, static demands,
|
|
174
|
+
visits and inputs participate in keys. A cached lint miss always audits two
|
|
175
|
+
identical batches. Providers and transforms audit misses. Cached output hooks also always audit;
|
|
176
|
+
uncached output hooks audit by default. Different validated outputs refuse before caching. All hook families
|
|
177
|
+
have bounded in-process caches and opt-in `cacheDir` persistence. Disk hits
|
|
178
|
+
validate their schema, keys and host-confined outputs before reuse.
|
|
179
|
+
|
|
180
|
+
Costs include elapsed host work and JavaScript call time, including audit calls.
|
|
181
|
+
A cache hit executes no callback; its `jsNs` and batch crossing bytes are zero.
|
|
182
|
+
A determinism audit observes repeated output. Trusted callbacks can conceal
|
|
183
|
+
ambient reads; their declarations remain the author's responsibility.
|
|
184
|
+
|
|
185
|
+
## Isolated callback sources
|
|
186
|
+
|
|
187
|
+
```js
|
|
188
|
+
import {
|
|
189
|
+
createSandboxRunner,
|
|
190
|
+
SANDBOX_IMAGE,
|
|
191
|
+
SandboxRuntimeError,
|
|
192
|
+
} from "@vizejs/plugin-sdk/sandbox";
|
|
193
|
+
const isolated = createSandboxRunner(
|
|
194
|
+
{
|
|
195
|
+
name: "isolated-rule",
|
|
196
|
+
version: "1",
|
|
197
|
+
cacheInputs: [],
|
|
198
|
+
configuration: { message: "Use our design system" },
|
|
199
|
+
rules: { check: "(ctx, config) => { ctx.report(ctx.nodes[0], config.message); }" },
|
|
200
|
+
},
|
|
201
|
+
{ timeoutMs: 5000, maxBytes: 1048576 },
|
|
202
|
+
);
|
|
203
|
+
native.lintWithPlugins(source, [isolated], { cache: true });
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Install Docker, start its daemon and pull the exported pinned `SANDBOX_IMAGE`
|
|
207
|
+
before use. Callback source and explicit JSON configuration cross stdin; host
|
|
208
|
+
closures and imports do not. Each invocation uses a nonroot, read-only container
|
|
209
|
+
without host mounts, host environment, external network or capabilities, with
|
|
210
|
+
memory/CPU/PID/file-descriptor limits. The host bounds request/output bytes and
|
|
211
|
+
execution time, then confirms container removal. Runtime checking and cleanup
|
|
212
|
+
each add at most five seconds to `timeoutMs`.
|
|
213
|
+
|
|
214
|
+
`SandboxRuntimeError.code` distinguishes `runtime_unavailable`,
|
|
215
|
+
`execution_stopped`, `execution_failed` and `cleanup_failed`. Unavailable Docker
|
|
216
|
+
or a missing image refuses execution; it never runs the guest as trusted code.
|
|
217
|
+
The image's own filesystem remains readable. Docker supplies the isolation;
|
|
218
|
+
this API does not claim virtual-machine isolation.
|
|
219
|
+
|
|
220
|
+
`definePlugin` and the other ordinary helpers execute trusted application code
|
|
221
|
+
synchronously in the calling Node process and have no execution deadline.
|
|
222
|
+
Freezing batches and auditing output do not isolate those callbacks. The measured
|
|
223
|
+
proxy handle remains for spike compatibility. `@vizejs/extension-sdk` is the
|
|
224
|
+
separate versioned WIT guest interface.
|
package/facts.d.ts
ADDED
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
export interface TemplateScopeEntry {
|
|
2
|
+
readonly name: string;
|
|
3
|
+
readonly position: "value" | "key" | "index" | "slot";
|
|
4
|
+
}
|
|
5
|
+
export type BindingKind =
|
|
6
|
+
| "setup-let"
|
|
7
|
+
| "setup-maybe-ref"
|
|
8
|
+
| "setup-ref"
|
|
9
|
+
| "setup-reactive-const"
|
|
10
|
+
| "setup-const"
|
|
11
|
+
| "props"
|
|
12
|
+
| "props-aliased"
|
|
13
|
+
| "data"
|
|
14
|
+
| "options"
|
|
15
|
+
| "literal-const"
|
|
16
|
+
| "js-global-universal"
|
|
17
|
+
| "js-global-browser"
|
|
18
|
+
| "js-global-node"
|
|
19
|
+
| "js-global-deno"
|
|
20
|
+
| "js-global-bun"
|
|
21
|
+
| "vue-global"
|
|
22
|
+
| "external-module";
|
|
23
|
+
export type BindingFact =
|
|
24
|
+
| Readonly<{ scriptSetup: true }>
|
|
25
|
+
| Readonly<{
|
|
26
|
+
kind: BindingKind | null;
|
|
27
|
+
span: readonly [number, number] | null;
|
|
28
|
+
propKey: string | null;
|
|
29
|
+
}>;
|
|
30
|
+
export interface UndefinedRefFact {
|
|
31
|
+
readonly name: string;
|
|
32
|
+
readonly offset: number;
|
|
33
|
+
readonly context: string;
|
|
34
|
+
}
|
|
35
|
+
export interface UnusedBindingFact {
|
|
36
|
+
readonly span: readonly [number, number];
|
|
37
|
+
}
|
|
38
|
+
export interface FactRange {
|
|
39
|
+
readonly start: number;
|
|
40
|
+
readonly end: number;
|
|
41
|
+
}
|
|
42
|
+
export interface ComponentPropUsage extends FactRange {
|
|
43
|
+
readonly name: string;
|
|
44
|
+
readonly nameIsDynamic: boolean;
|
|
45
|
+
readonly value: string | null;
|
|
46
|
+
readonly isDynamic: boolean;
|
|
47
|
+
}
|
|
48
|
+
export interface ComponentEventUsage extends FactRange {
|
|
49
|
+
readonly name: string;
|
|
50
|
+
readonly nameIsDynamic: boolean;
|
|
51
|
+
readonly handler: string | null;
|
|
52
|
+
readonly modifiers: readonly string[];
|
|
53
|
+
}
|
|
54
|
+
export interface ComponentSlotUsage extends FactRange {
|
|
55
|
+
readonly name: string;
|
|
56
|
+
readonly nameIsDynamic: boolean;
|
|
57
|
+
readonly scopeVars: readonly string[];
|
|
58
|
+
readonly hasScope: boolean;
|
|
59
|
+
}
|
|
60
|
+
export interface ComponentSpreadUsage extends FactRange {
|
|
61
|
+
readonly expression: string;
|
|
62
|
+
}
|
|
63
|
+
export interface ComponentUsage extends FactRange {
|
|
64
|
+
readonly name: string;
|
|
65
|
+
readonly props: readonly ComponentPropUsage[];
|
|
66
|
+
readonly events: readonly ComponentEventUsage[];
|
|
67
|
+
readonly slots: readonly ComponentSlotUsage[];
|
|
68
|
+
readonly hasSpreadAttrs: boolean;
|
|
69
|
+
readonly spreadProps: readonly ComponentSpreadUsage[];
|
|
70
|
+
readonly scopeId: number;
|
|
71
|
+
readonly vifGuard: string | null;
|
|
72
|
+
}
|
|
73
|
+
export interface ComponentUsageSite {
|
|
74
|
+
readonly tag: string;
|
|
75
|
+
readonly usage: ComponentUsage | null;
|
|
76
|
+
readonly usageIndex: number | null;
|
|
77
|
+
readonly nameOrdinal: number | null;
|
|
78
|
+
}
|
|
79
|
+
export interface ComponentUsagesFact {
|
|
80
|
+
readonly module: string | null;
|
|
81
|
+
readonly export: string;
|
|
82
|
+
readonly sites: readonly ComponentUsageSite[];
|
|
83
|
+
}
|
|
84
|
+
export type ReactivityClass = "static" | "props-stable" | "reactive" | "unstable";
|
|
85
|
+
export type Verdict = "proven" | "refuted" | "unknown";
|
|
86
|
+
export type ReactivityEffect =
|
|
87
|
+
| "freeze"
|
|
88
|
+
| "capture"
|
|
89
|
+
| "read-prop"
|
|
90
|
+
| "read-reactive"
|
|
91
|
+
| "mutate-local"
|
|
92
|
+
| "mutate-global"
|
|
93
|
+
| "call-unknown"
|
|
94
|
+
| "allocate";
|
|
95
|
+
export type ReactiveSourceKind =
|
|
96
|
+
| "ref"
|
|
97
|
+
| "shallow-ref"
|
|
98
|
+
| "reactive"
|
|
99
|
+
| "shallow-reactive"
|
|
100
|
+
| "computed"
|
|
101
|
+
| "readonly"
|
|
102
|
+
| "shallow-readonly"
|
|
103
|
+
| "to-ref"
|
|
104
|
+
| "to-refs";
|
|
105
|
+
export type ReactivityLoss =
|
|
106
|
+
| Readonly<{
|
|
107
|
+
kind: "reactive-destructure" | "ref-value-destructure";
|
|
108
|
+
sourceName: string;
|
|
109
|
+
destructuredProps: readonly string[];
|
|
110
|
+
}>
|
|
111
|
+
| Readonly<{ kind: "ref-value-extract"; sourceName: string; targetName: string }>
|
|
112
|
+
| Readonly<{
|
|
113
|
+
kind: "reactive-property-extract";
|
|
114
|
+
sourceName: string;
|
|
115
|
+
propName: string;
|
|
116
|
+
targetName: string;
|
|
117
|
+
}>
|
|
118
|
+
| Readonly<{ kind: "props-destructure"; destructuredProps: readonly string[] }>
|
|
119
|
+
| Readonly<{
|
|
120
|
+
kind: "function-argument-extract";
|
|
121
|
+
sourceName: string;
|
|
122
|
+
argumentName: string;
|
|
123
|
+
calleeName: string;
|
|
124
|
+
}>
|
|
125
|
+
| Readonly<{
|
|
126
|
+
kind: "getter-call-extract";
|
|
127
|
+
contextName: string;
|
|
128
|
+
getterName: string;
|
|
129
|
+
targetName: string;
|
|
130
|
+
calleeName: string;
|
|
131
|
+
sourceName: string;
|
|
132
|
+
}>
|
|
133
|
+
| Readonly<{
|
|
134
|
+
kind: "plain-value-alias";
|
|
135
|
+
sourceName: string;
|
|
136
|
+
aliasName: string;
|
|
137
|
+
targetName: string;
|
|
138
|
+
}>
|
|
139
|
+
| Readonly<{ kind: "reactive-spread" | "reactive-reassign"; sourceName: string }>;
|
|
140
|
+
export type ReactivityFact =
|
|
141
|
+
| Readonly<{
|
|
142
|
+
kind: "source";
|
|
143
|
+
name: string;
|
|
144
|
+
sourceKind: ReactiveSourceKind;
|
|
145
|
+
declarationOffset: number;
|
|
146
|
+
class: ReactivityClass;
|
|
147
|
+
verdict: Verdict;
|
|
148
|
+
effects: readonly ReactivityEffect[];
|
|
149
|
+
}>
|
|
150
|
+
| Readonly<{
|
|
151
|
+
kind: "loss";
|
|
152
|
+
loss: ReactivityLoss;
|
|
153
|
+
start: number;
|
|
154
|
+
end: number;
|
|
155
|
+
}>;
|
|
156
|
+
|
|
157
|
+
export interface ProvideKey {
|
|
158
|
+
readonly kind: "string" | "symbol";
|
|
159
|
+
readonly name: string;
|
|
160
|
+
}
|
|
161
|
+
export type InjectPattern =
|
|
162
|
+
| Readonly<{ kind: "simple" }>
|
|
163
|
+
| Readonly<{ kind: "object" | "array"; props: readonly string[] }>
|
|
164
|
+
| Readonly<{ kind: "indirect"; injectVar: string; props: readonly string[]; offset: number }>;
|
|
165
|
+
export type ProvideInjectFact =
|
|
166
|
+
| Readonly<{
|
|
167
|
+
kind: "provide";
|
|
168
|
+
id: number;
|
|
169
|
+
key: ProvideKey;
|
|
170
|
+
value: string;
|
|
171
|
+
valueType: string | null;
|
|
172
|
+
fromComposable: string | null;
|
|
173
|
+
start: number;
|
|
174
|
+
end: number;
|
|
175
|
+
}>
|
|
176
|
+
| Readonly<{
|
|
177
|
+
kind: "inject";
|
|
178
|
+
key: ProvideKey;
|
|
179
|
+
localName: string;
|
|
180
|
+
defaultValue: string | null;
|
|
181
|
+
expectedType: string | null;
|
|
182
|
+
pattern: InjectPattern;
|
|
183
|
+
fromComposable: string | null;
|
|
184
|
+
start: number;
|
|
185
|
+
end: number;
|
|
186
|
+
}>
|
|
187
|
+
| Readonly<{
|
|
188
|
+
kind: "composable";
|
|
189
|
+
name: string;
|
|
190
|
+
source: string;
|
|
191
|
+
localName: string | null;
|
|
192
|
+
usesProvide: boolean;
|
|
193
|
+
usesInject: boolean;
|
|
194
|
+
usesReactivity: boolean;
|
|
195
|
+
start: number;
|
|
196
|
+
end: number;
|
|
197
|
+
}>;
|
|
198
|
+
|
|
199
|
+
export type RaceRisk =
|
|
200
|
+
| Readonly<{
|
|
201
|
+
kind: "async-watcher";
|
|
202
|
+
watcherName: string;
|
|
203
|
+
asyncOperation: string;
|
|
204
|
+
mutatedTargets: readonly string[];
|
|
205
|
+
}>
|
|
206
|
+
| Readonly<{
|
|
207
|
+
kind: "async-watch-effect" | "promise-continuation";
|
|
208
|
+
asyncOperation: string;
|
|
209
|
+
mutatedTargets: readonly string[];
|
|
210
|
+
}>
|
|
211
|
+
| Readonly<{
|
|
212
|
+
kind: "async-lifecycle";
|
|
213
|
+
hookName: string;
|
|
214
|
+
asyncOperation: string;
|
|
215
|
+
mutatedTargets: readonly string[];
|
|
216
|
+
}>
|
|
217
|
+
| Readonly<{ kind: "scheduled"; schedulerName: string; mutatedTargets: readonly string[] }>;
|
|
218
|
+
export interface RaceConditionFact extends FactRange {
|
|
219
|
+
readonly kind: RaceRisk;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
export type UnresolvedTypeReason =
|
|
223
|
+
| "external-world-unavailable"
|
|
224
|
+
| "unbound"
|
|
225
|
+
| "missing-module"
|
|
226
|
+
| "incomplete-module"
|
|
227
|
+
| "unsupported-declaration"
|
|
228
|
+
| "unsupported-qualification"
|
|
229
|
+
| "ambiguous-export"
|
|
230
|
+
| "resolution-cycle"
|
|
231
|
+
| "resolution-limit";
|
|
232
|
+
export interface TypeDependency {
|
|
233
|
+
readonly name: string;
|
|
234
|
+
readonly kind: "alias" | "interface" | "imported" | "unresolved";
|
|
235
|
+
readonly module: string | null;
|
|
236
|
+
readonly export: string | null;
|
|
237
|
+
/** Authored type text, including literal whitespace. */
|
|
238
|
+
readonly body: string | null;
|
|
239
|
+
readonly parameters: string | null;
|
|
240
|
+
readonly extends: readonly string[];
|
|
241
|
+
readonly unresolved_reason: UnresolvedTypeReason | null;
|
|
242
|
+
}
|
|
243
|
+
export interface TypeEnvironment {
|
|
244
|
+
/** False means some reachable type information could not be resolved. */
|
|
245
|
+
readonly complete: boolean;
|
|
246
|
+
readonly declarations: readonly TypeDependency[];
|
|
247
|
+
}
|
|
248
|
+
export interface ExposeContract {
|
|
249
|
+
readonly name: string;
|
|
250
|
+
readonly type: string | null;
|
|
251
|
+
}
|
|
252
|
+
export interface SignatureContract {
|
|
253
|
+
readonly schema: 1;
|
|
254
|
+
readonly name: string;
|
|
255
|
+
readonly declared_name: string | null;
|
|
256
|
+
readonly generic: string | null;
|
|
257
|
+
readonly component_shape: "unspecified" | "class-api";
|
|
258
|
+
readonly script_setup: boolean;
|
|
259
|
+
readonly prop_type_arguments: string | null;
|
|
260
|
+
readonly emit_type_arguments: string | null;
|
|
261
|
+
readonly slot_type_arguments: string | null;
|
|
262
|
+
/** Public names in authored completion order, without folio key encoding. */
|
|
263
|
+
readonly props_complete: boolean;
|
|
264
|
+
readonly with_defaults: string | null;
|
|
265
|
+
readonly prop_order: readonly string[];
|
|
266
|
+
readonly slot_order: readonly string[];
|
|
267
|
+
readonly exposes_complete: boolean;
|
|
268
|
+
readonly exposes: readonly ExposeContract[];
|
|
269
|
+
readonly type_dependencies: TypeEnvironment;
|
|
270
|
+
}
|
|
271
|
+
export interface PropContract {
|
|
272
|
+
readonly schema: 1;
|
|
273
|
+
readonly name: string;
|
|
274
|
+
readonly type: string | null;
|
|
275
|
+
readonly required: boolean | null;
|
|
276
|
+
readonly default: string | null;
|
|
277
|
+
readonly model_modifiers: string | null;
|
|
278
|
+
readonly type_dependencies: TypeEnvironment;
|
|
279
|
+
}
|
|
280
|
+
export interface EmitContract {
|
|
281
|
+
readonly schema: 1;
|
|
282
|
+
readonly name: string;
|
|
283
|
+
readonly payload: string | null;
|
|
284
|
+
readonly overload_payloads: readonly (string | null)[];
|
|
285
|
+
readonly unresolved_type_arguments: string | null;
|
|
286
|
+
readonly validator_signatures: readonly string[];
|
|
287
|
+
readonly validator_type_annotations: readonly string[];
|
|
288
|
+
readonly type_dependencies: TypeEnvironment;
|
|
289
|
+
}
|
|
290
|
+
export interface SlotContract {
|
|
291
|
+
readonly schema: 1;
|
|
292
|
+
readonly name: string;
|
|
293
|
+
readonly props: string | null;
|
|
294
|
+
readonly type_dependencies: TypeEnvironment;
|
|
295
|
+
}
|
|
296
|
+
export type AlphaReactiveKind =
|
|
297
|
+
| "ref"
|
|
298
|
+
| "shallowRef"
|
|
299
|
+
| "reactive"
|
|
300
|
+
| "shallowReactive"
|
|
301
|
+
| "computed"
|
|
302
|
+
| "readonly"
|
|
303
|
+
| "shallowReadonly"
|
|
304
|
+
| "toRef"
|
|
305
|
+
| "toRefs";
|
|
306
|
+
export interface ReactivityContract {
|
|
307
|
+
readonly schema: 1;
|
|
308
|
+
readonly name: string;
|
|
309
|
+
readonly type: string | null;
|
|
310
|
+
readonly kind: AlphaReactiveKind | null;
|
|
311
|
+
readonly class: ReactivityClass | null;
|
|
312
|
+
readonly verdict: Verdict;
|
|
313
|
+
readonly effects: readonly ReactivityEffect[];
|
|
314
|
+
readonly type_dependencies: TypeEnvironment;
|
|
315
|
+
}
|
|
316
|
+
export interface ComponentContract {
|
|
317
|
+
readonly schema: 1;
|
|
318
|
+
readonly module: string | null;
|
|
319
|
+
readonly export: string;
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/** Exact native group names and map key/value contracts. */
|
|
323
|
+
export interface FactGroups {
|
|
324
|
+
readonly templateScopes: ReadonlyMap<number, readonly TemplateScopeEntry[]>;
|
|
325
|
+
readonly bindings: ReadonlyMap<string, BindingFact>;
|
|
326
|
+
readonly "undefined-refs": ReadonlyMap<number, UndefinedRefFact>;
|
|
327
|
+
readonly "unused-bindings": ReadonlyMap<string, UnusedBindingFact>;
|
|
328
|
+
/** Keys are serialized JSON tuples [module | null, export]. */
|
|
329
|
+
readonly "component-usages": ReadonlyMap<string, ComponentUsagesFact>;
|
|
330
|
+
readonly reactivity: ReadonlyMap<`source:${number}` | `loss:${number}`, ReactivityFact>;
|
|
331
|
+
readonly "provide-inject": ReadonlyMap<
|
|
332
|
+
`provide:${number}` | `inject:${number}` | `composable:${number}`,
|
|
333
|
+
ProvideInjectFact
|
|
334
|
+
>;
|
|
335
|
+
readonly "race-conditions": ReadonlyMap<number, RaceConditionFact>;
|
|
336
|
+
/** Alpha keys are canonical folio declaration keys. */
|
|
337
|
+
readonly "component-signature": ReadonlyMap<string, SignatureContract>;
|
|
338
|
+
readonly "prop-types": ReadonlyMap<string, PropContract>;
|
|
339
|
+
readonly "emit-types": ReadonlyMap<string, EmitContract>;
|
|
340
|
+
readonly "slot-types": ReadonlyMap<string, SlotContract>;
|
|
341
|
+
readonly "reactivity-classes": ReadonlyMap<string, ReactivityContract>;
|
|
342
|
+
readonly "component-references": ReadonlyMap<string, ComponentContract>;
|
|
343
|
+
}
|
|
344
|
+
export type FactKey<K extends keyof FactGroups> =
|
|
345
|
+
FactGroups[K] extends ReadonlyMap<infer Key, unknown> ? Key : never;
|
|
346
|
+
export type FactValue<K extends keyof FactGroups> =
|
|
347
|
+
FactGroups[K] extends ReadonlyMap<unknown, infer Value> ? Value : never;
|
|
348
|
+
export type FactEntry<K extends keyof FactGroups> = readonly [FactKey<K>, FactValue<K>];
|
package/fixes.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export interface PluginFix {
|
|
2
|
+
ruleId?: string;
|
|
3
|
+
plugin?: string;
|
|
4
|
+
start: number;
|
|
5
|
+
end: number;
|
|
6
|
+
text: string;
|
|
7
|
+
}
|
|
8
|
+
/** Apply all nonoverlapping host-owned UTF-8 ranges, or throw without editing. */
|
|
9
|
+
export declare function applyFixes(source: string, fixes: readonly PluginFix[]): string;
|
package/fixes.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Buffer } from "node:buffer";
|
|
2
|
+
|
|
3
|
+
/** Apply host-owned UTF-8 ranges atomically; overlapping suggestions refuse. */
|
|
4
|
+
export function applyFixes(source, fixes) {
|
|
5
|
+
const bytes = Buffer.from(source);
|
|
6
|
+
const sorted = [...fixes].sort((left, right) => left.start - right.start || left.end - right.end);
|
|
7
|
+
const parts = [];
|
|
8
|
+
let cursor = 0;
|
|
9
|
+
let previous;
|
|
10
|
+
for (const fix of sorted) {
|
|
11
|
+
if (
|
|
12
|
+
!Number.isInteger(fix.start) ||
|
|
13
|
+
!Number.isInteger(fix.end) ||
|
|
14
|
+
fix.start < 0 ||
|
|
15
|
+
fix.end < fix.start ||
|
|
16
|
+
fix.end > bytes.length ||
|
|
17
|
+
typeof fix.text !== "string" ||
|
|
18
|
+
!boundary(bytes, fix.start) ||
|
|
19
|
+
!boundary(bytes, fix.end)
|
|
20
|
+
)
|
|
21
|
+
throw new RangeError("plugin autofix must use valid UTF-8 source boundaries");
|
|
22
|
+
if (
|
|
23
|
+
previous &&
|
|
24
|
+
fix.start === previous.start &&
|
|
25
|
+
fix.end === previous.end &&
|
|
26
|
+
fix.text === previous.text
|
|
27
|
+
)
|
|
28
|
+
continue;
|
|
29
|
+
if (fix.start < cursor || (previous && fix.start === previous.start)) {
|
|
30
|
+
throw new Error("overlapping plugin autofixes cannot be applied together");
|
|
31
|
+
}
|
|
32
|
+
parts.push(bytes.subarray(cursor, fix.start), Buffer.from(fix.text));
|
|
33
|
+
cursor = fix.end;
|
|
34
|
+
previous = fix;
|
|
35
|
+
}
|
|
36
|
+
parts.push(bytes.subarray(cursor));
|
|
37
|
+
return Buffer.concat(parts).toString();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function boundary(bytes, offset) {
|
|
41
|
+
return offset === bytes.length || (bytes[offset] & 0xc0) !== 0x80;
|
|
42
|
+
}
|
package/identity.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
// Cache identity includes the authoring runtime itself, including dirty edits.
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
|
|
5
|
+
const hash = createHash("sha256");
|
|
6
|
+
for (const file of ["index.js", "stages.js", "identity.js", "package.json"]) {
|
|
7
|
+
const bytes = readFileSync(new URL(file, import.meta.url));
|
|
8
|
+
hash.update(file).update(String(bytes.length)).update(bytes);
|
|
9
|
+
}
|
|
10
|
+
export const SDK_FINGERPRINT = hash.digest("hex");
|