Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion compatibility/support-matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -492,7 +492,7 @@
"test:opy-rs-differential",
"fixtures:real-world/overpy-meipocalypse"
],
"notes": "Issue #6 + #7 part B. `#!define name(args) __script__(\"path.js\")` parsed (root-relative script resolution, script-not-found at the define site like the reference's ENOENT), expanded through opy_macro_js::MacroRuntime with the reference's var-injection ABI, indentation rule, and string-only completion contract — compile-time expansion executes and is source-supported. `#!postCompileHook` is parsed, validated, and recorded (duplicate rejection); compiler execution against final Workshop text is tracked by hooks/post-compile-workshop. Structured script-* diagnostics carry script provenance. Catalog constants remain lowering-dependent; `#!require` does not exist in the pinned reference."
"notes": "Issue #6 + #7 part B. `#!define name(args) __script__(\"path.js\")` parsed (script resolution relative to the definition file, script-not-found at the define site like the reference's ENOENT), expanded through opy_macro_js::MacroRuntime with the reference's var-injection ABI, indentation rule, and string-only completion contract — compile-time expansion executes and is source-supported. `#!postCompileHook` is parsed, validated, and recorded (duplicate rejection); compiler execution against final Workshop text is tracked by hooks/post-compile-workshop. Structured script-* diagnostics carry script provenance. Catalog constants remain lowering-dependent; `#!require` does not exist in the pinned reference."
},
{
"id": "hooks/post-compile-workshop",
Expand Down
2 changes: 2 additions & 0 deletions crates/opy-rs/src/compiler/integration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ mod issue_142_preprocessing;
mod issue_144_builtin_surface;
#[path = "tests/issue_145_lowering.rs"]
mod issue_145_lowering;
#[path = "tests/issue_161_preprocessing.rs"]
mod issue_161_preprocessing;
#[path = "tests/issue_42_oracle.rs"]
mod issue_42_oracle;
#[path = "tests/issue_46_oracle.rs"]
Expand Down
87 changes: 87 additions & 0 deletions crates/opy-rs/src/compiler/tests/issue_161_preprocessing.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
//! Public project-composition coverage for issue #161.

use std::collections::BTreeMap;
use std::path::{Path, PathBuf};

use crate::compile;

fn fixture_dir() -> PathBuf {
Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/issue-161-project")
}

#[test]
fn directory_includes_are_sorted_and_preserve_nested_source_ownership() {
let dir = fixture_dir();
let source = std::fs::read_to_string(dir.join("main.opy")).unwrap();
let hir = compile(&source, "main.opy", &dir).expect("directory include must compile");

let paths = hir
.files
.iter()
.map(|file| file.path.as_str())
.collect::<Vec<_>>();
assert_eq!(
paths,
[
"main.opy",
"modules/a.opy",
"modules/nested/first.opy",
"modules/nested/scripted.opy",
"modules/rules.opy",
]
);
let names = hir
.rules
.iter()
.filter_map(|entry| match entry {
crate::hir::RuleEntry::Rule(rule) => Some(rule.name.as_str()),
crate::hir::RuleEntry::SubroutineDef { .. } => None,
})
.collect::<Vec<_>>();
assert_eq!(names, ["nested", "a", "rules", "main"]);
}

#[test]
fn included_script_macro_resolves_relative_to_its_definition_file() {
let dir = fixture_dir();
let source = std::fs::read_to_string(dir.join("main.opy")).unwrap();
let source = format!("{source}\nrule \"script\":\n @Event global\n A = addFive(1)\n");
let hir = compile(&source, "main.opy", &dir).expect("included script macro must compile");
assert!(hir.dump().contains("assign A = 6"), "{}", hir.dump());
}

#[test]
fn included_script_macro_errors_keep_the_resolved_script_provenance() {
let dir = fixture_dir();
let source = std::fs::read_to_string(dir.join("main.opy")).unwrap();
let source = format!("{source}\nrule \"script error\":\n @Event global\n A = fail(1)\n");
let error = crate::compile(&source, "main.opy", &dir).expect_err("script must fail");
assert_eq!(error.code, "script-error");
assert!(
error.message.contains("modules/nested/scripts/fail.js"),
"{}",
error.message
);
}

#[test]
fn included_script_macro_reads_an_open_document_overlay() {
let dir = fixture_dir();
let source = std::fs::read_to_string(dir.join("main.opy")).unwrap();
let source =
format!("{source}\nrule \"script overlay\":\n @Event global\n A = addFive(1)\n");
let overlay = BTreeMap::from([(
"modules/nested/scripts/add.js".to_string(),
"(value + 7).toString();".to_string(),
)]);
let hir = crate::compile_with_overlay(&source, "main.opy", &dir, &overlay)
.expect("included script macro must read the overlay");
assert!(hir.dump().contains("assign A = 8"), "{}", hir.dump());
}

#[test]
fn empty_macro_replacement_is_a_preprocessing_error() {
let error = crate::preprocess::preprocess("#!define EMPTY\n", "main.opy", Path::new("."))
.expect_err("empty macro replacements must be rejected");
assert_eq!(error.code, "define-invalid");
}
2 changes: 1 addition & 1 deletion crates/opy-rs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ pub struct CompileOutcome {
/// frontend never fabricates a Workshop payload.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PostCompileHookRecord {
/// The script path as declared (root-relative).
/// The resolved project-relative script path.
pub script: String,
/// The resolved script source, retained for the backend hook ABI.
pub source: String,
Expand Down
Loading