@theoremjs/studio 0.0.0-stage → 0.3.2
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 +21 -0
- package/README.md +295 -2
- package/agreement.d.ts +14 -0
- package/agreement.js +80 -0
- package/asks.d.ts +47 -0
- package/asks.js +54 -0
- package/browser-connection.d.ts +49 -0
- package/browser-connection.js +215 -0
- package/browser-engine.d.ts +11 -0
- package/browser-engine.js +11 -0
- package/browser-execution.d.ts +6 -0
- package/browser-execution.js +18 -0
- package/browser-live.d.ts +4 -0
- package/browser-live.js +132 -0
- package/browser-transport.d.ts +33 -0
- package/browser-transport.js +229 -0
- package/browser.d.ts +4 -0
- package/browser.js +4 -0
- package/canonical.d.ts +10 -0
- package/canonical.js +25 -0
- package/compile-workspace.d.ts +28 -0
- package/compile-workspace.js +295 -0
- package/compile.d.ts +65 -0
- package/compile.js +1605 -0
- package/concierge-demo.d.ts +17 -0
- package/concierge-demo.js +1230 -0
- package/conversation-key.d.ts +5 -0
- package/conversation-key.js +10 -0
- package/decision-example-images.d.ts +30 -0
- package/decision-example-images.js +30 -0
- package/deno.json +3 -0
- package/draft.d.ts +407 -0
- package/draft.js +505 -0
- package/editors.d.ts +29 -0
- package/editors.js +51 -0
- package/example-embedding-files.d.ts +11 -0
- package/example-embedding-files.js +20 -0
- package/example.d.ts +36 -0
- package/example.js +357 -0
- package/guardrail-probe.d.ts +100 -0
- package/guardrail-probe.js +722 -0
- package/held-credentials.d.ts +28 -0
- package/held-credentials.js +87 -0
- package/installed.mjs +40 -0
- package/lent-keys.d.ts +122 -0
- package/lent-keys.js +47 -0
- package/line-diff.d.ts +15 -0
- package/line-diff.js +41 -0
- package/live-connection.d.ts +22 -0
- package/live-connection.js +27 -0
- package/live-session-bridge.d.ts +15 -0
- package/live-session-bridge.js +202 -0
- package/map-line.d.ts +19 -0
- package/map-line.js +42 -0
- package/map.d.ts +154 -0
- package/map.js +525 -0
- package/media-accept.d.ts +19 -0
- package/media-accept.js +65 -0
- package/mod.d.ts +39 -0
- package/mod.js +33 -0
- package/origins.d.ts +52 -0
- package/origins.js +165 -0
- package/package.json +85 -4
- package/page/assets/Banner-BISz3nXA.js +2 -0
- package/page/assets/BottomSheet-BwfBDtje.js +1 -0
- package/page/assets/Card-Lp-0P5eL.js +1 -0
- package/page/assets/CardWall-CBRLdDZW.js +1 -0
- package/page/assets/Center-nHS1JSux.js +1 -0
- package/page/assets/CheckboxInput-Cj5pk-KI.js +2 -0
- package/page/assets/CodeBlock-C7XJ4ioE.js +51 -0
- package/page/assets/CollapsibleGroup-N7_SOjbm.js +2 -0
- package/page/assets/ContextMenu-CBu9H-9H.js +2 -0
- package/page/assets/DateInput-BGrQ5tPC.js +1 -0
- package/page/assets/Dialog-DELWQo-v.js +1 -0
- package/page/assets/EmptyState-C8W4JKBZ.js +1 -0
- package/page/assets/HoverCard-D1Y1ltWQ.js +1 -0
- package/page/assets/Icon-JrysT8aU.js +3 -0
- package/page/assets/IconAlertTriangle-BqAEqqdU.js +1 -0
- package/page/assets/IconButton-DlOGzJIX.js +1 -0
- package/page/assets/IconEye-BB5aY-wQ.js +1 -0
- package/page/assets/IconFileText-6zOFKKrF.js +1 -0
- package/page/assets/IconSearch-BTx66a40.js +1 -0
- package/page/assets/IconShield-DDOh7Gr5.js +1 -0
- package/page/assets/ItemSwipeLayer-Ckn4X6h9.js +1 -0
- package/page/assets/PaneState-BvA2tLQg.js +1 -0
- package/page/assets/PaneState-eUzUkNE8.css +1 -0
- package/page/assets/ScrollableArea-Ckugdj1N.js +1 -0
- package/page/assets/Section-DJ97F7QE.js +1 -0
- package/page/assets/SegmentedControlItem-h-aaXzyS.js +1 -0
- package/page/assets/SidePanel-BL32abk8.js +1 -0
- package/page/assets/StackItem-CQviqAfM.js +1 -0
- package/page/assets/Text-bum6zWWx.js +2 -0
- package/page/assets/TimestampHoverCard-DQIG0RZF.js +1 -0
- package/page/assets/ToolResultCharts-CgshSv9Q.js +1 -0
- package/page/assets/Tooltip-BH9xiRMl.js +1 -0
- package/page/assets/TraceInspector-_yZiz05W.js +1 -0
- package/page/assets/TraceOverview-BPO-HIYK.js +57 -0
- package/page/assets/VStack-CkVMcNiK.js +1 -0
- package/page/assets/_virtual_studio-type-sources-ByxqQaOG.js +12936 -0
- package/page/assets/arrive-DvbqpeRS.js +1 -0
- package/page/assets/browser-engine-CYq9DQAM.js +9 -0
- package/page/assets/browser-transport-D-2jDYe7.js +47 -0
- package/page/assets/codicon-DF1abBS2.ttf +0 -0
- package/page/assets/compile-egress-BSxOdX4K.js +23 -0
- package/page/assets/container.stylex-BmOi-8DQ.js +1 -0
- package/page/assets/createReactComponent-CZwsv6DD.js +1 -0
- package/page/assets/decision-example-images-CZGr7YIT.js +1 -0
- package/page/assets/devWarning-DxuplmRl.js +1 -0
- package/page/assets/editor-DjpsE8to.css +1 -0
- package/page/assets/editor.api-Cd3ViPQa.js +908 -0
- package/page/assets/editor.worker-6GY8RhDc.js +1 -0
- package/page/assets/editor.worker-BmdWdrHZ.js +33 -0
- package/page/assets/editors-BJX_S59A.js +1 -0
- package/page/assets/egress-automata-B5ZGE1mT.js +1 -0
- package/page/assets/embedding-space-view-CfQBPDxD.js +140 -0
- package/page/assets/encode-files-Bc3HJyAh.js +1 -0
- package/page/assets/export-agent-C3yyzXH8.js +43 -0
- package/page/assets/figtree-latin-400-normal-BD4aNku5.woff +0 -0
- package/page/assets/figtree-latin-400-normal-g7Dtegnw.woff2 +0 -0
- package/page/assets/figtree-latin-500-normal-BWnGEVsr.woff2 +0 -0
- package/page/assets/figtree-latin-500-normal-B_yy1phn.woff +0 -0
- package/page/assets/figtree-latin-600-normal-BM_oTo4n.woff +0 -0
- package/page/assets/figtree-latin-600-normal-Cv_xCTDl.woff2 +0 -0
- package/page/assets/figtree-latin-700-normal-BCCt2T1q.woff +0 -0
- package/page/assets/figtree-latin-700-normal-th6qEP7c.woff2 +0 -0
- package/page/assets/focusOutline.stylex-BT0kfa4t.js +1 -0
- package/page/assets/focusableSelector-DXZeMU85.js +1 -0
- package/page/assets/groupStyles-C7Ter7zC.js +1 -0
- package/page/assets/guardrail-tester-Gut5xK4a.js +9 -0
- package/page/assets/host-patterns-C9RMP16Q.js +1 -0
- package/page/assets/index-CvvmFKgS.js +105 -0
- package/page/assets/index-DOaHlqBo.css +1 -0
- package/page/assets/isRtlElement-BG33-k_H.js +1 -0
- package/page/assets/labels-provider-4TIWDvsE.js +3 -0
- package/page/assets/lent-keys-C32ems-I.js +1 -0
- package/page/assets/live-ingress-lDUGPwth.js +1 -0
- package/page/assets/live-messages-CnkL4GWi.js +1 -0
- package/page/assets/mergeProps-ptS-gIMz.js +1 -0
- package/page/assets/padding.stylex-B76907Zf.js +1 -0
- package/page/assets/preload-helper-DpV6A1kQ.js +1 -0
- package/page/assets/reader-BOCCYaRp.js +7 -0
- package/page/assets/register-CDgivxvX.js +2 -0
- package/page/assets/rolldown-runtime-hePW80VL.js +1 -0
- package/page/assets/schemas-BQ9Jydzb.js +56 -0
- package/page/assets/shaped-data-BcarWqd5.js +16 -0
- package/page/assets/sharedResizeObserver-4U9Xq5Oq.js +1 -0
- package/page/assets/stub-DhdcU_Ew.js +471 -0
- package/page/assets/studio-code-pane-C_SAbcOa.js +8 -0
- package/page/assets/studio-console-DZwlEsvr.js +3 -0
- package/page/assets/studio-decision-B0FnCAXA.js +1 -0
- package/page/assets/studio-embedding-CmA51HVo.js +2 -0
- package/page/assets/studio-host-console-By7hqu2l.js +2 -0
- package/page/assets/studio-live-BrcVQcyV.js +1 -0
- package/page/assets/studio-map-B_xSia-z.js +1 -0
- package/page/assets/studio-map-Bb06Tw1F.css +1 -0
- package/page/assets/studio-monaco-CbKf90i6.css +1 -0
- package/page/assets/studio-monaco-xfyfezQS.js +34 -0
- package/page/assets/studio-ts.worker-B-EXG4Sv.js +67741 -0
- package/page/assets/studio-ts.worker-BXtW3z-O.js +1 -0
- package/page/assets/studio-type-checker-BOvxudki.js +23 -0
- package/page/assets/themeProps-3IqahpCr.js +1 -0
- package/page/assets/tokens.stylex-DkHf0P9C.js +1 -0
- package/page/assets/tool-schema-idaOe2nI.js +18 -0
- package/page/assets/trace-patterns-DnYJa0nI.js +1 -0
- package/page/assets/trace-record-C3zNksA_.js +1 -0
- package/page/assets/trace-schema-9CdEyG6Q.js +1 -0
- package/page/assets/tree-DqyAMsbI.js +3 -0
- package/page/assets/turn-client-Du3Um3Mg.js +2 -0
- package/page/assets/types-BhfXQCLx.js +1 -0
- package/page/assets/typescript-20GgZ8PJ.js +1 -0
- package/page/assets/use-one-call-Hcs8YBp1.js +1 -0
- package/page/assets/useAdaptivePresentation-BzmPCddb.js +1 -0
- package/page/assets/useClipboard-BgGRxRMJ.js +1 -0
- package/page/assets/useContainerReveal-BFy8N_QU.js +1 -0
- package/page/assets/useDevWarning-Z9SoixOP.js +1 -0
- package/page/assets/useLayerDismissal-B_G7IWpC.js +1 -0
- package/page/assets/useMediaQuery-BCT0NUeZ.js +1 -0
- package/page/assets/useScrollLock-Dyf6PHJt.js +1 -0
- package/page/assets/useScrollableArea-0NzdiBCq.js +1 -0
- package/page/assets/useTooltip-CJM_xhLB.js +1 -0
- package/page/assets/workspace-DZALqEDX.js +1 -0
- package/page/assets/zip-DTcEfps6.js +1 -0
- package/page/index.html +90 -0
- package/page.mjs +49 -0
- package/people.d.ts +87 -0
- package/people.js +110 -0
- package/policy.d.ts +43 -0
- package/policy.js +102 -0
- package/ports.d.mts +32 -0
- package/ports.mjs +85 -0
- package/probe-battery.d.ts +13 -0
- package/probe-battery.js +404 -0
- package/providers.d.ts +149 -0
- package/providers.js +306 -0
- package/read-source.d.ts +48 -0
- package/read-source.js +1301 -0
- package/read-steps.d.ts +23 -0
- package/read-steps.js +2 -0
- package/reader.d.ts +11 -0
- package/reader.js +11 -0
- package/registrations.d.ts +83 -0
- package/registrations.js +1 -0
- package/requirements.d.ts +22 -0
- package/requirements.js +38 -0
- package/run-payload-core.d.ts +28 -0
- package/run-payload-core.js +123 -0
- package/run-payload.d.ts +32 -0
- package/run-payload.js +26 -0
- package/runtime-scope.d.ts +84 -0
- package/runtime-scope.js +308 -0
- package/runtime.d.ts +84 -0
- package/runtime.js +387 -0
- package/section-notes.d.ts +33 -0
- package/section-notes.js +38 -0
- package/serve-page.mjs +96 -0
- package/server/draft-decide.d.ts +72 -0
- package/server/draft-decide.js +111 -0
- package/server/draft-embedding.d.ts +3 -0
- package/server/draft-embedding.js +35 -0
- package/server/draft-handler.d.ts +82 -0
- package/server/draft-handler.js +213 -0
- package/server/draft-probe.d.ts +14 -0
- package/server/draft-probe.js +61 -0
- package/server/draft-replies.d.ts +22 -0
- package/server/draft-replies.js +46 -0
- package/server/edited-session.d.ts +28 -0
- package/server/edited-session.js +83 -0
- package/server/edits.d.ts +38 -0
- package/server/edits.js +195 -0
- package/server/handler.d.ts +133 -0
- package/server/handler.js +598 -0
- package/server/lent-draft.d.ts +19 -0
- package/server/lent-draft.js +23 -0
- package/server/live.d.ts +15 -0
- package/server/live.js +96 -0
- package/server/open-editor.d.ts +63 -0
- package/server/open-editor.js +115 -0
- package/server/origins.d.ts +18 -0
- package/server/origins.js +330 -0
- package/server/project-source.d.ts +208 -0
- package/server/project-source.js +1042 -0
- package/server/project.d.ts +22 -0
- package/server/project.js +56 -0
- package/server/run-traces.d.ts +20 -0
- package/server/run-traces.js +96 -0
- package/server/save-new.d.ts +29 -0
- package/server/save-new.js +514 -0
- package/server/save-plan.d.ts +46 -0
- package/server/save-plan.js +1134 -0
- package/server/save-remove.d.ts +21 -0
- package/server/save-remove.js +528 -0
- package/server/save-session.d.ts +96 -0
- package/server/save-session.js +418 -0
- package/server/save-wire.d.ts +235 -0
- package/server/save-wire.js +8 -0
- package/server/save.d.ts +52 -0
- package/server/save.js +209 -0
- package/server/serve.d.ts +23 -0
- package/server/serve.js +413 -0
- package/server/test-connection.d.ts +17 -0
- package/server/test-connection.js +362 -0
- package/server/test-key.d.ts +2 -0
- package/server/test-key.js +83 -0
- package/server/watch.d.ts +28 -0
- package/server/watch.js +93 -0
- package/server/zod-edit.d.ts +74 -0
- package/server/zod-edit.js +558 -0
- package/server.d.ts +8 -0
- package/server.js +8 -0
- package/setups.mjs +170 -0
- package/shared-carry.d.ts +114 -0
- package/shared-carry.js +523 -0
- package/shared-settings.d.ts +34 -0
- package/shared-settings.js +70 -0
- package/source.d.ts +75 -0
- package/source.js +414 -0
- package/start.mjs +169 -0
- package/stream-lines.d.ts +9 -0
- package/stream-lines.js +27 -0
- package/stub.d.ts +6 -0
- package/stub.js +33 -0
- package/surface.d.ts +53 -0
- package/surface.js +631 -0
- package/system-markup.d.ts +20 -0
- package/system-markup.js +51 -0
- package/tool-schema.d.ts +23 -0
- package/tool-schema.js +69 -0
- package/tools.d.ts +16 -0
- package/tools.js +91 -0
- package/traces.d.ts +24 -0
- package/traces.js +29 -0
- package/transport.d.ts +57 -0
- package/transport.js +187 -0
- package/tree.d.ts +32 -0
- package/tree.js +83 -0
- package/types.d.ts +62 -0
- package/types.js +6 -0
- package/ui/acting-as-button.d.ts +2 -0
- package/ui/acting-as-button.js +16 -0
- package/ui/add-provider.d.ts +13 -0
- package/ui/add-provider.js +20 -0
- package/ui/boot-mark.css +53 -0
- package/ui/boot-mark.d.ts +19 -0
- package/ui/boot-mark.js +128 -0
- package/ui/boot-paint.d.ts +8 -0
- package/ui/boot-paint.js +8 -0
- package/ui/brand-icons.d.ts +57 -0
- package/ui/brand-icons.js +148 -0
- package/ui/built/theme.css +830 -0
- package/ui/built/theme.css.d.ts +9 -0
- package/ui/built/theorem-studio.d.ts +13 -0
- package/ui/built/theorem-studio.js +705 -0
- package/ui/built/theorem-studio.variants.d.ts +28 -0
- package/ui/code/monaco-contributions.d.ts +45 -0
- package/ui/code/studio-code.d.ts +7 -0
- package/ui/code/studio-code.js +253 -0
- package/ui/code/studio-monaco.d.ts +44 -0
- package/ui/code/studio-monaco.js +48 -0
- package/ui/code/studio-ts.worker.d.ts +1 -0
- package/ui/code/studio-ts.worker.js +16 -0
- package/ui/code/studio-type-checker.d.ts +12 -0
- package/ui/code/studio-type-checker.js +308 -0
- package/ui/code/studio-type-sources.d.ts +26 -0
- package/ui/confirm-write.d.ts +12 -0
- package/ui/confirm-write.js +45 -0
- package/ui/diff-block.d.ts +21 -0
- package/ui/diff-block.js +17 -0
- package/ui/figtree.css +59 -0
- package/ui/file-place.d.ts +4 -0
- package/ui/file-place.js +26 -0
- package/ui/guardrail-tester.d.ts +18 -0
- package/ui/guardrail-tester.js +248 -0
- package/ui/inspector-context.d.ts +119 -0
- package/ui/inspector-context.js +84 -0
- package/ui/inspector.d.ts +247 -0
- package/ui/inspector.js +284 -0
- package/ui/lib/acting-as.d.ts +25 -0
- package/ui/lib/acting-as.js +90 -0
- package/ui/lib/boot-sequence.d.ts +32 -0
- package/ui/lib/boot-sequence.js +57 -0
- package/ui/lib/export-agent.d.ts +11 -0
- package/ui/lib/export-agent.js +335 -0
- package/ui/lib/facet-icons.d.ts +20 -0
- package/ui/lib/facet-icons.js +21 -0
- package/ui/lib/panel-motion.d.ts +8 -0
- package/ui/lib/panel-motion.js +74 -0
- package/ui/lib/project-address.d.ts +14 -0
- package/ui/lib/project-address.js +24 -0
- package/ui/lib/project-starting.d.ts +13 -0
- package/ui/lib/project-starting.js +30 -0
- package/ui/lib/project-traces.d.ts +17 -0
- package/ui/lib/project-traces.js +54 -0
- package/ui/lib/read-map.d.ts +30 -0
- package/ui/lib/read-map.js +173 -0
- package/ui/lib/read-progress.d.ts +14 -0
- package/ui/lib/read-progress.js +12 -0
- package/ui/lib/refusal-lines.d.ts +3 -0
- package/ui/lib/refusal-lines.js +8 -0
- package/ui/lib/studio-activity.d.ts +5 -0
- package/ui/lib/studio-activity.js +17 -0
- package/ui/lib/studio-console.d.ts +27 -0
- package/ui/lib/studio-console.js +9 -0
- package/ui/lib/studio-conversation.d.ts +20 -0
- package/ui/lib/studio-conversation.js +130 -0
- package/ui/lib/studio-examples.d.ts +31 -0
- package/ui/lib/studio-examples.js +25 -0
- package/ui/lib/studio-labels.d.ts +6 -0
- package/ui/lib/studio-labels.js +13 -0
- package/ui/lib/studio-map-kept.d.ts +17 -0
- package/ui/lib/studio-map-kept.js +48 -0
- package/ui/lib/studio-open.d.ts +22 -0
- package/ui/lib/studio-open.js +54 -0
- package/ui/lib/studio-page.d.ts +33 -0
- package/ui/lib/studio-page.js +121 -0
- package/ui/lib/studio-prefs.d.ts +26 -0
- package/ui/lib/studio-prefs.js +87 -0
- package/ui/lib/studio-project.d.ts +106 -0
- package/ui/lib/studio-project.js +177 -0
- package/ui/lib/studio-restore.d.ts +49 -0
- package/ui/lib/studio-restore.js +251 -0
- package/ui/lib/studio-run-open.d.ts +15 -0
- package/ui/lib/studio-run-open.js +16 -0
- package/ui/lib/studio-session.d.ts +34 -0
- package/ui/lib/studio-session.js +17 -0
- package/ui/lib/studio-settings.d.ts +15 -0
- package/ui/lib/studio-settings.js +48 -0
- package/ui/lib/studio-store.d.ts +64 -0
- package/ui/lib/studio-store.js +391 -0
- package/ui/lib/studio-watch.d.ts +12 -0
- package/ui/lib/studio-watch.js +27 -0
- package/ui/lib/tab-fills.d.ts +6 -0
- package/ui/lib/tab-fills.js +14 -0
- package/ui/lib/tool-credentials.d.ts +3 -0
- package/ui/lib/tool-credentials.js +23 -0
- package/ui/lib/tool-probe.d.ts +19 -0
- package/ui/lib/tool-probe.js +104 -0
- package/ui/lib/type-icons.d.ts +35 -0
- package/ui/lib/type-icons.js +38 -0
- package/ui/lib/when-idle.d.ts +5 -0
- package/ui/lib/when-idle.js +14 -0
- package/ui/lib/zip.d.ts +5 -0
- package/ui/lib/zip.js +8 -0
- package/ui/map-token.css +11 -0
- package/ui/map-token.d.ts +17 -0
- package/ui/map-token.js +29 -0
- package/ui/mcp-icon.d.ts +6 -0
- package/ui/mcp-icon.js +8 -0
- package/ui/motion.css +113 -0
- package/ui/pan.d.ts +32 -0
- package/ui/pan.js +90 -0
- package/ui/profile-editor.d.ts +33 -0
- package/ui/profile-editor.js +2638 -0
- package/ui/provider-editor.d.ts +15 -0
- package/ui/provider-editor.js +92 -0
- package/ui/read-map.css +48 -0
- package/ui/read-map.d.ts +7 -0
- package/ui/read-map.js +163 -0
- package/ui/refusal-detail.d.ts +4 -0
- package/ui/refusal-detail.js +21 -0
- package/ui/shared-change.d.ts +9 -0
- package/ui/shared-change.js +28 -0
- package/ui/shell-motion.css +121 -0
- package/ui/shell-motion.d.ts +73 -0
- package/ui/shell-motion.js +194 -0
- package/ui/studio-code-pane.d.ts +19 -0
- package/ui/studio-code-pane.js +44 -0
- package/ui/studio-compare.d.ts +80 -0
- package/ui/studio-compare.js +326 -0
- package/ui/studio-conflicts.d.ts +17 -0
- package/ui/studio-conflicts.js +87 -0
- package/ui/studio-connection.d.ts +64 -0
- package/ui/studio-connection.js +286 -0
- package/ui/studio-decision.d.ts +6 -0
- package/ui/studio-decision.js +49 -0
- package/ui/studio-embedding.d.ts +6 -0
- package/ui/studio-embedding.js +30 -0
- package/ui/studio-host-console.d.ts +6 -0
- package/ui/studio-host-console.js +17 -0
- package/ui/studio-host.d.ts +73 -0
- package/ui/studio-host.js +12 -0
- package/ui/studio-live.d.ts +17 -0
- package/ui/studio-live.js +26 -0
- package/ui/studio-map.css +273 -0
- package/ui/studio-map.d.ts +17 -0
- package/ui/studio-map.js +397 -0
- package/ui/studio-nav.d.ts +24 -0
- package/ui/studio-nav.js +17 -0
- package/ui/studio-no-project.d.ts +5 -0
- package/ui/studio-no-project.js +9 -0
- package/ui/studio-run.css +76 -0
- package/ui/studio-run.d.ts +14 -0
- package/ui/studio-run.js +119 -0
- package/ui/studio-runner.d.ts +45 -0
- package/ui/studio-runner.js +187 -0
- package/ui/studio-save.d.ts +49 -0
- package/ui/studio-save.js +307 -0
- package/ui/studio-screen.d.ts +61 -0
- package/ui/studio-screen.js +2078 -0
- package/ui/studio-settings.d.ts +31 -0
- package/ui/studio-settings.js +288 -0
- package/ui/studio-shell.css +27 -0
- package/ui/studio-shell.d.ts +43 -0
- package/ui/studio-shell.js +87 -0
- package/ui/studio-theme.d.ts +7 -0
- package/ui/studio-theme.js +94 -0
- package/ui/studio.css +427 -0
- package/ui/theorem-mark.css +84 -0
- package/ui/theorem-mark.d.ts +22 -0
- package/ui/theorem-mark.js +21 -0
- package/vite.d.mts +30 -0
- package/vite.mjs +277 -0
- package/workspace.d.ts +230 -0
- package/workspace.js +1054 -0
|
@@ -0,0 +1,1042 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The project's own source, read as syntax: where each `defineProfile` and
|
|
3
|
+
* `registerTool` call is written, so Save can change a value in the builder's
|
|
4
|
+
* file. It starts at the setup module and follows the project's relative
|
|
5
|
+
* imports. Nothing is loaded or run.
|
|
6
|
+
*
|
|
7
|
+
* @module
|
|
8
|
+
*/
|
|
9
|
+
import { dirname, isAbsolute, relative, resolve } from 'node:path';
|
|
10
|
+
import ts from 'typescript';
|
|
11
|
+
import { definitionOfFactory } from "../providers.js";
|
|
12
|
+
/** Where the project's files write each thing of `kind`. */
|
|
13
|
+
export function definedAs(project, kind) {
|
|
14
|
+
if (kind === 'profile')
|
|
15
|
+
return project.profiles;
|
|
16
|
+
return kind === 'tool' ? project.tools : project.providers;
|
|
17
|
+
}
|
|
18
|
+
const EXTENSIONS = ['', '.ts', '.tsx', '.mts', '.js', '.mjs', '/index.ts', '/mod.ts'];
|
|
19
|
+
/** Whether `path` is `root` or inside it. */
|
|
20
|
+
export function isInside(root, path) {
|
|
21
|
+
const from = relative(root, path);
|
|
22
|
+
return from === '' || (!from.startsWith('..') && !isAbsolute(from));
|
|
23
|
+
}
|
|
24
|
+
/** The file a relative import names, or undefined when it is not one of the project's. */
|
|
25
|
+
function importedFile(from, specifier, exists, root) {
|
|
26
|
+
if (!specifier.startsWith('./') && !specifier.startsWith('../'))
|
|
27
|
+
return undefined;
|
|
28
|
+
const base = resolve(dirname(from), specifier);
|
|
29
|
+
// `./tools.js` in a TypeScript project names `./tools.ts`.
|
|
30
|
+
const bases = /\.[cm]?js$/.test(base) ? [base, base.replace(/\.([cm]?)js$/, '.$1ts')] : [base];
|
|
31
|
+
for (const candidate of bases) {
|
|
32
|
+
for (const extension of EXTENSIONS) {
|
|
33
|
+
const path = candidate + extension;
|
|
34
|
+
if (!isInside(root, path) || path.includes('/node_modules/'))
|
|
35
|
+
continue;
|
|
36
|
+
if (exists(path))
|
|
37
|
+
return path;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
/** The setup module's `export const <name>`, when it writes one. */
|
|
43
|
+
export function setupExport(source, name) {
|
|
44
|
+
for (const statement of source.statements) {
|
|
45
|
+
if (!ts.isVariableStatement(statement))
|
|
46
|
+
continue;
|
|
47
|
+
if (!statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword))
|
|
48
|
+
continue;
|
|
49
|
+
const found = statement.declarationList.declarations.find((declaration) => ts.isIdentifier(declaration.name) && declaration.name.text === name);
|
|
50
|
+
if (found)
|
|
51
|
+
return found;
|
|
52
|
+
}
|
|
53
|
+
return undefined;
|
|
54
|
+
}
|
|
55
|
+
/** `expression` without the wrappers that do not change its value. */
|
|
56
|
+
export function unwrapped(expression) {
|
|
57
|
+
let node = expression;
|
|
58
|
+
while (ts.isParenthesizedExpression(node) ||
|
|
59
|
+
ts.isAsExpression(node) ||
|
|
60
|
+
ts.isSatisfiesExpression(node) ||
|
|
61
|
+
ts.isNonNullExpression(node))
|
|
62
|
+
node = node.expression;
|
|
63
|
+
return node;
|
|
64
|
+
}
|
|
65
|
+
/** The name a property is written under, when it is a plain one. */
|
|
66
|
+
export function propertyName(property) {
|
|
67
|
+
const { name } = property;
|
|
68
|
+
if (!name)
|
|
69
|
+
return undefined;
|
|
70
|
+
if (ts.isIdentifier(name) || ts.isStringLiteralLike(name) || ts.isNumericLiteral(name))
|
|
71
|
+
return name.text;
|
|
72
|
+
return undefined;
|
|
73
|
+
}
|
|
74
|
+
/** The module specifier of each import, export-from, and literal `import()` in a file. */
|
|
75
|
+
function specifiers(source) {
|
|
76
|
+
const found = [];
|
|
77
|
+
const visit = (node) => {
|
|
78
|
+
if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier) {
|
|
79
|
+
if (ts.isStringLiteralLike(node.moduleSpecifier))
|
|
80
|
+
found.push(node.moduleSpecifier.text);
|
|
81
|
+
}
|
|
82
|
+
else if (ts.isCallExpression(node) &&
|
|
83
|
+
node.expression.kind === ts.SyntaxKind.ImportKeyword &&
|
|
84
|
+
node.arguments[0] &&
|
|
85
|
+
ts.isStringLiteralLike(node.arguments[0]))
|
|
86
|
+
found.push(node.arguments[0].text);
|
|
87
|
+
ts.forEachChild(node, visit);
|
|
88
|
+
};
|
|
89
|
+
visit(source);
|
|
90
|
+
return found;
|
|
91
|
+
}
|
|
92
|
+
/** The kernel's package, however a specifier names it: bare, `npm:`, or `jsr:`, with a version, or a path in it. */
|
|
93
|
+
const KERNEL_PACKAGE = /^(?:npm:|jsr:)?@theoremjs\/agents(?:@[^/]*)?(?:\/|$)/;
|
|
94
|
+
/** Whether an import from `specifier` may bring in the kernel: its package, or a path, which may lead to it. Another package does not. */
|
|
95
|
+
function mayBeKernel(specifier) {
|
|
96
|
+
return KERNEL_PACKAGE.test(specifier) || /^(?:\.|\/|file:)/.test(specifier);
|
|
97
|
+
}
|
|
98
|
+
const bound = new WeakMap();
|
|
99
|
+
function bindingsOf(source) {
|
|
100
|
+
const held = bound.get(source);
|
|
101
|
+
if (held)
|
|
102
|
+
return held;
|
|
103
|
+
const names = new Map();
|
|
104
|
+
for (const statement of source.statements) {
|
|
105
|
+
if (ts.isImportDeclaration(statement) && ts.isStringLiteralLike(statement.moduleSpecifier)) {
|
|
106
|
+
const module = statement.moduleSpecifier.text;
|
|
107
|
+
const clause = statement.importClause;
|
|
108
|
+
if (clause?.name)
|
|
109
|
+
names.set(clause.name.text, { module, imported: undefined });
|
|
110
|
+
const bindings = clause?.namedBindings;
|
|
111
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
112
|
+
names.set(bindings.name.text, { module, imported: '*' });
|
|
113
|
+
else if (bindings) {
|
|
114
|
+
for (const element of bindings.elements) {
|
|
115
|
+
names.set(element.name.text, {
|
|
116
|
+
module,
|
|
117
|
+
imported: (element.propertyName ?? element.name).text,
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
else if ((ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement)) &&
|
|
123
|
+
statement.name) {
|
|
124
|
+
names.set(statement.name.text, 'own');
|
|
125
|
+
}
|
|
126
|
+
else if (ts.isVariableStatement(statement)) {
|
|
127
|
+
for (const { name } of statement.declarationList.declarations)
|
|
128
|
+
if (ts.isIdentifier(name))
|
|
129
|
+
names.set(name.text, 'own');
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
bound.set(source, names);
|
|
133
|
+
return names;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* The kernel function a call calls, by what its name is bound to: `defineProfile(...)`, `reg(...)`
|
|
137
|
+
* after `import { registerProfile as reg }`, `theorem.defineProfile(...)` after `import * as theorem`.
|
|
138
|
+
* A name another package exports, or the file declares itself, is not the kernel's.
|
|
139
|
+
*/
|
|
140
|
+
export function calleeName(call) {
|
|
141
|
+
const callee = call.expression;
|
|
142
|
+
const names = bindingsOf(call.getSourceFile());
|
|
143
|
+
if (ts.isIdentifier(callee)) {
|
|
144
|
+
const binding = names.get(callee.text);
|
|
145
|
+
if (binding === undefined)
|
|
146
|
+
return callee.text;
|
|
147
|
+
if (binding === 'own' ||
|
|
148
|
+
!mayBeKernel(binding.module) ||
|
|
149
|
+
!binding.imported ||
|
|
150
|
+
binding.imported === '*')
|
|
151
|
+
return undefined;
|
|
152
|
+
return binding.imported;
|
|
153
|
+
}
|
|
154
|
+
if (ts.isPropertyAccessExpression(callee)) {
|
|
155
|
+
const binding = ts.isIdentifier(callee.expression)
|
|
156
|
+
? names.get(callee.expression.text)
|
|
157
|
+
: undefined;
|
|
158
|
+
if (binding && binding !== 'own' && !mayBeKernel(binding.module))
|
|
159
|
+
return undefined;
|
|
160
|
+
return callee.name.text;
|
|
161
|
+
}
|
|
162
|
+
return undefined;
|
|
163
|
+
}
|
|
164
|
+
/** Whether `at` is a name its file imports from the kernel by name, such as a protocol: Save rewrites it in place. */
|
|
165
|
+
export function isKernelName(at) {
|
|
166
|
+
const node = unwrapped(at.node);
|
|
167
|
+
if (!ts.isIdentifier(node) || at.env?.bound.has(node.text))
|
|
168
|
+
return false;
|
|
169
|
+
const binding = bindingsOf(at.source).get(node.text);
|
|
170
|
+
return (binding !== undefined &&
|
|
171
|
+
binding !== 'own' &&
|
|
172
|
+
binding.imported !== '*' &&
|
|
173
|
+
binding.imported !== undefined &&
|
|
174
|
+
mayBeKernel(binding.module));
|
|
175
|
+
}
|
|
176
|
+
/** Where `source` imports the kernel's `name` from, and the name it is called by there. */
|
|
177
|
+
export function kernelImportOf(source, name) {
|
|
178
|
+
for (const [local, binding] of bindingsOf(source)) {
|
|
179
|
+
if (binding !== 'own' && binding.imported === name && mayBeKernel(binding.module))
|
|
180
|
+
return { specifier: binding.module, local };
|
|
181
|
+
}
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
/** The calls a profile's options are written in: where it is defined, or where it is registered without being defined first. */
|
|
185
|
+
const PROFILE_CALLS = new Set(['defineProfile', 'registerProfile']);
|
|
186
|
+
/** Whether a call is one a profile's options may be written straight into. */
|
|
187
|
+
function takesProfile(call) {
|
|
188
|
+
return PROFILE_CALLS.has(calleeName(call) ?? '');
|
|
189
|
+
}
|
|
190
|
+
/** How far a name is followed, through constants and calls. */
|
|
191
|
+
const MAX_DEPTH = 8;
|
|
192
|
+
/** A value the files leave out: a key an object does not write, a parameter a call gives no argument. */
|
|
193
|
+
const MISSING = Symbol('missing');
|
|
194
|
+
/** What a function returns, when returning it is all the function does. */
|
|
195
|
+
function returned(fn) {
|
|
196
|
+
const { body } = fn;
|
|
197
|
+
const plainCall = !fn.asteriskToken &&
|
|
198
|
+
!fn.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.AsyncKeyword);
|
|
199
|
+
if (!body || !plainCall)
|
|
200
|
+
return undefined;
|
|
201
|
+
if (!ts.isBlock(body))
|
|
202
|
+
return body;
|
|
203
|
+
const [only] = body.statements;
|
|
204
|
+
return body.statements.length === 1 && only && ts.isReturnStatement(only)
|
|
205
|
+
? only.expression
|
|
206
|
+
: undefined;
|
|
207
|
+
}
|
|
208
|
+
/** `fn` as a function the studio follows: plain parameters, and one value returned. */
|
|
209
|
+
function followable(name, source, fn) {
|
|
210
|
+
const value = returned(fn);
|
|
211
|
+
if (!value ||
|
|
212
|
+
fn.parameters.some((parameter) => !ts.isIdentifier(parameter.name) || parameter.dotDotDotToken)) {
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
return {
|
|
216
|
+
name,
|
|
217
|
+
source,
|
|
218
|
+
parameters: fn.parameters.map((parameter) => parameter.name.text),
|
|
219
|
+
defaults: fn.parameters.map((parameter) => parameter.initializer),
|
|
220
|
+
value,
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
/** Each top-level `const` of a file that is set to something, by its name. */
|
|
224
|
+
function constsOf(source) {
|
|
225
|
+
return source.statements
|
|
226
|
+
.filter((statement) => ts.isVariableStatement(statement) &&
|
|
227
|
+
Boolean(statement.declarationList.flags & ts.NodeFlags.Const))
|
|
228
|
+
.flatMap((statement) => [...statement.declarationList.declarations])
|
|
229
|
+
.flatMap(({ name, initializer }) => initializer && ts.isIdentifier(name) ? [{ name: name.text, initializer }] : []);
|
|
230
|
+
}
|
|
231
|
+
const isFunctionValue = (node) => ts.isArrowFunction(node) || ts.isFunctionExpression(node);
|
|
232
|
+
/** Each top-level function of a file: a declaration, or a `const` set to one. */
|
|
233
|
+
function functionsOf(source) {
|
|
234
|
+
const declared = source.statements
|
|
235
|
+
.filter((statement) => ts.isFunctionDeclaration(statement))
|
|
236
|
+
.flatMap((fn) => (fn.name ? [{ name: fn.name.text, fn }] : []));
|
|
237
|
+
const held = constsOf(source).flatMap(({ name, initializer }) => {
|
|
238
|
+
const value = unwrapped(initializer);
|
|
239
|
+
return isFunctionValue(value) ? [{ name, fn: value }] : [];
|
|
240
|
+
});
|
|
241
|
+
return [...declared, ...held];
|
|
242
|
+
}
|
|
243
|
+
/** The function a name in `source` calls: one declared there, or one a relative import brings in. */
|
|
244
|
+
function functionOf(project, source, name) {
|
|
245
|
+
const declared = (file, called) => {
|
|
246
|
+
const held = functionsOf(file).filter((each) => each.name === called);
|
|
247
|
+
return held.length === 1 && held[0] ? followable(called, file, held[0].fn) : undefined;
|
|
248
|
+
};
|
|
249
|
+
const here = declared(source, name);
|
|
250
|
+
if (here)
|
|
251
|
+
return here;
|
|
252
|
+
const from = importOf(source, name);
|
|
253
|
+
const origin = from && sourceOf(project, source, from.specifier);
|
|
254
|
+
return origin && from ? declared(origin, from.name) : undefined;
|
|
255
|
+
}
|
|
256
|
+
/** What a call of a project function returns, with its parameters standing for the call's arguments. */
|
|
257
|
+
function calledValue(project, call, at) {
|
|
258
|
+
const callee = call.expression;
|
|
259
|
+
if (!ts.isIdentifier(callee) || at.env?.bound.has(callee.text))
|
|
260
|
+
return undefined;
|
|
261
|
+
const depth = (at.env?.depth ?? 0) + 1;
|
|
262
|
+
const fn = depth > MAX_DEPTH ? undefined : functionOf(project, at.source, callee.text);
|
|
263
|
+
if (!fn || call.arguments.some(ts.isSpreadElement))
|
|
264
|
+
return undefined;
|
|
265
|
+
const bound = new Map();
|
|
266
|
+
fn.parameters.forEach((name, index) => {
|
|
267
|
+
const argument = call.arguments[index];
|
|
268
|
+
const preset = fn.defaults[index];
|
|
269
|
+
if (argument)
|
|
270
|
+
bound.set(name, { node: argument, source: at.source, env: at.env });
|
|
271
|
+
else
|
|
272
|
+
bound.set(name, preset ? { node: preset, source: fn.source } : undefined);
|
|
273
|
+
});
|
|
274
|
+
return { node: fn.value, source: fn.source, env: { bound, depth } };
|
|
275
|
+
}
|
|
276
|
+
/** The calls a registered provider makes a model with. */
|
|
277
|
+
const MODEL_CALLS = new Set(['model', 'decisionModel']);
|
|
278
|
+
/**
|
|
279
|
+
* A registered provider's `model(protocol, apiId, settings)` or `decisionModel(…)`: it returns the
|
|
280
|
+
* settings with the protocol, the provider's id and the API id.
|
|
281
|
+
*/
|
|
282
|
+
function isModelCall(node) {
|
|
283
|
+
return (ts.isCallExpression(node) &&
|
|
284
|
+
ts.isPropertyAccessExpression(node.expression) &&
|
|
285
|
+
MODEL_CALLS.has(node.expression.name.text) &&
|
|
286
|
+
(node.arguments.length === 2 || node.arguments.length === 3) &&
|
|
287
|
+
!node.arguments.some(ts.isSpreadElement));
|
|
288
|
+
}
|
|
289
|
+
/** What a call stands for, when the studio reads through it: `defineProfile`'s options, a project function's value. */
|
|
290
|
+
function seenThrough(project, call, at) {
|
|
291
|
+
const [first] = call.arguments;
|
|
292
|
+
if (calleeName(call) === 'defineProfile' &&
|
|
293
|
+
call.arguments.length === 1 &&
|
|
294
|
+
first &&
|
|
295
|
+
!ts.isSpreadElement(first)) {
|
|
296
|
+
return { node: first, source: at.source, env: at.env };
|
|
297
|
+
}
|
|
298
|
+
return calledValue(project, call, at);
|
|
299
|
+
}
|
|
300
|
+
/** The name a property is written under: a plain one, or a computed one that names a constant's text. */
|
|
301
|
+
function keyOf(project, property, at, depth) {
|
|
302
|
+
const plainName = propertyName(property);
|
|
303
|
+
if (plainName !== undefined || !property.name || !ts.isComputedPropertyName(property.name))
|
|
304
|
+
return plainName;
|
|
305
|
+
const held = step(project, { node: property.name.expression, source: at.source, env: at.env }, depth + 1);
|
|
306
|
+
if (held === MISSING)
|
|
307
|
+
return undefined;
|
|
308
|
+
return ts.isStringLiteralLike(held.node) || ts.isNumericLiteral(held.node)
|
|
309
|
+
? held.node.text
|
|
310
|
+
: undefined;
|
|
311
|
+
}
|
|
312
|
+
/** Where a provider's id is written: the `id` of the `registerProvider` call the name stands for. */
|
|
313
|
+
function providerId(project, at, depth) {
|
|
314
|
+
const held = step(project, at, depth + 1);
|
|
315
|
+
if (held === MISSING ||
|
|
316
|
+
!ts.isCallExpression(held.node) ||
|
|
317
|
+
calleeName(held.node) !== 'registerProvider')
|
|
318
|
+
return undefined;
|
|
319
|
+
const [, options] = held.node.arguments;
|
|
320
|
+
const id = options && ts.isObjectLiteralExpression(options)
|
|
321
|
+
? options.properties.findLast((property) => propertyName(property) === 'id')
|
|
322
|
+
: undefined;
|
|
323
|
+
return id && ts.isPropertyAssignment(id)
|
|
324
|
+
? { node: id.initializer, source: held.source, env: held.env }
|
|
325
|
+
: undefined;
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* The object an expression writes out: an object literal, or a provider's `model()` call. Each
|
|
329
|
+
* spread is followed to the object it names. Undefined for anything else.
|
|
330
|
+
*/
|
|
331
|
+
export function shapeOf(project, at, depth = 0) {
|
|
332
|
+
const node = unwrapped(at.node);
|
|
333
|
+
if (depth > MAX_DEPTH || !(ts.isObjectLiteralExpression(node) || isModelCall(node)))
|
|
334
|
+
return undefined;
|
|
335
|
+
const shape = { entries: new Map() };
|
|
336
|
+
const inside = (inner) => ({
|
|
337
|
+
node: inner,
|
|
338
|
+
source: at.source,
|
|
339
|
+
env: at.env,
|
|
340
|
+
});
|
|
341
|
+
const put = (entry) => {
|
|
342
|
+
shape.entries.delete(entry.key);
|
|
343
|
+
shape.entries.set(entry.key, entry);
|
|
344
|
+
shape.open?.after.add(entry.key);
|
|
345
|
+
};
|
|
346
|
+
const spread = (from, written) => {
|
|
347
|
+
const inner = step(project, inside(from), depth + 1);
|
|
348
|
+
// A spread of nothing adds nothing.
|
|
349
|
+
if (inner === MISSING)
|
|
350
|
+
return;
|
|
351
|
+
const held = shapeOf(project, inner, depth + 1);
|
|
352
|
+
if (!held) {
|
|
353
|
+
shape.open = { node: written, source: at.source, after: new Set() };
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
const sure = held.open?.after;
|
|
357
|
+
if (held.open)
|
|
358
|
+
shape.open = { ...held.open, after: new Set() };
|
|
359
|
+
for (const entry of held.entries.values()) {
|
|
360
|
+
shape.entries.delete(entry.key);
|
|
361
|
+
shape.entries.set(entry.key, entry);
|
|
362
|
+
if (!sure || sure.has(entry.key))
|
|
363
|
+
shape.open?.after.add(entry.key);
|
|
364
|
+
}
|
|
365
|
+
};
|
|
366
|
+
if (ts.isObjectLiteralExpression(node)) {
|
|
367
|
+
shape.own = { node, source: at.source };
|
|
368
|
+
for (const property of node.properties) {
|
|
369
|
+
if (ts.isSpreadAssignment(property)) {
|
|
370
|
+
spread(property.expression, property);
|
|
371
|
+
continue;
|
|
372
|
+
}
|
|
373
|
+
const key = keyOf(project, property, at, depth);
|
|
374
|
+
if (key === undefined) {
|
|
375
|
+
shape.open = { node: property, source: at.source, after: new Set() };
|
|
376
|
+
continue;
|
|
377
|
+
}
|
|
378
|
+
const value = ts.isPropertyAssignment(property)
|
|
379
|
+
? property.initializer
|
|
380
|
+
: ts.isShorthandPropertyAssignment(property)
|
|
381
|
+
? property.name
|
|
382
|
+
: undefined;
|
|
383
|
+
put({ key, property, source: at.source, ...(value ? { value: inside(value) } : {}) });
|
|
384
|
+
}
|
|
385
|
+
return shape;
|
|
386
|
+
}
|
|
387
|
+
const [protocol, apiId, settings] = node.arguments;
|
|
388
|
+
if (settings) {
|
|
389
|
+
spread(settings, settings);
|
|
390
|
+
const direct = unwrapped(settings);
|
|
391
|
+
if (ts.isObjectLiteralExpression(direct))
|
|
392
|
+
shape.own = { node: direct, source: at.source };
|
|
393
|
+
}
|
|
394
|
+
const receiver = inside(node.expression.expression);
|
|
395
|
+
const provider = providerId(project, receiver, depth);
|
|
396
|
+
put({ key: 'provider', source: at.source, receiver, ...(provider ? { value: provider } : {}) });
|
|
397
|
+
if (protocol)
|
|
398
|
+
put({ key: 'protocol', source: at.source, value: inside(protocol) });
|
|
399
|
+
if (apiId)
|
|
400
|
+
put({ key: 'apiId', source: at.source, value: inside(apiId) });
|
|
401
|
+
return shape;
|
|
402
|
+
}
|
|
403
|
+
/** Whether an expression writes a value that is surely there: not `undefined`, not `null`. */
|
|
404
|
+
function isWritten(node) {
|
|
405
|
+
return (ts.isStringLiteralLike(node) ||
|
|
406
|
+
ts.isNumericLiteral(node) ||
|
|
407
|
+
ts.isObjectLiteralExpression(node) ||
|
|
408
|
+
ts.isArrayLiteralExpression(node) ||
|
|
409
|
+
node.kind === ts.SyntaxKind.TrueKeyword ||
|
|
410
|
+
node.kind === ts.SyntaxKind.FalseKeyword ||
|
|
411
|
+
isModelCall(node));
|
|
412
|
+
}
|
|
413
|
+
/** A key read off an object: what the object holds there. */
|
|
414
|
+
function stepAccess(project, node, here, depth) {
|
|
415
|
+
const owner = step(project, { ...here, node: node.expression }, depth + 1);
|
|
416
|
+
const shape = owner === MISSING ? undefined : shapeOf(project, owner, depth + 1);
|
|
417
|
+
if (!shape)
|
|
418
|
+
return here;
|
|
419
|
+
const held = shape.entries.get(node.name.text);
|
|
420
|
+
if (shape.open && !(held && shape.open.after.has(held.key)))
|
|
421
|
+
return here;
|
|
422
|
+
if (!held)
|
|
423
|
+
return MISSING;
|
|
424
|
+
return held.value ? step(project, held.value, depth + 1) : here;
|
|
425
|
+
}
|
|
426
|
+
/** A name: the argument a followed function was called with, or the `const` it names. */
|
|
427
|
+
function stepName(project, node, here, depth) {
|
|
428
|
+
if (here.env?.bound.has(node.text)) {
|
|
429
|
+
const argument = here.env.bound.get(node.text);
|
|
430
|
+
return argument ? step(project, argument, depth + 1) : MISSING;
|
|
431
|
+
}
|
|
432
|
+
const binding = bindingOf(project, here.source, node.text);
|
|
433
|
+
return binding
|
|
434
|
+
? step(project, { node: binding.initializer, source: binding.source }, depth + 1)
|
|
435
|
+
: here;
|
|
436
|
+
}
|
|
437
|
+
/** Whether a followed condition is written out as true or false. Undefined when the files do not say. */
|
|
438
|
+
function truthOf(when) {
|
|
439
|
+
if (when === MISSING)
|
|
440
|
+
return false;
|
|
441
|
+
if (when.node.kind === ts.SyntaxKind.TrueKeyword)
|
|
442
|
+
return true;
|
|
443
|
+
return when.node.kind === ts.SyntaxKind.FalseKeyword ? false : undefined;
|
|
444
|
+
}
|
|
445
|
+
/** A condition the files write out as true or false picks its side. */
|
|
446
|
+
function stepCondition(project, node, here, depth) {
|
|
447
|
+
const truth = truthOf(step(project, { ...here, node: node.condition }, depth + 1));
|
|
448
|
+
if (truth === undefined)
|
|
449
|
+
return here;
|
|
450
|
+
return step(project, { ...here, node: truth ? node.whenTrue : node.whenFalse }, depth + 1);
|
|
451
|
+
}
|
|
452
|
+
/** `left ?? right`: the right when the left is not there, the left when it is written out. */
|
|
453
|
+
function stepFallback(project, node, here, depth) {
|
|
454
|
+
const left = step(project, { ...here, node: node.left }, depth + 1);
|
|
455
|
+
if (left === MISSING)
|
|
456
|
+
return step(project, { ...here, node: node.right }, depth + 1);
|
|
457
|
+
return isWritten(left.node) ? left : here;
|
|
458
|
+
}
|
|
459
|
+
/** A copy of one list is that list. */
|
|
460
|
+
function stepCopy(project, node, here, depth) {
|
|
461
|
+
const [only] = node.elements;
|
|
462
|
+
if (node.elements.length !== 1 || !only || !ts.isSpreadElement(only))
|
|
463
|
+
return here;
|
|
464
|
+
const copied = step(project, { ...here, node: only.expression }, depth + 1);
|
|
465
|
+
return copied !== MISSING && ts.isArrayLiteralExpression(copied.node) ? copied : here;
|
|
466
|
+
}
|
|
467
|
+
/** One expression followed as far as the files say what it is, or `MISSING` when they say it is not there. */
|
|
468
|
+
function step(project, at, depth) {
|
|
469
|
+
const node = unwrapped(at.node);
|
|
470
|
+
const here = { ...at, node };
|
|
471
|
+
if (depth > MAX_DEPTH)
|
|
472
|
+
return here;
|
|
473
|
+
if (ts.isPropertyAccessExpression(node))
|
|
474
|
+
return stepAccess(project, node, here, depth);
|
|
475
|
+
if (ts.isIdentifier(node))
|
|
476
|
+
return stepName(project, node, here, depth);
|
|
477
|
+
if (ts.isCallExpression(node)) {
|
|
478
|
+
const seen = seenThrough(project, node, at);
|
|
479
|
+
return seen ? step(project, seen, depth + 1) : here;
|
|
480
|
+
}
|
|
481
|
+
if (ts.isConditionalExpression(node))
|
|
482
|
+
return stepCondition(project, node, here, depth);
|
|
483
|
+
if (ts.isBinaryExpression(node) &&
|
|
484
|
+
node.operatorToken.kind === ts.SyntaxKind.QuestionQuestionToken) {
|
|
485
|
+
return stepFallback(project, node, here, depth);
|
|
486
|
+
}
|
|
487
|
+
return ts.isArrayLiteralExpression(node) ? stepCopy(project, node, here, depth) : here;
|
|
488
|
+
}
|
|
489
|
+
/**
|
|
490
|
+
* Follows an expression to what it stands for: a `const` in the same file or one a relative
|
|
491
|
+
* import brings in, a key of an object, an argument of a call, what a project function returns.
|
|
492
|
+
* Anything else (a `let`, a package's export, code that computes) stays as it is.
|
|
493
|
+
*/
|
|
494
|
+
export function followed(project, at) {
|
|
495
|
+
const held = step(project, at, 0);
|
|
496
|
+
return held === MISSING ? { ...at, node: unwrapped(at.node) } : held;
|
|
497
|
+
}
|
|
498
|
+
/** The file of the project a relative import in `source` names. */
|
|
499
|
+
export function sourceOf(project, source, specifier) {
|
|
500
|
+
const file = importedFile(source.fileName, specifier, (path) => project.files.has(path), project.root);
|
|
501
|
+
return file ? project.files.get(file) : undefined;
|
|
502
|
+
}
|
|
503
|
+
/** The constant a name in `source` stands for: one declared there, or one a relative import brings in. */
|
|
504
|
+
function bindingOf(project, source, name) {
|
|
505
|
+
const declared = constant(source, name);
|
|
506
|
+
if (declared)
|
|
507
|
+
return { name, source, initializer: declared };
|
|
508
|
+
const from = importOf(source, name);
|
|
509
|
+
const origin = from && sourceOf(project, source, from.specifier);
|
|
510
|
+
const exported = origin && from && constant(origin, from.name);
|
|
511
|
+
return origin && from && exported
|
|
512
|
+
? { name: from.name, source: origin, initializer: exported }
|
|
513
|
+
: undefined;
|
|
514
|
+
}
|
|
515
|
+
/**
|
|
516
|
+
* The constant whose value holds `at`: the top-level `const` it is written inside. A function that
|
|
517
|
+
* only returns a value holds what it returns, as a constant does.
|
|
518
|
+
*/
|
|
519
|
+
export function holderOf(at) {
|
|
520
|
+
const holds = (value) => value.pos <= at.node.pos && at.node.end <= value.end;
|
|
521
|
+
for (const { name, fn } of functionsOf(at.source)) {
|
|
522
|
+
if (!holds(fn))
|
|
523
|
+
continue;
|
|
524
|
+
const value = returned(fn);
|
|
525
|
+
return value && holds(value)
|
|
526
|
+
? { name, source: at.source, initializer: value, returns: true }
|
|
527
|
+
: undefined;
|
|
528
|
+
}
|
|
529
|
+
const held = constsOf(at.source).find(({ initializer }) => holds(initializer));
|
|
530
|
+
return held && { ...held, source: at.source };
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Where a name's value is written, and the constant that holds it. No holder when the name could
|
|
534
|
+
* not be followed to a constant: an argument, a call, a module the project does not own.
|
|
535
|
+
*/
|
|
536
|
+
export function namedValue(project, at) {
|
|
537
|
+
const origin = followed(project, at);
|
|
538
|
+
return {
|
|
539
|
+
origin,
|
|
540
|
+
holder: origin.node === unwrapped(at.node) && origin.source === at.source
|
|
541
|
+
? undefined
|
|
542
|
+
: holderOf(origin),
|
|
543
|
+
};
|
|
544
|
+
}
|
|
545
|
+
/** Whether an identifier reads a value: not a declaration's name, a property's name, an import, or a type. */
|
|
546
|
+
export function isRead(node) {
|
|
547
|
+
const { parent } = node;
|
|
548
|
+
if (ts.isShorthandPropertyAssignment(parent))
|
|
549
|
+
return true;
|
|
550
|
+
if (ts.isPropertyAccessExpression(parent))
|
|
551
|
+
return parent.expression === node;
|
|
552
|
+
if (ts.isImportSpecifier(parent) || ts.isExportSpecifier(parent) || ts.isTypeNode(parent))
|
|
553
|
+
return false;
|
|
554
|
+
return parent.name !== node;
|
|
555
|
+
}
|
|
556
|
+
/** Each import in `source` that names what it brings in, with where it brings it from. */
|
|
557
|
+
function* namedImports(source) {
|
|
558
|
+
for (const statement of source.statements) {
|
|
559
|
+
if (!ts.isImportDeclaration(statement) || !ts.isStringLiteralLike(statement.moduleSpecifier))
|
|
560
|
+
continue;
|
|
561
|
+
const bindings = statement.importClause?.namedBindings;
|
|
562
|
+
if (bindings && ts.isNamedImports(bindings))
|
|
563
|
+
yield { specifier: statement.moduleSpecifier.text, elements: bindings.elements };
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
/** The name `source` knows a constant by: its own, or the one an import gives it. */
|
|
567
|
+
function localName(project, source, binding) {
|
|
568
|
+
if (source === binding.source)
|
|
569
|
+
return binding.name;
|
|
570
|
+
for (const { specifier, elements } of namedImports(source)) {
|
|
571
|
+
const element = elements.find((each) => (each.propertyName ?? each.name).text === binding.name);
|
|
572
|
+
if (element && sourceOf(project, source, specifier) === binding.source)
|
|
573
|
+
return element.name.text;
|
|
574
|
+
}
|
|
575
|
+
return undefined;
|
|
576
|
+
}
|
|
577
|
+
/** Each place the project's code reads a constant. */
|
|
578
|
+
function readsOf(project, binding) {
|
|
579
|
+
const reads = [];
|
|
580
|
+
for (const source of project.files.values()) {
|
|
581
|
+
const name = localName(project, source, binding);
|
|
582
|
+
if (name === undefined)
|
|
583
|
+
continue;
|
|
584
|
+
const visit = (node) => {
|
|
585
|
+
if (ts.isIdentifier(node) && node.text === name && isRead(node))
|
|
586
|
+
reads.push({ node, source });
|
|
587
|
+
ts.forEachChild(node, visit);
|
|
588
|
+
};
|
|
589
|
+
visit(source);
|
|
590
|
+
}
|
|
591
|
+
return reads;
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* The methods of a schema that return the schema again, changed: `LIMITS.optional()` and
|
|
595
|
+
* `RESULT.extend({ … })` still write `LIMITS` and `RESULT` out.
|
|
596
|
+
*/
|
|
597
|
+
const SCHEMA_METHODS = new Set([
|
|
598
|
+
'describe',
|
|
599
|
+
'meta',
|
|
600
|
+
'optional',
|
|
601
|
+
'nullable',
|
|
602
|
+
'nullish',
|
|
603
|
+
'default',
|
|
604
|
+
'array',
|
|
605
|
+
'extend',
|
|
606
|
+
'partial',
|
|
607
|
+
'required',
|
|
608
|
+
'pick',
|
|
609
|
+
'omit',
|
|
610
|
+
'strict',
|
|
611
|
+
'passthrough',
|
|
612
|
+
'loose',
|
|
613
|
+
'catchall',
|
|
614
|
+
'refine',
|
|
615
|
+
'superRefine',
|
|
616
|
+
'check',
|
|
617
|
+
'brand',
|
|
618
|
+
'readonly',
|
|
619
|
+
'or',
|
|
620
|
+
'and',
|
|
621
|
+
]);
|
|
622
|
+
/** Whether a call writes `child` out: an argument of a call the studio reads through, the project function called, or a schema a method is called on. */
|
|
623
|
+
function passes(project, call, child) {
|
|
624
|
+
const callee = call.expression;
|
|
625
|
+
const ours = ts.isIdentifier(callee) && functionOf(project, call.getSourceFile(), callee.text) !== undefined;
|
|
626
|
+
if (child === callee)
|
|
627
|
+
return ours || (ts.isPropertyAccessExpression(callee) && SCHEMA_METHODS.has(callee.name.text));
|
|
628
|
+
if (!call.arguments.some((argument) => argument === child))
|
|
629
|
+
return false;
|
|
630
|
+
return ours || isModelCall(call) || takesProfile(call);
|
|
631
|
+
}
|
|
632
|
+
/** Whether `node` is part of the value `value` writes out, not something a function or a call inside it reads. */
|
|
633
|
+
function isWrittenIn(project, node, value) {
|
|
634
|
+
let child = node;
|
|
635
|
+
for (let at = node.parent; at !== value.parent; child = at, at = at.parent) {
|
|
636
|
+
if (ts.isCallExpression(at)) {
|
|
637
|
+
if (!passes(project, at, child))
|
|
638
|
+
return false;
|
|
639
|
+
continue;
|
|
640
|
+
}
|
|
641
|
+
const data = ts.isObjectLiteralExpression(at) ||
|
|
642
|
+
ts.isArrayLiteralExpression(at) ||
|
|
643
|
+
ts.isPropertyAssignment(at) ||
|
|
644
|
+
ts.isShorthandPropertyAssignment(at) ||
|
|
645
|
+
ts.isSpreadAssignment(at) ||
|
|
646
|
+
ts.isSpreadElement(at) ||
|
|
647
|
+
ts.isPropertyAccessExpression(at) ||
|
|
648
|
+
ts.isParenthesizedExpression(at) ||
|
|
649
|
+
ts.isAsExpression(at) ||
|
|
650
|
+
ts.isSatisfiesExpression(at) ||
|
|
651
|
+
ts.isNonNullExpression(at);
|
|
652
|
+
if (!data)
|
|
653
|
+
return false;
|
|
654
|
+
}
|
|
655
|
+
return true;
|
|
656
|
+
}
|
|
657
|
+
/**
|
|
658
|
+
* The constant the provider `id` is defined under: the top-level `const` its one definition is
|
|
659
|
+
* set to. None for a provider written straight into its registration, or written twice.
|
|
660
|
+
*/
|
|
661
|
+
export function providerNamed(project, id) {
|
|
662
|
+
const targets = project.providers.get(id) ?? [];
|
|
663
|
+
const [target] = targets;
|
|
664
|
+
if (!target || targets.length > 1)
|
|
665
|
+
return undefined;
|
|
666
|
+
const call = target.options.parent;
|
|
667
|
+
const declaration = ts.isCallExpression(call) ? unwrappedParent(call) : undefined;
|
|
668
|
+
if (!declaration || !ts.isVariableDeclaration(declaration) || !ts.isIdentifier(declaration.name))
|
|
669
|
+
return undefined;
|
|
670
|
+
const statement = declaration.parent.parent;
|
|
671
|
+
const top = ts.isVariableStatement(statement) &&
|
|
672
|
+
ts.isSourceFile(statement.parent) &&
|
|
673
|
+
(declaration.parent.flags & ts.NodeFlags.Const) !== 0;
|
|
674
|
+
if (!top)
|
|
675
|
+
return undefined;
|
|
676
|
+
const exported = statement.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ?? false;
|
|
677
|
+
return { name: declaration.name.text, source: target.source, exported };
|
|
678
|
+
}
|
|
679
|
+
/** What holds an expression once the wrappers that leave its value alone are passed: `as`, `satisfies`, brackets. */
|
|
680
|
+
function unwrappedParent(node) {
|
|
681
|
+
const { parent } = node;
|
|
682
|
+
const wraps = ts.isParenthesizedExpression(parent) ||
|
|
683
|
+
ts.isAsExpression(parent) ||
|
|
684
|
+
ts.isSatisfiesExpression(parent) ||
|
|
685
|
+
ts.isNonNullExpression(parent);
|
|
686
|
+
return wraps ? unwrappedParent(parent) : parent;
|
|
687
|
+
}
|
|
688
|
+
/** Whether the top level of `source` already gives `name` a meaning: a declaration, or an import. */
|
|
689
|
+
export function declares(source, name) {
|
|
690
|
+
return source.statements.some((statement) => {
|
|
691
|
+
if (ts.isVariableStatement(statement)) {
|
|
692
|
+
return statement.declarationList.declarations.some((held) => ts.isIdentifier(held.name) && held.name.text === name);
|
|
693
|
+
}
|
|
694
|
+
if (ts.isFunctionDeclaration(statement) || ts.isClassDeclaration(statement))
|
|
695
|
+
return statement.name?.text === name;
|
|
696
|
+
if (!ts.isImportDeclaration(statement))
|
|
697
|
+
return false;
|
|
698
|
+
const clause = statement.importClause;
|
|
699
|
+
const bindings = clause?.namedBindings;
|
|
700
|
+
if (clause?.name?.text === name)
|
|
701
|
+
return true;
|
|
702
|
+
if (bindings && ts.isNamespaceImport(bindings))
|
|
703
|
+
return bindings.name.text === name;
|
|
704
|
+
return bindings?.elements.some((element) => element.name.text === name) ?? false;
|
|
705
|
+
});
|
|
706
|
+
}
|
|
707
|
+
/** The extension the project's relative imports end in: `.ts`, `.js`, or none. `fallback` is read when it has none. */
|
|
708
|
+
export function importExtension(project, fallback) {
|
|
709
|
+
const relatives = [...project.files.values()].flatMap((source) => source.statements.flatMap((statement) => ts.isImportDeclaration(statement) &&
|
|
710
|
+
ts.isStringLiteralLike(statement.moduleSpecifier) &&
|
|
711
|
+
statement.moduleSpecifier.text.startsWith('.')
|
|
712
|
+
? [statement.moduleSpecifier.text]
|
|
713
|
+
: []));
|
|
714
|
+
const model = relatives[0] ?? fallback;
|
|
715
|
+
return /\.[cm]?[jt]sx?$/.exec(model)?.[0].replace('tsx', 'ts') ?? '';
|
|
716
|
+
}
|
|
717
|
+
/** How `from` imports `to`: a relative path, ending as the project's imports do. */
|
|
718
|
+
export function specifierTo(from, to, extension) {
|
|
719
|
+
const path = relative(dirname(from), to).replace(/\.ts$/, extension);
|
|
720
|
+
return path.startsWith('.') ? path : `./${path}`;
|
|
721
|
+
}
|
|
722
|
+
/**
|
|
723
|
+
* Whether a value other code reads is a name that code knows something by: a profile's id, or the
|
|
724
|
+
* id a provider is defined with. The studio does not rename those.
|
|
725
|
+
*/
|
|
726
|
+
export function isHeldId(kind, path, at) {
|
|
727
|
+
if (kind !== 'tool' && path.length === 1 && path[0] === 'id')
|
|
728
|
+
return true;
|
|
729
|
+
const property = at.node.parent;
|
|
730
|
+
const call = property.parent.parent;
|
|
731
|
+
return (ts.isPropertyAssignment(property) &&
|
|
732
|
+
propertyName(property) === 'id' &&
|
|
733
|
+
ts.isCallExpression(call) &&
|
|
734
|
+
calleeName(call) === 'registerProvider');
|
|
735
|
+
}
|
|
736
|
+
/** Whether `at` is written inside a call's options. */
|
|
737
|
+
export function isInTarget(target, at) {
|
|
738
|
+
return (target.source === at.source &&
|
|
739
|
+
target.options.pos <= at.node.pos &&
|
|
740
|
+
at.node.end <= target.options.end);
|
|
741
|
+
}
|
|
742
|
+
/** Whether `at` is written in the arguments of the call that makes `target`: the target's alone. */
|
|
743
|
+
export function isInCall(target, at) {
|
|
744
|
+
const { call } = target;
|
|
745
|
+
return (!!call &&
|
|
746
|
+
call.source === at.source &&
|
|
747
|
+
call.node.pos <= at.node.pos &&
|
|
748
|
+
at.node.end <= call.node.end);
|
|
749
|
+
}
|
|
750
|
+
/** Each profile or tool whose options write `at` out as a value. A read inside a handler is code. */
|
|
751
|
+
function targetsOf(project, targets, at) {
|
|
752
|
+
const names = [];
|
|
753
|
+
for (const [name, held] of targets) {
|
|
754
|
+
if (held.some((target) => isInTarget(target, at) && isWrittenIn(project, at.node, target.options)))
|
|
755
|
+
names.push(name);
|
|
756
|
+
}
|
|
757
|
+
return names;
|
|
758
|
+
}
|
|
759
|
+
const USERS = new WeakMap();
|
|
760
|
+
/**
|
|
761
|
+
* Follows a constant to everything it sets: a profile, tool, or provider that reads it, and through a
|
|
762
|
+
* constant that reads it, whatever reads that one. The answer is the project's: do not change it.
|
|
763
|
+
*/
|
|
764
|
+
export function usersOf(project, binding) {
|
|
765
|
+
const known = USERS.get(project) ?? new Map();
|
|
766
|
+
USERS.set(project, known);
|
|
767
|
+
const cached = known.get(binding.initializer);
|
|
768
|
+
if (cached)
|
|
769
|
+
return cached;
|
|
770
|
+
const users = {
|
|
771
|
+
profiles: new Set(),
|
|
772
|
+
tools: new Set(),
|
|
773
|
+
providers: new Set(),
|
|
774
|
+
code: false,
|
|
775
|
+
readBy: [],
|
|
776
|
+
};
|
|
777
|
+
const seen = new Set();
|
|
778
|
+
const follow = (each) => {
|
|
779
|
+
if (seen.has(each.initializer))
|
|
780
|
+
return;
|
|
781
|
+
seen.add(each.initializer);
|
|
782
|
+
const reads = readsOf(project, each);
|
|
783
|
+
// A function nothing here calls is there for code the studio does not read.
|
|
784
|
+
if (each.returns && !reads.length)
|
|
785
|
+
users.code = true;
|
|
786
|
+
for (const read of reads) {
|
|
787
|
+
const profiles = targetsOf(project, project.profiles, read);
|
|
788
|
+
const tools = profiles.length ? [] : targetsOf(project, project.tools, read);
|
|
789
|
+
const providers = profiles.length || tools.length ? [] : targetsOf(project, project.providers, read);
|
|
790
|
+
const target = profiles.length || tools.length || providers.length;
|
|
791
|
+
const held = target ? undefined : holderOf(read);
|
|
792
|
+
const holder = held && isWrittenIn(project, read.node, held.initializer) ? held : undefined;
|
|
793
|
+
for (const profile of profiles)
|
|
794
|
+
users.profiles.add(profile);
|
|
795
|
+
for (const tool of tools)
|
|
796
|
+
users.tools.add(tool);
|
|
797
|
+
for (const provider of providers)
|
|
798
|
+
users.providers.add(provider);
|
|
799
|
+
if (holder)
|
|
800
|
+
follow(holder);
|
|
801
|
+
else if (!target) {
|
|
802
|
+
users.code = true;
|
|
803
|
+
users.readBy.push(read);
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
};
|
|
807
|
+
follow(binding);
|
|
808
|
+
known.set(binding.initializer, users);
|
|
809
|
+
return users;
|
|
810
|
+
}
|
|
811
|
+
/** Each top-level `const` of a file that is set to a value, not a function. */
|
|
812
|
+
function constantsOf(source) {
|
|
813
|
+
return constsOf(source)
|
|
814
|
+
.filter(({ initializer }) => !isFunctionValue(unwrapped(initializer)))
|
|
815
|
+
.map((held) => ({ ...held, source }));
|
|
816
|
+
}
|
|
817
|
+
/** `STANDARD_GUARDRAILS` and `standardGuardrails` as "Standard guardrails". */
|
|
818
|
+
export function readableName(name) {
|
|
819
|
+
const words = name
|
|
820
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1 $2')
|
|
821
|
+
.split(/[\s_]+/)
|
|
822
|
+
.filter(Boolean)
|
|
823
|
+
.map((word) => word.toLowerCase());
|
|
824
|
+
const text = words.join(' ');
|
|
825
|
+
return text ? text[0]?.toUpperCase() + text.slice(1) : name;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* The profile key a constant fills: set when every read of it is the whole value of that one key,
|
|
829
|
+
* written in a profile's own options. A constant read any other way has none.
|
|
830
|
+
*/
|
|
831
|
+
function wholeKey(project, binding) {
|
|
832
|
+
const keys = new Set();
|
|
833
|
+
for (const read of readsOf(project, binding)) {
|
|
834
|
+
let value = read.node;
|
|
835
|
+
while (ts.isParenthesizedExpression(value.parent) ||
|
|
836
|
+
ts.isAsExpression(value.parent) ||
|
|
837
|
+
ts.isSatisfiesExpression(value.parent))
|
|
838
|
+
value = value.parent;
|
|
839
|
+
const held = value.parent;
|
|
840
|
+
const whole = ts.isShorthandPropertyAssignment(held) ||
|
|
841
|
+
(ts.isPropertyAssignment(held) && held.initializer === value);
|
|
842
|
+
const own = whole &&
|
|
843
|
+
[...project.profiles.values()].flat().some((target) => target.options === held.parent);
|
|
844
|
+
keys.add(own ? propertyName(held) : undefined);
|
|
845
|
+
}
|
|
846
|
+
const [key] = keys;
|
|
847
|
+
return keys.size === 1 ? key : undefined;
|
|
848
|
+
}
|
|
849
|
+
/**
|
|
850
|
+
* The project's shared settings: each constant that more than one profile or tool reads, with
|
|
851
|
+
* who reads it. One that a single profile reads is a part of that profile, and is not listed.
|
|
852
|
+
*/
|
|
853
|
+
export function sharedSettings(project) {
|
|
854
|
+
const shared = [];
|
|
855
|
+
for (const source of project.files.values()) {
|
|
856
|
+
for (const binding of constantsOf(source)) {
|
|
857
|
+
const users = usersOf(project, binding);
|
|
858
|
+
if (users.profiles.size + users.tools.size < 2)
|
|
859
|
+
continue;
|
|
860
|
+
const key = users.tools.size ? undefined : wholeKey(project, binding);
|
|
861
|
+
shared.push({
|
|
862
|
+
name: binding.name,
|
|
863
|
+
label: readableName(binding.name),
|
|
864
|
+
file: source.fileName,
|
|
865
|
+
line: source.getLineAndCharacterOfPosition(binding.initializer.parent.getStart(source)).line +
|
|
866
|
+
1,
|
|
867
|
+
...(key === undefined ? {} : { key }),
|
|
868
|
+
profiles: [...users.profiles],
|
|
869
|
+
tools: [...users.tools],
|
|
870
|
+
readByCode: users.code,
|
|
871
|
+
});
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
return shared;
|
|
875
|
+
}
|
|
876
|
+
/** The initializer of the one top-level `const name = ...` in a file. */
|
|
877
|
+
function constant(source, name) {
|
|
878
|
+
const found = [];
|
|
879
|
+
for (const statement of source.statements) {
|
|
880
|
+
if (!ts.isVariableStatement(statement))
|
|
881
|
+
continue;
|
|
882
|
+
if (!(statement.declarationList.flags & ts.NodeFlags.Const))
|
|
883
|
+
continue;
|
|
884
|
+
for (const declaration of statement.declarationList.declarations) {
|
|
885
|
+
if (ts.isIdentifier(declaration.name) &&
|
|
886
|
+
declaration.name.text === name &&
|
|
887
|
+
declaration.initializer) {
|
|
888
|
+
found.push(declaration.initializer);
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
}
|
|
892
|
+
return found.length === 1 ? found[0] : undefined;
|
|
893
|
+
}
|
|
894
|
+
/** Where a file imports `name` from, and what it is called there. */
|
|
895
|
+
function importOf(source, name) {
|
|
896
|
+
for (const { specifier, elements } of namedImports(source)) {
|
|
897
|
+
const element = elements.find((each) => each.name.text === name);
|
|
898
|
+
if (element)
|
|
899
|
+
return { specifier, name: (element.propertyName ?? element.name).text };
|
|
900
|
+
}
|
|
901
|
+
return undefined;
|
|
902
|
+
}
|
|
903
|
+
/** The text a property of `options` holds, following a constant; undefined when it is not plain text. */
|
|
904
|
+
function textOf(project, target, key) {
|
|
905
|
+
const held = target.options.properties.find((property) => propertyName(property) === key);
|
|
906
|
+
if (!held || !ts.isPropertyAssignment(held))
|
|
907
|
+
return undefined;
|
|
908
|
+
const { node } = followed(project, {
|
|
909
|
+
node: held.initializer,
|
|
910
|
+
source: target.source,
|
|
911
|
+
env: target.env,
|
|
912
|
+
});
|
|
913
|
+
return ts.isStringLiteralLike(node) ? node.text : undefined;
|
|
914
|
+
}
|
|
915
|
+
/** The type a profile or a tool is written with, when the file says it in words. */
|
|
916
|
+
export function writtenType(project, target) {
|
|
917
|
+
return textOf(project, target, 'type');
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* The names a function gives the call it makes, one for each place the project calls the function.
|
|
921
|
+
* None unless every one of those calls says the name: the studio does not write what it cannot name.
|
|
922
|
+
*/
|
|
923
|
+
function madeBy(project, target, key) {
|
|
924
|
+
const maker = holderOf({ node: target.options, source: target.source });
|
|
925
|
+
const fn = maker && functionOf(project, target.source, maker.name);
|
|
926
|
+
if (!maker || !fn || fn.value !== maker.initializer)
|
|
927
|
+
return [];
|
|
928
|
+
const named = [];
|
|
929
|
+
for (const read of readsOf(project, maker)) {
|
|
930
|
+
const call = read.node.parent;
|
|
931
|
+
const value = ts.isCallExpression(call) && call.expression === read.node
|
|
932
|
+
? calledValue(project, call, read)
|
|
933
|
+
: undefined;
|
|
934
|
+
if (!value?.env || !ts.isCallExpression(call))
|
|
935
|
+
return [];
|
|
936
|
+
const made = {
|
|
937
|
+
...target,
|
|
938
|
+
env: value.env,
|
|
939
|
+
call: { node: call, source: read.source },
|
|
940
|
+
};
|
|
941
|
+
const name = textOf(project, made, key);
|
|
942
|
+
if (name === undefined)
|
|
943
|
+
return [];
|
|
944
|
+
named.push([name, made]);
|
|
945
|
+
}
|
|
946
|
+
return named;
|
|
947
|
+
}
|
|
948
|
+
/**
|
|
949
|
+
* Walks the project's files from `entry`, the setup module, following each import. Yields each
|
|
950
|
+
* file as it is read, so a caller can say where the walk is. Returns every file read.
|
|
951
|
+
*/
|
|
952
|
+
export function* walkProject(entry, root, read) {
|
|
953
|
+
const files = new Map();
|
|
954
|
+
const queue = [{ file: resolve(entry) }];
|
|
955
|
+
for (let step = queue.shift(); step !== undefined; step = queue.shift()) {
|
|
956
|
+
const path = step.file;
|
|
957
|
+
if (files.has(path))
|
|
958
|
+
continue;
|
|
959
|
+
const text = read(path);
|
|
960
|
+
if (text === undefined)
|
|
961
|
+
continue;
|
|
962
|
+
const source = ts.createSourceFile(path, text, ts.ScriptTarget.Latest, true);
|
|
963
|
+
files.set(path, source);
|
|
964
|
+
yield step;
|
|
965
|
+
for (const specifier of specifiers(source)) {
|
|
966
|
+
const next = importedFile(path, specifier, (file) => read(file) !== undefined, root);
|
|
967
|
+
if (next && !files.has(next))
|
|
968
|
+
queue.push({ file: next, from: path });
|
|
969
|
+
}
|
|
970
|
+
}
|
|
971
|
+
return files;
|
|
972
|
+
}
|
|
973
|
+
/** Reads the project from `entry`, the setup module. `read` returns a file's text, or undefined. */
|
|
974
|
+
export function readProjectSource(entry, root, read) {
|
|
975
|
+
const walk = walkProject(entry, root, read);
|
|
976
|
+
let step = walk.next();
|
|
977
|
+
while (!step.done)
|
|
978
|
+
step = walk.next();
|
|
979
|
+
return projectOf(step.value, entry, root);
|
|
980
|
+
}
|
|
981
|
+
/** The project the walked `files` hold: its profiles and tools, where each is made. */
|
|
982
|
+
export function projectOf(files, entry, root) {
|
|
983
|
+
const project = {
|
|
984
|
+
root,
|
|
985
|
+
entry: resolve(entry),
|
|
986
|
+
files,
|
|
987
|
+
profiles: new Map(),
|
|
988
|
+
tools: new Map(),
|
|
989
|
+
providers: new Map(),
|
|
990
|
+
};
|
|
991
|
+
const calls = [];
|
|
992
|
+
for (const source of project.files.values()) {
|
|
993
|
+
const visit = (node) => {
|
|
994
|
+
if (ts.isCallExpression(node) && node.arguments[0]) {
|
|
995
|
+
const name = calleeName(node);
|
|
996
|
+
// A tool has no define call of its own, so one written as a constant is followed to it.
|
|
997
|
+
const written = name === 'registerTool'
|
|
998
|
+
? followed(project, { node: node.arguments[0], source })
|
|
999
|
+
: { node: unwrapped(node.arguments[0]), source };
|
|
1000
|
+
const options = written.node;
|
|
1001
|
+
if (ts.isObjectLiteralExpression(options) && !written.env) {
|
|
1002
|
+
const target = { file: written.source.fileName, source: written.source, options };
|
|
1003
|
+
if (takesProfile(node))
|
|
1004
|
+
calls.push({ into: project.profiles, key: 'id', target });
|
|
1005
|
+
if (name === 'registerTool')
|
|
1006
|
+
calls.push({ into: project.tools, key: 'name', target });
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
// A provider's settings follow its definition; left without an id, it has the definition's.
|
|
1010
|
+
const settings = ts.isCallExpression(node) && calleeName(node) === 'registerProvider' && node.arguments[1]
|
|
1011
|
+
? unwrapped(node.arguments[1])
|
|
1012
|
+
: undefined;
|
|
1013
|
+
if (settings && ts.isObjectLiteralExpression(settings)) {
|
|
1014
|
+
const target = { file: source.fileName, source, options: settings };
|
|
1015
|
+
const definition = unwrapped(node.arguments[0]);
|
|
1016
|
+
const unnamed = !settings.properties.some((property) => propertyName(property) === 'id');
|
|
1017
|
+
const own = unnamed && ts.isIdentifier(definition) ? definitionOfFactory(definition.text) : undefined;
|
|
1018
|
+
if (own)
|
|
1019
|
+
project.providers.set(own, [...(project.providers.get(own) ?? []), target]);
|
|
1020
|
+
else
|
|
1021
|
+
calls.push({ into: project.providers, key: 'id', target });
|
|
1022
|
+
}
|
|
1023
|
+
ts.forEachChild(node, visit);
|
|
1024
|
+
};
|
|
1025
|
+
visit(source);
|
|
1026
|
+
}
|
|
1027
|
+
// Every file is read before a name is followed, so a constant from another file is found.
|
|
1028
|
+
const made = [];
|
|
1029
|
+
for (const { into, key, target } of calls) {
|
|
1030
|
+
const named = textOf(project, target, key);
|
|
1031
|
+
if (named !== undefined)
|
|
1032
|
+
into.set(named, [...(into.get(named) ?? []), target]);
|
|
1033
|
+
else
|
|
1034
|
+
made.push({ into, key, target });
|
|
1035
|
+
}
|
|
1036
|
+
// A function that makes the call names it by a parameter: each call of the function is one.
|
|
1037
|
+
for (const { into, key, target } of made) {
|
|
1038
|
+
for (const [named, each] of madeBy(project, target, key))
|
|
1039
|
+
into.set(named, [...(into.get(named) ?? []), each]);
|
|
1040
|
+
}
|
|
1041
|
+
return project;
|
|
1042
|
+
}
|