95 lines
4.2 KiB
Rust
95 lines
4.2 KiB
Rust
//! #759: ExtendScript's `#include` / `//@include` / `#includepath` directives.
|
|
|
|
use std::path::{Path, PathBuf};
|
|
|
|
use effectcraft_engine::Session;
|
|
use serde_json::json;
|
|
|
|
use crate::{Outcome, run_code};
|
|
|
|
fn session() -> Session {
|
|
let mut s = Session::default();
|
|
crate::install(&mut s);
|
|
s
|
|
}
|
|
|
|
fn tmp(tag: &str) -> PathBuf {
|
|
let dir = std::env::temp_dir().join(format!("ec-include-{tag}-{}", std::process::id()));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
std::fs::create_dir_all(&dir).unwrap();
|
|
dir
|
|
}
|
|
|
|
fn write(path: &Path, text: &str) {
|
|
std::fs::create_dir_all(path.parent().unwrap()).unwrap();
|
|
std::fs::write(path, text).unwrap();
|
|
}
|
|
|
|
/// Run the script file `main` (its code read from disk, as File ▸ Scripts ▸ Run Script File does).
|
|
fn run_file(s: &mut Session, main: &Path) -> Outcome {
|
|
let code = std::fs::read_to_string(main).unwrap();
|
|
run_code(s, &code, &main.to_string_lossy())
|
|
}
|
|
|
|
#[test]
|
|
fn includes_are_inlined_relative_to_the_script() {
|
|
let dir = tmp("ok");
|
|
write(&dir.join("lib/math.jsx"), "#include \"util.jsxinc\"\nfunction twice(x) { return 2 * x; }");
|
|
write(&dir.join("lib/util.jsxinc"), "// includes its includer back: inlined once only\n#include \"math.jsx\"\nvar unit = 'px';");
|
|
write(&dir.join("vendor/extra.jsx"), "var extra = 40;");
|
|
write(
|
|
&dir.join("main.jsx"),
|
|
"#target aftereffects\n#targetengine \"main\"\n#strict on\n//@include 'lib/math.jsx'\n#includepath \"vendor;lib\"\n#include \"extra.jsx\";\n#include \"util.jsxinc\"\n[twice(1) + extra, unit, $.fileName.split('/').pop()]",
|
|
);
|
|
let mut s = session();
|
|
let o = run_file(&mut s, &dir.join("main.jsx"));
|
|
assert!(o.error.is_none(), "{:?}", o.error);
|
|
assert_eq!(o.result, json!([42, "px", "main.jsx"]));
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|
|
|
|
#[test]
|
|
fn errors_point_at_the_file_and_line_they_came_from() {
|
|
let dir = tmp("err");
|
|
write(&dir.join("lib.jsx"), "var a = 1;\nvar b = 2;\nnotDefined();");
|
|
write(&dir.join("main.jsx"), "// main\n#include \"lib.jsx\"\nvar c = 3;");
|
|
let mut s = session();
|
|
let e = run_file(&mut s, &dir.join("main.jsx")).error.expect("an error");
|
|
assert!(e.file.ends_with("lib.jsx") && e.line == Some(3), "{e:?}");
|
|
// After the include, lines in the script itself keep their numbers.
|
|
write(&dir.join("lib.jsx"), "var a = 1;\nvar b = 2;");
|
|
write(&dir.join("main.jsx"), "// main\n#include \"lib.jsx\"\nvar c = 3;\nalsoNotDefined();");
|
|
let e = run_file(&mut s, &dir.join("main.jsx")).error.expect("an error");
|
|
assert!(e.file.ends_with("main.jsx") && e.line == Some(4), "{e:?}");
|
|
// A missing file is a clear error on the directive's line.
|
|
write(&dir.join("main.jsx"), "var x = 1;\n#include \"missing.jsx\"");
|
|
let e = run_file(&mut s, &dir.join("main.jsx")).error.expect("an error");
|
|
assert!(e.message.contains("missing.jsx") && e.line == Some(2), "{e:?}");
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|
|
|
|
#[test]
|
|
fn hostile_includes_are_bounded_and_gated() {
|
|
let dir = tmp("hostile");
|
|
// A chain deeper than the limit is an error, not a stack overflow.
|
|
for i in 0..100 {
|
|
write(&dir.join(format!("chain/{i}.jsx")), &format!("#include \"{}.jsx\"", i + 1));
|
|
}
|
|
write(&dir.join("chain/100.jsx"), "1");
|
|
let mut s = session();
|
|
let e = run_file(&mut s, &dir.join("chain/0.jsx")).error.expect("an error");
|
|
assert!(e.message.contains("nested too deeply"), "{e:?}");
|
|
// Files outside the script's (and the project's) folder need the file preference.
|
|
write(&dir.join("outside.jsx"), "var secret = 7;");
|
|
write(&dir.join("app/main.jsx"), "#include \"../outside.jsx\"\nsecret");
|
|
let e = run_file(&mut s, &dir.join("app/main.jsx")).error.expect("blocked");
|
|
assert!(e.message.contains("Allow Scripts to Write Files"), "{e:?}");
|
|
// Code run without a file can't include relative to anything but the project's folder.
|
|
let e = run_code(&mut s, "#include \"outside.jsx\"", "console").error.expect("no folder");
|
|
assert!(e.message.contains("outside.jsx"), "{e:?}");
|
|
s.prefs.scripting.allow_scripts_write_files = true;
|
|
let o = run_file(&mut s, &dir.join("app/main.jsx"));
|
|
assert_eq!(o.result, json!(7), "{:?}", o.error);
|
|
let _ = std::fs::remove_dir_all(&dir);
|
|
}
|