Skip to content

Scripts: Check Script… reports what a .jsx script needs and does, without running it - #1608

Merged
bflatastic merged 1 commit into
storytold:mainfrom
dotCooCoo:feat/script-checker
Oct 13, 2026
Merged

bflatastic merged 1 commit into
storytold:mainfrom
dotCooCoo:feat/script-checker

Conversation

@dotCooCoo

Copy link
Copy Markdown
Contributor

What and why

VectorCraft doesn't run scripts written for the reference app, and File › Scripts held only the disabled Other
Script…. This change adds File › Scripts › Check Script…, which picks a .jsx, .jsxinc or .jsxbin file, reads it
without running any of it, and shows a report:

  • what the script needs: the script language's own objects, the reference app's document model, files, menu
    commands, dialogs, or features only the reference app has;
  • what it would do outside the document: write, move or delete files, open files in other apps, open network
    connections, load native code, send messages to other apps, read or set environment variables, run code the check
    can't read, or change the reference app's preferences;
  • the menu-command strings it passes, the document-model member chains and type names it uses, its directives, the
    files it includes, and the problems that would stop it loading.

Copy Report copies the report as JSON. File › Open and drag-and-drop show the report for those files too. #844 asks
for scripts to run; Check Script… does not run them.

  • vectorcraft-scriptcheck is a new Standalone crate with no dependencies. It holds a lexer for ECMAScript 3 with E4X
    literals and the #name and //@name directive lines, and an analysis of the tokens that follows variables,
    function scopes and the files a script includes. Neither the lexer nor the analysis recurses. The crate reads at
    most 8 MiB of a file and 32 MiB with the included files, follows 64 included files 16 deep, looks up at most 1,024
    paths for them, keeps 1,000 entries in a list, tracks 1,000 variables holding member chains and 1,000 holding File
    objects, and cuts echoed text to 120 characters.
  • script.check (engine, a query) checks a script given as path, data or dataBase64. With a path and
    includes: true, it also reads the files the #include lines name from inside the script's folder, through the
    automation read root (mcp: add --automation-read-root / --automation-write-root so the server can be confined to a folder #832). ui.checkScript shows the report, and docs/mcp.md describes the reply.

The language rules come from ECMA-262 3rd edition and ECMA-357 2nd edition. The directive, platform and
document-model root names come from the public JavaScript Tools Guide pages (https://extendscript.docsforadobe.dev/)
and the scripting guide's Application page (https://ai-scripting.docsforadobe.dev/jsobjref/Application/), and
executeMenuCommand, doScript, loadAction, unloadAction and selectTool come from two public forum posts.
crates/scriptcheck/src/catalog.rs cites each page by URL next to the names taken from it. Only names are used, and
the check does not map them to VectorCraft commands. The check recognizes a compiled script by its .jsxbin name or
a leading @JSXBIN@, as EffectCraft's script commands do (storytold/effectcraft
crates/engine/src/commands/file_more.rs:535 at 9a7d3e7). Every script in the tests is written from scratch with
invented names.

How it was tested

Tests that fail without the change:

  • crates/scriptcheck/src/tests.rs: each of the 34 feature ids on a synthetic script with the feature and on one
    without it; directives in both forms; member chains, assignments, and the names a function or a catch block
    declares; menu commands, effects outside the document and problems, code in an XML literal's braces included;
    includes over files in memory (folders, cycles, every limit, and variables that one file sets and another uses);
    100,000 levels of nesting on a 256 KiB stack; and properties: the tokens cover the text exactly once, the work
    stays linear in the size, compiled input isn't read, and include graphs keep to every limit.
  • crates/engine/src/tests_scripts.rs: script.check from a path, from text and from base64; included files read only
    when asked; a link out of the script's folder; the automation read root; a pipe or a folder named as a script or an
    include; kinds, limits and bad params. open_exts_cover_every_readable_format checks the Scripts filter.
  • crates/ui-egui/src/tests_script_check.rs: the menu item, the report window and Copy Report, each verdict, a label
    for every id a report can hold, File › Open (which doesn't read the whole file), and the file picker. A test in
    tests_place.rs drops a script on the window.
  • crates/engine/tests/import_fuzz.rs: garbage, generated and mutated scripts through script.check.

cargo clippy -p <crate> --all-targets -- -D warnings passes for vectorcraft-scriptcheck, vectorcraft-engine,
vectorcraft-ui-egui and xtask, and cargo xtask brands, layers and assets pass.

On macOS 27.0.1 (Apple Silicon) with rustc 1.99.0, on top of main at d9e4435:

  • CRAFT_FONTS_DIR=<craft-fonts checkout> cargo xtask ci passes its 7 steps: 6,080 tests passed, 0 failed, 6 ignored.
  • PROPTEST_CASES=20000 cargo test --release -p vectorcraft-engine --test import_fuzz: 65 passed and 0 failed in
    698 seconds.

git merge-tree reports no conflicts with main at 0573d3c.

#1183 also changes File › Scripts. With both merged, Check Script… goes into #1183's scripts::menu() above Other
Script…, file_scripts_holds_check_script_above_other_script then expects its ui.runScript item, and #1183's
without_a_scripts_folder_only_other_script_is_listed expects Check Script… before Other Script…. Keeping both sides
resolves the other conflicts in Cargo.toml, Cargo.lock, xtask/src/layers.rs, crates/ui-egui/src/lib.rs,
picks.rs, menus.rs and the Automation list in docs/target-app-parity.md. #1183 itself conflicts with main at
0573d3c in ROADMAP.md, crates/engine/src/lib.rs, docs/gaps.md, docs/roadmap.md, docs/target-app-parity.md
and xtask/src/layers.rs.

With #1223 merged, this branch conflicts in fileio/mod.rs, fileio/tests.rs, io.rs, place.rs and
docs/mcp.md. Keeping both sides resolves each one, with this branch's read_file_head, which refuses anything but a
regular file, and nine filters besides the formats in open_exts_cover_every_readable_format. Both pull requests add
the rows "Copy Report" and "Not read" to the cs, de, es, fr, hu, it, ja, ru, uk, zh-hans and zh-hant catalogs, so the
pull request merged second drops its own rows there. In cs, ja and ru the two pull requests translate "Not read"
differently. #1223 itself conflicts with main at 0573d3c in io.rs, where main's background open (023c8e3)
replaced the open_path body that #1223 changes, and in docs/roadmap.md.

Checklist

  • cargo xtask ci passes (fmt, clippy, tests, assets, brands, layers, wasm).
  • No panics in shipped code: no unwrap(), expect(), panic!, unreachable!, todo! or
    unimplemented! outside tests, and no unchecked indexing on data from files, parameters or
    messages. Errors are returned as Result and shown to the user
    (why and how).
  • Clean-room: nothing copied from Adobe products or GPL/AGPL code (AGENTS.md).
  • Every new asset has a row in ASSETS.md.

…hout running it

VectorCraft does not run scripts written for the reference app, and
File › Scripts held only the disabled Other Script…. File › Scripts ›
Check Script… picks a .jsx, .jsxinc or .jsxbin file, reads it without
running any of it, and shows a report: what the script needs (the
script language's own objects, the document model, files, menu
commands, dialogs, or features only the reference app has), what it
would do outside the document, the menu-command strings it passes, the
document-model member chains and type names it uses, its directives,
the files it includes, and the problems that would stop it loading.
Copy Report copies the report as JSON. File › Open and drag-and-drop
show the report for those files too.

- vectorcraft-scriptcheck, a new Standalone crate with no
  dependencies, holds the checker: a lexer for ECMAScript 3 with E4X
  literals and the #name and //@name directive lines, and an analysis
  of the tokens that follows variables, function scopes and the files
  a script includes. It never recurses: open brackets, the levels of
  an XML literal and the function bodies being read are kept on stacks
  capped at 4,096. It reads at most 8 MiB of a file and 32 MiB with the
  included files, follows 64 included files 16 deep, looks up at most
  1,024 paths for them, keeps 1,000 entries in a list, tracks 1,000
  variables holding member chains and 1,000 holding File objects, and
  cuts echoed text to 120 characters. A compiled script is reported as
  compiled and isn't read.
- script.check (engine, a query) takes path, data or dataBase64. With a
  path and includes: true, it reads the files the #include lines name
  from inside the script's folder, through the automation read root,
  and when the script or one of those files assigns a member chain or a
  File object to a variable, the check treats the variable as holding
  it in every file. It doesn't follow absolute names, names, folders or
  links that lead out of the folder, names or folders longer than 4,096
  bytes, or JSINCLUDE, and it reads only regular files.
- ui.checkScript shows the report (dialog scriptCheck). File › Open
  lists Scripts (.jsx, .jsxinc, .jsxbin) and sends a script's path to
  script.check, which reads at most 8 MiB of it.

The language rules come from ECMA-262 3rd edition and ECMA-357 2nd
edition. The directive, platform and document-model root names come
from the public JavaScript Tools Guide and scripting guide pages that
catalog.rs cites by URL, and executeMenuCommand, doScript, loadAction,
unloadAction and selectTool come from the public forum posts cited
there; only names are used. Every script in the tests is written from
scratch with invented names.

Docs: docs/mcp.md, docs/target-app-parity.md.
@bflatastic
bflatastic merged commit 24afe0f into storytold:main Oct 13, 2026
1 check passed
@bflatastic

Copy link
Copy Markdown
Member

Thanks @dotCooCoo, merged. I read it for what it does with a script: the checker is a hand-written scanner with no new dependency and forbid(unsafe_code), nothing in a script is run or fetched, included files are only read from inside the script's folder through the read root, and the names it recognises are cited from public pages with test scripts written from scratch. Since it only reports, it doesn't touch the open question of running such scripts (#1210). Checked on a release build: File › Scripts › Check Script… on a small script shows its needs, the file it writes, its menu command and member chains; an #include of ../outside/… is listed as not followed; a module and a compiled script are reported as such over MCP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants