paseo-plugin-genui 0.0.0-stage → 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/LICENSE +202 -0
- package/OVERVIEW.md +21 -0
- package/README.md +77 -2
- package/assets/fonts/Excalifont-OFL.txt +109 -0
- package/assets/fonts/Excalifont-Regular-latin.woff2 +0 -0
- package/assets/icon.png +0 -0
- package/assets/icon.svg +1 -0
- package/assets/screenshot-chat.png +0 -0
- package/assets/screenshot-gallery.png +0 -0
- package/assets/screenshot-playground.png +0 -0
- package/client/components/content.tsx +468 -0
- package/client/components/data.tsx +967 -0
- package/client/components/dev.tsx +998 -0
- package/client/components/diagram.tsx +513 -0
- package/client/components/inputs.tsx +994 -0
- package/client/components/layout.tsx +239 -0
- package/client/components/media.tsx +428 -0
- package/client/components/project.tsx +641 -0
- package/client/components/widgets.tsx +836 -0
- package/client/gallery.tsx +449 -0
- package/client/live-ui.tsx +525 -0
- package/client/loaders.ts +342 -0
- package/client/markdown.tsx +172 -0
- package/client/node-renderer.tsx +149 -0
- package/client/render.tsx +186 -0
- package/client/runtime.ts +103 -0
- package/client/settings.tsx +57 -0
- package/client/web.ts +186 -0
- package/gallery/Acceptance-baseline.ui +1 -0
- package/gallery/Acceptance-checklist.ui +14 -0
- package/gallery/Acceptance-evidence.ui +9 -0
- package/gallery/Acceptance.ui +1 -0
- package/gallery/Badge.ui +3 -0
- package/gallery/Benchmark-context.ui +8 -0
- package/gallery/Benchmark-hyperfine.ui +1 -0
- package/gallery/Benchmark-runs-list.ui +9 -0
- package/gallery/Benchmark-runs.ui +1 -0
- package/gallery/Benchmark-vitest.ui +1 -0
- package/gallery/Benchmark.ui +9 -0
- package/gallery/Board-bb-tasks.ui +1 -0
- package/gallery/Board-gh-issues.ui +1 -0
- package/gallery/Board-gh-prs.ui +1 -0
- package/gallery/Board.ui +12 -0
- package/gallery/Button-destructive.ui +4 -0
- package/gallery/Button.ui +5 -0
- package/gallery/Calendar.ui +15 -0
- package/gallery/CalendarHeatmap-short.ui +11 -0
- package/gallery/CalendarHeatmap.ui +4 -0
- package/gallery/Callout.ui +4 -0
- package/gallery/Card.ui +4 -0
- package/gallery/Carousel.ui +8 -0
- package/gallery/Changes-paths.ui +1 -0
- package/gallery/Changes-uncommitted.ui +1 -0
- package/gallery/Changes.ui +1 -0
- package/gallery/Chart-area.ui +3 -0
- package/gallery/Chart-funnel.ui +3 -0
- package/gallery/Chart-heatmap.ui +11 -0
- package/gallery/Chart-pie.ui +3 -0
- package/gallery/Chart-radar.ui +3 -0
- package/gallery/Chart-scatter.ui +4 -0
- package/gallery/Chart-stacked.ui +5 -0
- package/gallery/Chart.ui +3 -0
- package/gallery/Checklist.ui +6 -0
- package/gallery/Choice.ui +8 -0
- package/gallery/Code.ui +3 -0
- package/gallery/Commits-limit.ui +1 -0
- package/gallery/Commits.ui +1 -0
- package/gallery/Compare-conditions.ui +4 -0
- package/gallery/Compare-three.ui +7 -0
- package/gallery/Compare.ui +4 -0
- package/gallery/Coverage-go.ui +1 -0
- package/gallery/Coverage-lcov.ui +1 -0
- package/gallery/Coverage-python.ui +1 -0
- package/gallery/Coverage.ui +1 -0
- package/gallery/DateInput.ui +5 -0
- package/gallery/Decision-percent.ui +10 -0
- package/gallery/Decision.ui +12 -0
- package/gallery/Dependencies-audit.ui +1 -0
- package/gallery/Dependencies-both.ui +1 -0
- package/gallery/Dependencies-list.ui +7 -0
- package/gallery/Dependencies-pip.ui +1 -0
- package/gallery/Dependencies-pnpm.ui +1 -0
- package/gallery/Dependencies.ui +1 -0
- package/gallery/Details.ui +4 -0
- package/gallery/Diagram-flow.ui +16 -0
- package/gallery/Diagram-sequence.ui +15 -0
- package/gallery/Diagram.ui +16 -0
- package/gallery/Diff.ui +3 -0
- package/gallery/Divider.ui +5 -0
- package/gallery/EditableTable.ui +9 -0
- package/gallery/Entity-list.ui +6 -0
- package/gallery/Entity.ui +8 -0
- package/gallery/Environments.ui +1 -0
- package/gallery/Estimate-points.ui +6 -0
- package/gallery/Estimate.ui +8 -0
- package/gallery/File.ui +4 -0
- package/gallery/Findings-biome.ui +1 -0
- package/gallery/Findings-eslint.ui +1 -0
- package/gallery/Findings-oxlint.ui +1 -0
- package/gallery/Findings-review.ui +1 -0
- package/gallery/Findings-ruff.ui +1 -0
- package/gallery/Findings-sarif.ui +1 -0
- package/gallery/Findings-ship.ui +11 -0
- package/gallery/Findings-tsc.ui +1 -0
- package/gallery/Findings.ui +7 -0
- package/gallery/Flashcards.ui +3 -0
- package/gallery/FollowUps.ui +4 -0
- package/gallery/Form.ui +8 -0
- package/gallery/Gallery-links.ui +8 -0
- package/gallery/Gallery-strip.ui +14 -0
- package/gallery/Gallery.ui +10 -0
- package/gallery/Gauge.ui +7 -0
- package/gallery/Grid.ui +8 -0
- package/gallery/Hotspots-photo.ui +8 -0
- package/gallery/Hotspots.ui +11 -0
- package/gallery/Icon.ui +5 -0
- package/gallery/Image.ui +4 -0
- package/gallery/ImageSearch-aspects.ui +6 -0
- package/gallery/ImageSearch.ui +4 -0
- package/gallery/Input.ui +5 -0
- package/gallery/Json-api.ui +1 -0
- package/gallery/Json.ui +8 -0
- package/gallery/KeyValue.ui +3 -0
- package/gallery/List-dots.ui +8 -0
- package/gallery/List.ui +8 -0
- package/gallery/ListEditor.ui +5 -0
- package/gallery/Logs-jsonl.ui +1 -0
- package/gallery/Logs-lines.ui +11 -0
- package/gallery/Logs-python.ui +1 -0
- package/gallery/Logs.ui +1 -0
- package/gallery/Map-city.ui +8 -0
- package/gallery/Map.ui +8 -0
- package/gallery/Metric.ui +3 -0
- package/gallery/OptionCards-multi.ui +10 -0
- package/gallery/OptionCards.ui +10 -0
- package/gallery/Pipeline-checks.ui +1 -0
- package/gallery/Pipeline-passed.ui +1 -0
- package/gallery/Pipeline-running.ui +1 -0
- package/gallery/Pipeline-stages.ui +13 -0
- package/gallery/Pipeline.ui +1 -0
- package/gallery/Plan.ui +9 -0
- package/gallery/Progress.ui +4 -0
- package/gallery/Provenance-deploy.ui +6 -0
- package/gallery/Provenance-no-receipt.ui +1 -0
- package/gallery/Provenance.ui +1 -0
- package/gallery/PullRequest.ui +1 -0
- package/gallery/Quiz.ui +6 -0
- package/gallery/Radio.ui +5 -0
- package/gallery/Rating.ui +5 -0
- package/gallery/Roadmap-sprint.ui +8 -0
- package/gallery/Roadmap-year.ui +9 -0
- package/gallery/Roadmap.ui +12 -0
- package/gallery/Row.ui +3 -0
- package/gallery/SegmentedBar-status.ui +8 -0
- package/gallery/SegmentedBar.ui +8 -0
- package/gallery/Select.ui +5 -0
- package/gallery/Slider.ui +7 -0
- package/gallery/Snippet.ui +3 -0
- package/gallery/Sources-chips.ui +4 -0
- package/gallery/Sources.ui +8 -0
- package/gallery/Sparkline.ui +6 -0
- package/gallery/Stack.ui +7 -0
- package/gallery/StackTrace-aggregate.ui +1 -0
- package/gallery/StackTrace-browser.ui +1 -0
- package/gallery/StackTrace-go.ui +1 -0
- package/gallery/StackTrace-java.ui +1 -0
- package/gallery/StackTrace-python.ui +1 -0
- package/gallery/StackTrace-rust.ui +1 -0
- package/gallery/StackTrace.ui +1 -0
- package/gallery/Status.ui +9 -0
- package/gallery/Stepper.ui +5 -0
- package/gallery/Steps.ui +8 -0
- package/gallery/Swatches-long-names.ui +4 -0
- package/gallery/Swatches-picker.ui +5 -0
- package/gallery/Swatches.ui +10 -0
- package/gallery/Table.ui +7 -0
- package/gallery/Tabs.ui +8 -0
- package/gallery/Tests-junit.ui +1 -0
- package/gallery/Tests-vitest-junit.ui +1 -0
- package/gallery/Tests-vitest.ui +1 -0
- package/gallery/Tests.ui +9 -0
- package/gallery/Text.ui +4 -0
- package/gallery/TextArea.ui +5 -0
- package/gallery/Threads-ids.ui +1 -0
- package/gallery/Threads.ui +1 -0
- package/gallery/Timeline.ui +8 -0
- package/gallery/Timer.ui +4 -0
- package/gallery/Toggle.ui +5 -0
- package/gallery/Transcript-text.ui +1 -0
- package/gallery/Transcript-untimed.ui +1 -0
- package/gallery/Transcript-whisper.ui +1 -0
- package/gallery/Transcript.ui +1 -0
- package/gallery/Tree.ui +3 -0
- package/gallery/Treemap-du.ui +1 -0
- package/gallery/Treemap-esbuild.ui +1 -0
- package/gallery/Treemap-tree.ui +7 -0
- package/gallery/Treemap.ui +18 -0
- package/gallery/Walkthrough.ui +9 -0
- package/gallery/Waterfall-har.ui +1 -0
- package/gallery/Waterfall-trace.ui +1 -0
- package/gallery/Waterfall.ui +11 -0
- package/gallery/data/acceptance/coil/viewport-lifecycle.json +25 -0
- package/gallery/data/acceptance/coil-gate.json +153 -0
- package/gallery/data/acceptance/orbit-baseline.json +163 -0
- package/gallery/data/acceptance/shots/gate-cell-opened.png +0 -0
- package/gallery/data/acceptance/shots/gate-light.png +0 -0
- package/gallery/data/acceptance/shots/gate-phone-dark.png +0 -0
- package/gallery/data/activity.json +1 -0
- package/gallery/data/bb-tasks-orb.json +420 -0
- package/gallery/data/biome.json +229 -0
- package/gallery/data/compare/status-after.png +0 -0
- package/gallery/data/compare/status-before.png +0 -0
- package/gallery/data/compare/status-dark-2x.png +0 -0
- package/gallery/data/compare/status-rerender.png +0 -0
- package/gallery/data/coverage-py.json +1 -0
- package/gallery/data/coverage-summary.json +1 -0
- package/gallery/data/coverage.out +23 -0
- package/gallery/data/deploys.json +1 -0
- package/gallery/data/device-runs.json +285 -0
- package/gallery/data/du-node-modules.txt +359 -0
- package/gallery/data/esbuild-meta.json +292 -0
- package/gallery/data/eslint.json +266 -0
- package/gallery/data/eslint.sarif +343 -0
- package/gallery/data/findings-review.json +76 -0
- package/gallery/data/gh-issues-nextjs.json +293 -0
- package/gallery/data/gh-pr-checks.json +272 -0
- package/gallery/data/gh-prs-nextjs.json +419 -0
- package/gallery/data/gh-run-failed.json +1 -0
- package/gallery/data/gh-run-passed.json +1 -0
- package/gallery/data/gh-run-running.json +1 -0
- package/gallery/data/github-release.json +57 -0
- package/gallery/data/hyperfine-startup.json +187 -0
- package/gallery/data/lcov.info +234 -0
- package/gallery/data/logs/media-sync.log +127 -0
- package/gallery/data/logs/thumbnailer.jsonl +17 -0
- package/gallery/data/logs/vitest-run.log +89 -0
- package/gallery/data/npm-audit.json +672 -0
- package/gallery/data/npm-outdated.json +179 -0
- package/gallery/data/oxlint.json +156 -0
- package/gallery/data/pip-outdated.json +68 -0
- package/gallery/data/pnpm-outdated.json +58 -0
- package/gallery/data/provenance/devicectl-install.json +35 -0
- package/gallery/data/provenance/install-timeout.log +4 -0
- package/gallery/data/provenance/iphone-install.json +72 -0
- package/gallery/data/provenance/iphone-no-receipt.json +57 -0
- package/gallery/data/provenance/live-home.jpg +0 -0
- package/gallery/data/provenance/live-probe.txt +10 -0
- package/gallery/data/provenance/xcodebuild-2811.log +19 -0
- package/gallery/data/provenance/xcodebuild-4712.log +19 -0
- package/gallery/data/pytest-junit.xml +64 -0
- package/gallery/data/python-org-trace.json +35 -0
- package/gallery/data/python-org.har +31 -0
- package/gallery/data/retry.ts +13 -0
- package/gallery/data/ruff.json +261 -0
- package/gallery/data/src/durations.py +63 -0
- package/gallery/data/src/json-logic.ts +156 -0
- package/gallery/data/src/mapMath.ts +135 -0
- package/gallery/data/src/semver/bump.go +73 -0
- package/gallery/data/traces/browser-error.json +5 -0
- package/gallery/data/traces/go/main.go +27 -0
- package/gallery/data/traces/go-panic.txt +16 -0
- package/gallery/data/traces/java-caused-by.txt +15 -0
- package/gallery/data/traces/node-aggregate.txt +30 -0
- package/gallery/data/traces/node-cause.txt +21 -0
- package/gallery/data/traces/py/deploy.json +5 -0
- package/gallery/data/traces/py/deploy.py +17 -0
- package/gallery/data/traces/py/settings.py +19 -0
- package/gallery/data/traces/python-chain.txt +30 -0
- package/gallery/data/traces/rust/main.rs +11 -0
- package/gallery/data/traces/rust-panic.txt +16 -0
- package/gallery/data/traces/shop/cart.mjs +19 -0
- package/gallery/data/traces/shop/pricing.mjs +12 -0
- package/gallery/data/traces/shop/status.mjs +9 -0
- package/gallery/data/traces/web/app.js +11 -0
- package/gallery/data/traces/web/index.html +1 -0
- package/gallery/data/traces/web/search.js +10 -0
- package/gallery/data/transcript/call-noisy.wav +0 -0
- package/gallery/data/transcript/call.json +362 -0
- package/gallery/data/transcript/call.wav +0 -0
- package/gallery/data/transcript/mlx-whisper-tiny.en-noisy.json +1 -0
- package/gallery/data/transcript/whisper-cpp-base.en-noisy.json +658 -0
- package/gallery/data/transcript/whisper-cpp-base.en.json +658 -0
- package/gallery/data/transcript/whisper-cpp-base.en.srt +16 -0
- package/gallery/data/transcript/whisper-cpp-tiny.en-noisy.json +619 -0
- package/gallery/data/tsc.txt +4 -0
- package/gallery/data/vitest-bench.json +119 -0
- package/gallery/data/vitest-junit.xml +133 -0
- package/gallery/data/vitest-report.json +612 -0
- package/gallery/images/bell.svg +10 -0
- package/gallery/images/bike.svg +47 -0
- package/gallery/images/footer-planet.webp +0 -0
- package/gallery/images/front-light.svg +13 -0
- package/gallery/images/golden-hour.webp +0 -0
- package/gallery/images/option-flowers.webp +0 -0
- package/gallery/images/option-hilltop.webp +0 -0
- package/gallery/images/option-meadow.webp +0 -0
- package/gallery/images/option-sky.webp +0 -0
- package/gallery/images/u-lock.svg +10 -0
- package/index.client.tsx +164 -0
- package/index.server.ts +216 -0
- package/package.json +50 -4
- package/paseo-plugin.json +14 -0
- package/server/data-source.ts +407 -0
- package/server/project.ts +981 -0
- package/server/web/address.ts +57 -0
- package/server/web/cache.ts +85 -0
- package/server/web/commons.ts +126 -0
- package/server/web/geocode.ts +40 -0
- package/server/web/image.ts +35 -0
- package/server/web/lookups.ts +248 -0
- package/server/web/net.ts +210 -0
- package/shared/core/acceptance.ts +744 -0
- package/shared/core/ast.ts +76 -0
- package/shared/core/benchmark.ts +743 -0
- package/shared/core/catalog.ts +733 -0
- package/shared/core/compare.ts +225 -0
- package/shared/core/coverage.ts +456 -0
- package/shared/core/data.ts +334 -0
- package/shared/core/decisions.ts +527 -0
- package/shared/core/dependencies.ts +604 -0
- package/shared/core/diagnostics.ts +24 -0
- package/shared/core/environments.ts +212 -0
- package/shared/core/evaluate.ts +737 -0
- package/shared/core/fields.ts +66 -0
- package/shared/core/findings.ts +1172 -0
- package/shared/core/form.ts +69 -0
- package/shared/core/index.ts +44 -0
- package/shared/core/junit.ts +252 -0
- package/shared/core/lexer.ts +275 -0
- package/shared/core/logs.ts +496 -0
- package/shared/core/markdown.ts +2374 -0
- package/shared/core/parser.ts +355 -0
- package/shared/core/perf-units.ts +228 -0
- package/shared/core/pipeline.ts +679 -0
- package/shared/core/project.ts +472 -0
- package/shared/core/provenance.ts +725 -0
- package/shared/core/stack-trace.ts +718 -0
- package/shared/core/streaming.ts +136 -0
- package/shared/core/test-results.ts +562 -0
- package/shared/core/thread-activity.ts +203 -0
- package/shared/core/tracking.ts +887 -0
- package/shared/core/transcript.ts +1364 -0
- package/shared/core/treemap.ts +446 -0
- package/shared/core/types.ts +70 -0
- package/shared/core/validate.ts +574 -0
- package/shared/core/waterfall.ts +536 -0
- package/shared/diagram/flowchart.ts +205 -0
- package/shared/diagram/layout.ts +440 -0
- package/shared/diagram/markdown.ts +135 -0
- package/shared/diagram/sequence.ts +122 -0
- package/shared/diagram/zoom.ts +114 -0
- package/shared/fallback.ts +120 -0
- package/shared/genui.test.ts +122 -0
- package/shared/instructions.ts +144 -0
- package/shared/logic/calendarMath.ts +130 -0
- package/shared/logic/chartMath.ts +289 -0
- package/shared/logic/choose-logic.ts +196 -0
- package/shared/logic/compare-logic.ts +270 -0
- package/shared/logic/content-model.ts +227 -0
- package/shared/logic/environments-logic.ts +53 -0
- package/shared/logic/explain-logic.ts +315 -0
- package/shared/logic/fieldLogic.ts +138 -0
- package/shared/logic/gallery-search.ts +71 -0
- package/shared/logic/icons.ts +322 -0
- package/shared/logic/json-logic.ts +153 -0
- package/shared/logic/mapMath.ts +133 -0
- package/shared/logic/project-logic.ts +39 -0
- package/shared/logic/references-model.ts +201 -0
- package/shared/logic/sketch.ts +165 -0
- package/shared/logic/supersession.ts +77 -0
- package/shared/logic/values.ts +133 -0
- package/shared/logic/widget-logic.ts +163 -0
- package/shared/rpc.ts +252 -0
- package/shared/segment.ts +81 -0
- package/shared/settings.ts +12 -0
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
// Data by reference: `Load("path")` reads a file the host fetched before
|
|
2
|
+
// evaluation, so agents bind tool results instead of retyping their values;
|
|
3
|
+
// `Snippet("path", from, to)` shows lines of a file the same way.
|
|
4
|
+
import type { Expr, Program } from "./ast";
|
|
5
|
+
import { LIMITS } from "./catalog";
|
|
6
|
+
import type { Pos } from "./diagnostics";
|
|
7
|
+
import { JUnitError, readJUnit } from "./junit";
|
|
8
|
+
import type { Value } from "./types";
|
|
9
|
+
|
|
10
|
+
export type LoadedData = { readonly ok: true; readonly value: Value } | { readonly ok: false; readonly error: string };
|
|
11
|
+
|
|
12
|
+
/** One file the host fetches before evaluation: parsed by extension for Load, raw text for Snippet. */
|
|
13
|
+
export interface DataRequest {
|
|
14
|
+
readonly path: string;
|
|
15
|
+
readonly raw: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** The data-map key of a request, so `Load("a.json")` and `Snippet("a.json")` never share an entry. */
|
|
19
|
+
export function dataKey(request: DataRequest): string {
|
|
20
|
+
return `${request.raw ? "raw" : "load"}:${request.path}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** Lines a Snippet shows at most; longer ranges are cut at `from + MAX_SNIPPET_LINES - 1`. */
|
|
24
|
+
export const MAX_SNIPPET_LINES = 400;
|
|
25
|
+
|
|
26
|
+
/** The path of a `Load` argument when it is a plain string (no `${}`), else null. */
|
|
27
|
+
export function loadPath(arg: Expr | undefined): string | null {
|
|
28
|
+
if (arg?.type === "literal" && typeof arg.value === "string") return arg.value;
|
|
29
|
+
if (arg?.type === "template" && arg.parts.length === 0) return arg.quasis[0];
|
|
30
|
+
return null;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** Files the spec reads: Load paths (parsed) and Snippet paths (raw), in source order, deduplicated. */
|
|
34
|
+
export function collectLoads(program: Program): DataRequest[] {
|
|
35
|
+
const found: { readonly request: DataRequest; readonly pos: Pos }[] = [];
|
|
36
|
+
const visit = (expr: Expr): void => {
|
|
37
|
+
switch (expr.type) {
|
|
38
|
+
case "literal":
|
|
39
|
+
case "state":
|
|
40
|
+
case "ident":
|
|
41
|
+
return;
|
|
42
|
+
case "template":
|
|
43
|
+
expr.parts.forEach(visit);
|
|
44
|
+
return;
|
|
45
|
+
case "list":
|
|
46
|
+
expr.items.forEach(visit);
|
|
47
|
+
return;
|
|
48
|
+
case "object":
|
|
49
|
+
for (const entry of expr.entries) visit(entry.value);
|
|
50
|
+
return;
|
|
51
|
+
case "unary":
|
|
52
|
+
visit(expr.operand);
|
|
53
|
+
return;
|
|
54
|
+
case "binary":
|
|
55
|
+
visit(expr.left);
|
|
56
|
+
visit(expr.right);
|
|
57
|
+
return;
|
|
58
|
+
case "conditional":
|
|
59
|
+
visit(expr.test);
|
|
60
|
+
visit(expr.then);
|
|
61
|
+
visit(expr.else);
|
|
62
|
+
return;
|
|
63
|
+
case "member":
|
|
64
|
+
visit(expr.object);
|
|
65
|
+
return;
|
|
66
|
+
case "index":
|
|
67
|
+
visit(expr.object);
|
|
68
|
+
visit(expr.index);
|
|
69
|
+
return;
|
|
70
|
+
case "arrow":
|
|
71
|
+
visit(expr.body);
|
|
72
|
+
return;
|
|
73
|
+
case "method":
|
|
74
|
+
visit(expr.object);
|
|
75
|
+
expr.args.forEach(visit);
|
|
76
|
+
for (const arg of expr.named) visit(arg.value);
|
|
77
|
+
return;
|
|
78
|
+
case "call": {
|
|
79
|
+
if (expr.name === "Load") {
|
|
80
|
+
const path = loadPath(expr.args[0]);
|
|
81
|
+
if (path) found.push({ request: { path, raw: false }, pos: expr.pos });
|
|
82
|
+
} else if (expr.name === "Snippet") {
|
|
83
|
+
// The path is the first argument, or named `path:`.
|
|
84
|
+
const path = loadPath(expr.args[0] ?? expr.named.find((arg) => arg.name === "path")?.value);
|
|
85
|
+
if (path) found.push({ request: { path, raw: true }, pos: expr.pos });
|
|
86
|
+
}
|
|
87
|
+
expr.args.forEach(visit);
|
|
88
|
+
for (const arg of expr.named) visit(arg.value);
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
};
|
|
93
|
+
for (const decl of program.states) visit(decl.value);
|
|
94
|
+
for (const decl of program.named.values()) visit(decl.value);
|
|
95
|
+
program.roots.forEach(visit);
|
|
96
|
+
found.sort((a, b) => a.pos.line - b.pos.line || a.pos.column - b.pos.column);
|
|
97
|
+
const requests = new Map<string, DataRequest>();
|
|
98
|
+
for (const { request } of found) if (!requests.has(dataKey(request))) requests.set(dataKey(request), request);
|
|
99
|
+
return [...requests.values()];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/** The lines a Snippet shows, or a user-facing reason it cannot show them. */
|
|
103
|
+
export type SnippetLines = { readonly code: string; readonly error: null } | { readonly code: null; readonly error: string };
|
|
104
|
+
|
|
105
|
+
/** Why `from`/`to` cannot address lines, or null; checked before the file arrives. */
|
|
106
|
+
export function snippetRangeError(from: number | null, to: number | null): string | null {
|
|
107
|
+
for (const [name, line] of [["from", from], ["to", to]] as const) {
|
|
108
|
+
if (line !== null && (!Number.isInteger(line) || line < 1)) return `\`${name}\` must be a line number (1 or more), got ${line}`;
|
|
109
|
+
}
|
|
110
|
+
if (from !== null && to !== null && from > to) return `\`from\` (${from}) is after \`to\` (${to})`;
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Lines `from`..`to` of `text` (1-based, inclusive). `from` defaults to 1 and
|
|
116
|
+
* `to` to the last line; `to` is clamped to the file, and at most
|
|
117
|
+
* MAX_SNIPPET_LINES lines are returned.
|
|
118
|
+
*/
|
|
119
|
+
export function snippetLines(text: string, from: number | null, to: number | null): SnippetLines {
|
|
120
|
+
const rangeError = snippetRangeError(from, to);
|
|
121
|
+
if (rangeError !== null) return { code: null, error: rangeError };
|
|
122
|
+
const lines = text.split(/\r?\n/);
|
|
123
|
+
// A final newline ends the last line; it does not start another.
|
|
124
|
+
if (lines[lines.length - 1] === "") lines.pop();
|
|
125
|
+
if (lines.length === 0) return { code: null, error: "the file is empty" };
|
|
126
|
+
const start = from ?? 1;
|
|
127
|
+
if (start > lines.length) {
|
|
128
|
+
return { code: null, error: `the file has ${lines.length} line${lines.length === 1 ? "" : "s"}; \`from\` is ${start}` };
|
|
129
|
+
}
|
|
130
|
+
const end = Math.min(to ?? lines.length, lines.length, start + MAX_SNIPPET_LINES - 1);
|
|
131
|
+
return { code: lines.slice(start - 1, end).join("\n"), error: null };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const LANGUAGES: Readonly<Record<string, string>> = {
|
|
135
|
+
ts: "typescript", mts: "typescript", cts: "typescript", tsx: "tsx",
|
|
136
|
+
js: "javascript", mjs: "javascript", cjs: "javascript", jsx: "jsx",
|
|
137
|
+
py: "python", rb: "ruby", rs: "rust", go: "go", java: "java", kt: "kotlin", kts: "kotlin",
|
|
138
|
+
swift: "swift", m: "objective-c", c: "c", h: "c", cc: "cpp", cpp: "cpp", cxx: "cpp", hpp: "cpp",
|
|
139
|
+
cs: "csharp", php: "php", scala: "scala", dart: "dart", lua: "lua", ex: "elixir", exs: "elixir",
|
|
140
|
+
erl: "erlang", hs: "haskell", clj: "clojure", zig: "zig", r: "r", jl: "julia", pl: "perl",
|
|
141
|
+
sh: "bash", bash: "bash", zsh: "bash", fish: "fish", ps1: "powershell",
|
|
142
|
+
json: "json", jsonc: "jsonc", jsonl: "json", yaml: "yaml", yml: "yaml", toml: "toml", ini: "ini", xml: "xml",
|
|
143
|
+
html: "html", htm: "html", css: "css", scss: "scss", less: "less", vue: "vue", svelte: "svelte", astro: "astro",
|
|
144
|
+
md: "markdown", mdx: "mdx", sql: "sql", graphql: "graphql", gql: "graphql", proto: "protobuf",
|
|
145
|
+
tf: "hcl", hcl: "hcl", nix: "nix", diff: "diff", patch: "diff", csv: "csv", txt: "text",
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const FILE_NAMES: Readonly<Record<string, string>> = {
|
|
149
|
+
dockerfile: "dockerfile",
|
|
150
|
+
makefile: "makefile",
|
|
151
|
+
gemfile: "ruby",
|
|
152
|
+
rakefile: "ruby",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/** The language of a file, by extension or well-known name; null if unknown. */
|
|
156
|
+
export function languageOf(path: string): string | null {
|
|
157
|
+
const name = path.split(/[/\\]/).pop()?.toLowerCase() ?? "";
|
|
158
|
+
if (Object.hasOwn(FILE_NAMES, name)) return FILE_NAMES[name];
|
|
159
|
+
const extension = /\.([^.]+)$/.exec(name)?.[1];
|
|
160
|
+
return extension !== undefined && Object.hasOwn(LANGUAGES, extension) ? LANGUAGES[extension] : null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Parses file text by extension: .json (and .sarif, .har, which are JSON) -> JSON; .jsonl/.ndjson -> list of
|
|
165
|
+
* values (one per non-empty line); .csv/.tsv -> list of objects keyed by the
|
|
166
|
+
* header row (RFC 4180 quoting for csv; numeric-looking cells become numbers,
|
|
167
|
+
* empty cells null, true/false booleans); .xml -> a JUnit test report as the
|
|
168
|
+
* list `Tests` takes (any other XML is refused); anything else -> the text as a
|
|
169
|
+
* string. Lists longer than LIMITS.loadedListLength are an error. Objects whose
|
|
170
|
+
* `kind` is "node", "action" or "binding" anywhere in the data are an error
|
|
171
|
+
* (loaded data must never pose as interpreter values). Malformed input is
|
|
172
|
+
* { ok: false } with a short reason, never a throw.
|
|
173
|
+
*/
|
|
174
|
+
export function parseDataFile(path: string, text: string): LoadedData {
|
|
175
|
+
const body = text.startsWith("\uFEFF") ? text.slice(1) : text;
|
|
176
|
+
const extension = /\.([^./\\]+)$/.exec(path)?.[1].toLowerCase();
|
|
177
|
+
let value: unknown;
|
|
178
|
+
try {
|
|
179
|
+
switch (extension) {
|
|
180
|
+
case "json":
|
|
181
|
+
case "sarif":
|
|
182
|
+
case "har":
|
|
183
|
+
value = JSON.parse(body);
|
|
184
|
+
break;
|
|
185
|
+
case "jsonl":
|
|
186
|
+
case "ndjson":
|
|
187
|
+
value = parseJsonLines(body);
|
|
188
|
+
break;
|
|
189
|
+
case "csv":
|
|
190
|
+
value = table(parseCsv(body));
|
|
191
|
+
break;
|
|
192
|
+
case "tsv":
|
|
193
|
+
value = table(body.split(/\r?\n/).filter((line) => line !== "").map((line) => line.split("\t")));
|
|
194
|
+
break;
|
|
195
|
+
case "xml":
|
|
196
|
+
value = readJUnit(body);
|
|
197
|
+
break;
|
|
198
|
+
default:
|
|
199
|
+
return { ok: true, value: body };
|
|
200
|
+
}
|
|
201
|
+
} catch (error) {
|
|
202
|
+
if (error instanceof DataError || error instanceof JUnitError) return { ok: false, error: error.message };
|
|
203
|
+
// Only JSON.parse of a .json file gets here: SyntaxError, or RangeError for deep nesting.
|
|
204
|
+
return { ok: false, error: `invalid JSON: ${error instanceof Error ? error.message : String(error)}` };
|
|
205
|
+
}
|
|
206
|
+
const problem = check(value);
|
|
207
|
+
if (problem !== null) return { ok: false, error: problem };
|
|
208
|
+
// check() walked every node: JSON and table cells are scalars, lists and plain objects.
|
|
209
|
+
const checked = value as Value;
|
|
210
|
+
return { ok: true, value: checked };
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/** A malformed-input reason, already phrased for the user. */
|
|
214
|
+
class DataError extends Error {}
|
|
215
|
+
|
|
216
|
+
function parseJsonLines(text: string): unknown[] {
|
|
217
|
+
const values: unknown[] = [];
|
|
218
|
+
text.split(/\r?\n/).forEach((line, index) => {
|
|
219
|
+
if (line.trim() === "") return;
|
|
220
|
+
try {
|
|
221
|
+
values.push(JSON.parse(line));
|
|
222
|
+
} catch {
|
|
223
|
+
throw new DataError(`line ${index + 1} is not valid JSON`);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
return values;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/** RFC 4180 records: `"` quotes a field, `""` escapes a quote, quoted fields may hold commas and newlines. */
|
|
230
|
+
function parseCsv(text: string): string[][] {
|
|
231
|
+
const records: string[][] = [];
|
|
232
|
+
let record: string[] = [];
|
|
233
|
+
let field = "";
|
|
234
|
+
let line = 1;
|
|
235
|
+
let i = 0;
|
|
236
|
+
const endField = () => {
|
|
237
|
+
record.push(field);
|
|
238
|
+
field = "";
|
|
239
|
+
};
|
|
240
|
+
const endRecord = () => {
|
|
241
|
+
endField();
|
|
242
|
+
// Blank lines carry no record.
|
|
243
|
+
if (record.length > 1 || record[0] !== "") records.push(record);
|
|
244
|
+
record = [];
|
|
245
|
+
};
|
|
246
|
+
while (i < text.length) {
|
|
247
|
+
const char = text[i];
|
|
248
|
+
if (char === '"' && field === "") {
|
|
249
|
+
const start = line;
|
|
250
|
+
i++;
|
|
251
|
+
for (;;) {
|
|
252
|
+
if (i >= text.length) throw new DataError(`a quoted field starting on line ${start} never closes`);
|
|
253
|
+
if (text[i] === '"') {
|
|
254
|
+
if (text[i + 1] !== '"') break;
|
|
255
|
+
field += '"';
|
|
256
|
+
i += 2;
|
|
257
|
+
} else {
|
|
258
|
+
if (text[i] === "\n") line++;
|
|
259
|
+
field += text[i++];
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
i++;
|
|
263
|
+
const next = text[i];
|
|
264
|
+
if (next !== undefined && next !== "," && next !== "\n" && next !== "\r") {
|
|
265
|
+
throw new DataError(`unexpected text after a closing quote on line ${line}`);
|
|
266
|
+
}
|
|
267
|
+
} else if (char === ",") {
|
|
268
|
+
endField();
|
|
269
|
+
i++;
|
|
270
|
+
} else if (char === "\n" || char === "\r") {
|
|
271
|
+
endRecord();
|
|
272
|
+
i += char === "\r" && text[i + 1] === "\n" ? 2 : 1;
|
|
273
|
+
line++;
|
|
274
|
+
} else {
|
|
275
|
+
field += char;
|
|
276
|
+
i++;
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
if (field !== "" || record.length > 0) endRecord();
|
|
280
|
+
return records;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** Header row + records → objects; cells are typed by `cell`. */
|
|
284
|
+
function table(records: readonly string[][]): Record<string, Value>[] {
|
|
285
|
+
const [header, ...rows] = records;
|
|
286
|
+
if (header === undefined) return [];
|
|
287
|
+
const seen = new Set<string>();
|
|
288
|
+
for (const name of header) {
|
|
289
|
+
if (seen.has(name)) throw new DataError(`the header names column "${name}" twice`);
|
|
290
|
+
seen.add(name);
|
|
291
|
+
}
|
|
292
|
+
return rows.map((row, index) => {
|
|
293
|
+
if (row.length > header.length) {
|
|
294
|
+
throw new DataError(`row ${index + 1} has ${row.length} cells; the header has ${header.length}`);
|
|
295
|
+
}
|
|
296
|
+
// fromEntries defines own properties, so a `__proto__` column stays plain data.
|
|
297
|
+
return Object.fromEntries(header.map((name, column) => [name, cell(row[column] ?? "")]));
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
const NUMBER = /^-?(0|[1-9]\d*)(\.\d+)?([eE][-+]?\d+)?$/;
|
|
302
|
+
|
|
303
|
+
/** Numbers (JSON syntax, so "007" stays text), true/false, empty → null; anything else stays text. */
|
|
304
|
+
function cell(text: string): Value {
|
|
305
|
+
const trimmed = text.trim();
|
|
306
|
+
if (trimmed === "") return null;
|
|
307
|
+
if (NUMBER.test(trimmed)) return Number(trimmed);
|
|
308
|
+
const lower = trimmed.toLowerCase();
|
|
309
|
+
if (lower === "true" || lower === "false") return lower === "true";
|
|
310
|
+
return text;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/** Why parsed data cannot be a Value, or null. Iterative, so deep nesting cannot overflow the stack. */
|
|
314
|
+
function check(root: unknown): string | null {
|
|
315
|
+
if (Array.isArray(root) && root.length > LIMITS.loadedListLength) {
|
|
316
|
+
return `the file has ${root.length} rows; the limit is ${LIMITS.loadedListLength}`;
|
|
317
|
+
}
|
|
318
|
+
const pending: unknown[] = [root];
|
|
319
|
+
while (pending.length > 0) {
|
|
320
|
+
const value = pending.pop();
|
|
321
|
+
if (Array.isArray(value)) {
|
|
322
|
+
if (value.length > LIMITS.loadedListLength) {
|
|
323
|
+
return `a list in the file has ${value.length} items; the limit is ${LIMITS.loadedListLength}`;
|
|
324
|
+
}
|
|
325
|
+
for (const item of value) pending.push(item);
|
|
326
|
+
} else if (typeof value === "object" && value !== null) {
|
|
327
|
+
if ("kind" in value && (value.kind === "node" || value.kind === "action" || value.kind === "binding")) {
|
|
328
|
+
return `\`kind: "${value.kind}"\` is reserved; loaded data cannot use it`;
|
|
329
|
+
}
|
|
330
|
+
for (const item of Object.values(value)) pending.push(item);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
return null;
|
|
334
|
+
}
|