From 27714d9fe3e2f363bfe7a70eb4657b527caa0903 Mon Sep 17 00:00:00 2001 From: Steven Cole Date: Fri, 14 Nov 2025 00:05:03 -0500 Subject: [PATCH 1/3] Bug fixes found with more complete coverage --- funcs.sh | 11 ++-- src/arguments_object/tests.rs | 50 +++++++++++++++++++ src/compiler/mod.rs | 18 ++----- src/compiler/tests.rs | 34 +++++++++++++ src/execution_context/tests.rs | 17 +++++++ src/parser/additive_operators/tests.rs | 21 ++++++++ .../arrow_function_definitions/tests.rs | 9 ++++ src/parser/continue_statement/mod.rs | 6 --- .../declarations_and_variables/tests.rs | 12 +++++ src/parser/expression_statement/tests.rs | 9 ++++ src/parser/if_statement/mod.rs | 4 +- src/parser/if_statement/tests.rs | 25 ++++++++++ src/parser/left_hand_side_expressions/mod.rs | 4 +- .../left_hand_side_expressions/tests.rs | 36 +++++++++++++ src/parser/mod.rs | 9 ++++ src/parser/primary_expressions/tests.rs | 16 ++++++ src/parser/scripts/tests.rs | 17 +++++++ src/parser/statements_and_declarations/mod.rs | 3 +- src/parser/testhelp.rs | 29 ++++++++--- src/parser/throw_statement/mod.rs | 7 ++- src/parser/update_expressions/tests.rs | 27 ++++++++++ src/values/mod.rs | 9 +--- t | 1 + 23 files changed, 328 insertions(+), 46 deletions(-) diff --git a/funcs.sh b/funcs.sh index 0bd3265e..862f62ff 100644 --- a/funcs.sh +++ b/funcs.sh @@ -1,3 +1,6 @@ +CARGO_PROFDATA=$(find $HOME/.rustup -name llvm-profdata) +CARGO_COV=$(find $HOME/.rustup -name llvm-cov) + function objects() { for file in $( \ LLVM_PROFILE_FILE="res-%m.profraw" RUSTFLAGS="-Cinstrument-coverage" cargo test --profile coverage --no-run --message-format=json 2> /dev/null | \ @@ -26,7 +29,7 @@ function tst() { RUST_BACKTRACE=1 LLVM_PROFILE_FILE="res-%m.profraw" RUSTFLAGS="-Cinstrument-coverage" cargo test --bin res --profile coverage $quiet -- "$@" local covstatus=$? if [ $covstatus -eq 0 ]; then - cargo profdata -- merge res-*.profraw --output="$output" + $CARGO_PROFDATA merge res-*.profraw --output="$output" fi cd $here return $covstatus @@ -37,7 +40,7 @@ function summary() { cd ~/*/rust-e262 local profile=res.profdata if [ $# -gt 0 ]; then profile="$1"; fi - cargo cov -- report --use-color --ignore-filename-regex='/rustc/|/\.cargo/|\.rustup/toolchains|/.*tests\.rs|/testhelp\.rs|/tests/' --instr-profile="$profile" $(objects) + $CARGO_COV report --use-color --ignore-filename-regex='/rustc/|/\.cargo/|\.rustup/toolchains|/.*tests\.rs|/testhelp\.rs|/tests/' --instr-profile="$profile" $(objects) cd $here } @@ -50,7 +53,7 @@ function z() { function s() { local here=$(pwd) cd ~/*/rust-e262 - cargo cov -- show \ + $CARGO_COV show \ --use-color \ --ignore-filename-regex='/rustc/|/\.cargo/|\.rustup/toolchains|/.*tests\.rs|/testhelp\.rs|/tests/' \ --instr-profile=res.profdata $(objects) \ @@ -95,7 +98,7 @@ function report() { shift done - cargo cov -- show \ + $CARGO_COV show \ $color \ --ignore-filename-regex='/rustc/|/\.cargo/|\.rustup/toolchains|/.*tests\.rs|/testhelp\.rs|/tests/' \ --instr-profile="$profile" $(objects) \ diff --git a/src/arguments_object/tests.rs b/src/arguments_object/tests.rs index a53b992e..252722b0 100644 --- a/src/arguments_object/tests.rs +++ b/src/arguments_object/tests.rs @@ -638,4 +638,54 @@ mod arguments_object { none_function!(to_map_obj); none_function!(to_regexp_object); none_function!(to_string_obj); + + #[test] + fn own_property_keys() { + setup_test_agent(); + let obj = make(); // adds 0, 1, 2, and 100 + + let to_prim = wks(WksId::ToPrimitive); + let species = wks(WksId::Species); + + obj.o + .define_own_property( + "60".into(), + PotentialPropertyDescriptor::new().value("q").writable(true).enumerable(true).configurable(true), + ) + .unwrap(); + obj.o + .define_own_property( + "6".into(), + PotentialPropertyDescriptor::new().value("s").writable(true).enumerable(true).configurable(true), + ) + .unwrap(); + obj.o + .define_own_property( + "zebra".into(), + PotentialPropertyDescriptor::new().value(0).writable(true).enumerable(true).configurable(true), + ) + .unwrap(); + obj.o + .define_own_property( + "alpha".into(), + PotentialPropertyDescriptor::new().value(1).writable(true).enumerable(true).configurable(true), + ) + .unwrap(); + obj.o + .define_own_property( + to_prim.clone().into(), + PotentialPropertyDescriptor::new().value(2).writable(true).enumerable(true).configurable(true), + ) + .unwrap(); + obj.o + .define_own_property( + species.clone().into(), + PotentialPropertyDescriptor::new().value(3).writable(true).enumerable(true).configurable(true), + ) + .unwrap(); + + let keys = obj.o.own_property_keys().unwrap(); + + assert_eq!(keys, vec!["0".into(), "1".into(), "2".into(), "6".into(), "60".into(), "100".into(), "zebra".into(), "alpha".into(), to_prim.into(), species.into()]); + } } diff --git a/src/compiler/mod.rs b/src/compiler/mod.rs index ddae2159..e371f866 100644 --- a/src/compiler/mod.rs +++ b/src/compiler/mod.rs @@ -412,18 +412,13 @@ impl fmt::Display for Insn { /// A compilation might leave a value on the runtime stack that could be a reference. We want to communicate back to the /// parent compilation step about whether that's possible or not. The values are "Might leave a reference on top of the /// stack" and "Will never leave a reference on the top of the stack". -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub(crate) enum RefResult { Maybe, + #[default] Never, } -impl Default for RefResult { - fn default() -> Self { - Self::Never - } -} - impl From for RefResult { fn from(src: bool) -> Self { if src { RefResult::Maybe } else { RefResult::Never } @@ -433,18 +428,13 @@ impl From for RefResult { /// A compilation might leave a value on top of the runtime stack that could be an error. We want to communicate back to /// the parent compilation step about whether that's possible or not. The values are "Might leave an abrupt completion /// on top of the stack" and "Will never leave an abrupt completion on top of the stack". -#[derive(Debug, Clone, Copy, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] pub(crate) enum AbruptResult { Maybe, + #[default] Never, } -impl Default for AbruptResult { - fn default() -> Self { - Self::Never - } -} - impl From for AbruptResult { fn from(src: CompilerStatusFlags) -> Self { src.can_be_abrupt diff --git a/src/compiler/tests.rs b/src/compiler/tests.rs index c6f17b64..f7f6a4cb 100644 --- a/src/compiler/tests.rs +++ b/src/compiler/tests.rs @@ -399,6 +399,12 @@ mod abrupt_result { fn from(item: impl Into) -> AbruptResult { item.into() } + + #[test_case(AbruptResult::Maybe => CompilerStatusFlags { can_be_abrupt: AbruptResult::Maybe, can_be_reference: RefResult::Never }; "AbruptResult::Maybe")] + #[test_case(AbruptResult::Never => CompilerStatusFlags { can_be_abrupt: AbruptResult::Never, can_be_reference: RefResult::Never }; "AbruptResult::Never")] + fn into_compiiler_status_flags(item: AbruptResult) -> CompilerStatusFlags { + item.into() + } } mod always_abrupt_result { @@ -422,6 +428,20 @@ mod always_abrupt_result { let item = AlwaysAbruptResult {}; assert!(item.maybe_abrupt()); } + + #[test] + fn into_compiiler_status_flags() { + let item = AlwaysAbruptResult {}; + let csf: CompilerStatusFlags = item.into(); + assert_eq!(csf, CompilerStatusFlags { can_be_abrupt: AbruptResult::Maybe, can_be_reference: RefResult::Never }); + } + + #[test] + fn into_abrupt_result() { + let item = AlwaysAbruptResult {}; + let ar: AbruptResult = item.into(); + assert_eq!(ar, AbruptResult::Maybe); + } } mod always_ref_result { @@ -439,6 +459,13 @@ mod always_ref_result { let cloned = item.clone(); assert!(matches!(cloned, AlwaysRefResult {})); } + + #[test] + fn into_compiiler_status_flags() { + let item = AlwaysRefResult {}; + let csf: CompilerStatusFlags = item.into(); + assert_eq!(csf, CompilerStatusFlags { can_be_abrupt: AbruptResult::Never, can_be_reference: RefResult::Maybe }); + } } mod always_abrupt_ref_result { @@ -456,6 +483,13 @@ mod always_abrupt_ref_result { let cloned = item.clone(); assert!(matches!(cloned, AlwaysAbruptRefResult {})); } + + #[test] + fn into_compiiler_status_flags() { + let item = AlwaysAbruptRefResult {}; + let csf: CompilerStatusFlags = item.into(); + assert_eq!(csf, CompilerStatusFlags { can_be_abrupt: AbruptResult::Maybe, can_be_reference: RefResult::Maybe }); + } } mod never_abrupt_ref_result { diff --git a/src/execution_context/tests.rs b/src/execution_context/tests.rs index c8abdce1..77b50b30 100644 --- a/src/execution_context/tests.rs +++ b/src/execution_context/tests.rs @@ -81,6 +81,23 @@ mod script_or_module { text.clone() } + #[test_case(|| { + let mut sr = ScriptRecord::new_empty(current_realm_record().unwrap()); + sr.text = String::from("I am a walrus"); + ScriptOrModule::Script(Rc::new(sr)) + } => "I am a walrus --- "; "script")] + #[test_case(|| ScriptOrModule::Module(Rc::new(ModuleRecord{})) => panics "not yet implemented"; "module")] + fn source_tree(maker: impl FnOnce() -> ScriptOrModule) -> String { + setup_test_agent(); + let som = maker(); + let tree = som.source_tree(); + if let SourceTree { text, ast: ParsedText::Script(script) } = tree { + format!("{text} --- {script}") + } else { + panic!("expected ParsedText::Script, got something else") + } + } + #[test_case( || { let mut sr = ScriptRecord::new_empty(current_realm_record().unwrap()); diff --git a/src/parser/additive_operators/tests.rs b/src/parser/additive_operators/tests.rs index 3697da70..cb504406 100644 --- a/src/parser/additive_operators/tests.rs +++ b/src/parser/additive_operators/tests.rs @@ -191,4 +191,25 @@ mod additive_expression { fn location(src: &str) -> Location { Maker::new(src).additive_expression().location() } + + #[test_case("a+call()" => false; "add")] + #[test_case("a-call()" => false; "subtract")] + #[test_case("call()" => true; "fall-thru")] + #[test_case("37" => false; "fall-thru, no call")] + fn has_call_in_tail_position(src: &str) -> bool { + let location = find_call(src); + Maker::new(src).additive_expression().has_call_in_tail_position(&location) + } + + #[test_case("a+b" => None; "location not in parse node")] + #[test_case("call()" => None; "fall-thru; location in node, but no function body")] + #[test_case("(function(){ return call(); })()" => ssome("return call ( ) ;"); "fall-thru; location in function body")] + #[test_case("(function(){ return call(); })() + 3" => ssome("return call ( ) ;"); "left-side add; location in function body")] + #[test_case("10 + (function(){ return call(); })()" => ssome("return call ( ) ;"); "right-side add; location in function body")] + #[test_case("(function(){ return call(); })() - 3" => ssome("return call ( ) ;"); "left-side subtract; location in function body")] + #[test_case("10 - (function(){ return call(); })()" => ssome("return call ( ) ;"); "right-side subtract; location in function body")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).additive_expression().body_containing_location(&location).map(|node| node.to_string()) + } } diff --git a/src/parser/arrow_function_definitions/tests.rs b/src/parser/arrow_function_definitions/tests.rs index 4ec2760e..7fc5455b 100644 --- a/src/parser/arrow_function_definitions/tests.rs +++ b/src/parser/arrow_function_definitions/tests.rs @@ -143,6 +143,15 @@ mod arrow_function { fn location(src: &str) -> Location { Maker::new(src).arrow_function().location() } + + #[test_case("x => 3" => None; "location not in parse node")] + #[test_case("x => { return call(); }" => ssome("return call ( ) ;"); "location in body")] + #[test_case("(x = call()) => { return x; }" => None; "location in params, but not in a body itself")] + #[test_case("(x = (function(z) { return call(); })()) => { return x; }" => ssome("return call ( ) ;"); "location in params")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).arrow_function().body_containing_location(&location).map(|node| node.to_string()) + } } // ARROW PARAMETERS diff --git a/src/parser/continue_statement/mod.rs b/src/parser/continue_statement/mod.rs index aa3f7f08..3defaa58 100644 --- a/src/parser/continue_statement/mod.rs +++ b/src/parser/continue_statement/mod.rs @@ -102,12 +102,6 @@ impl ContinueStatement { label.early_errors(errs, strict); } } - - #[expect(unused_variables)] - pub(crate) fn body_containing_location(&self, location: &Location) -> Option { - // Finds the FunctionBody, ConciseBody, or AsyncConciseBody that contains location most closely. - todo!() - } } #[cfg(test)] diff --git a/src/parser/declarations_and_variables/tests.rs b/src/parser/declarations_and_variables/tests.rs index e77baea3..af526d8c 100644 --- a/src/parser/declarations_and_variables/tests.rs +++ b/src/parser/declarations_and_variables/tests.rs @@ -1441,6 +1441,18 @@ mod array_binding_pattern { fn contains(src: &str, kind: ParseNodeKind) -> bool { Maker::new(src).array_binding_pattern().contains(kind) } + + #[test_case("[,,, /* call() */]" => None; "elisions only")] + #[test_case("[...a /* call() */]" => None; "binding rest element; no function body")] + #[test_case("[...[a=function(){return call();}]]" => ssome("return call ( ) ;"); "binding rest element; has function body")] + #[test_case("[a=function(){return call();},b]" => ssome("return call ( ) ;"); "binding element list; has function body on head")] + #[test_case("[a,b=function(){return call();}]" => ssome("return call ( ) ;"); "binding element list; has function body in tail")] + #[test_case("[a=function(){return call();},b,]" => ssome("return call ( ) ;"); "binding element list + empty rest; has function body on head")] + #[test_case("[a,...[b=function(){return call();}]]" => ssome("return call ( ) ;"); "binding element list + rest; has function body in rest")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).array_binding_pattern().body_containing_location(&location).map(|node| node.to_string()) + } } // BINDING REST PROPERTY diff --git a/src/parser/expression_statement/tests.rs b/src/parser/expression_statement/tests.rs index 0df2f015..2c1e3ca1 100644 --- a/src/parser/expression_statement/tests.rs +++ b/src/parser/expression_statement/tests.rs @@ -146,4 +146,13 @@ mod expression_statement { fn location(src: &str) -> Location { Maker::new(src).expression_statement().location() } + + #[test_case("a;" => None; "location outside of source")] + #[test_case("(function () { return call(); })();" => Some("return call ( ) ;".to_string()); "call in function body")] + #[test_case("call();" => None; "call, but not in body")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).expression_statement().body_containing_location(&location).map(|node| node.to_string()) + } + } diff --git a/src/parser/if_statement/mod.rs b/src/parser/if_statement/mod.rs index c38fdfe3..eb5b1217 100644 --- a/src/parser/if_statement/mod.rs +++ b/src/parser/if_statement/mod.rs @@ -232,11 +232,11 @@ impl IfStatement { pub(crate) fn body_containing_location(&self, location: &Location) -> Option { if self.location().contains(location) { match self { - IfStatement::WithElse(expression, statement, statement1, location) => expression + IfStatement::WithElse(expression, statement, statement1, ..) => expression .body_containing_location(location) .or_else(|| statement.body_containing_location(location)) .or_else(|| statement1.body_containing_location(location)), - IfStatement::WithoutElse(expression, statement, location) => expression + IfStatement::WithoutElse(expression, statement, ..) => expression .body_containing_location(location) .or_else(|| statement.body_containing_location(location)), } diff --git a/src/parser/if_statement/tests.rs b/src/parser/if_statement/tests.rs index 34183a0c..071fd2b1 100644 --- a/src/parser/if_statement/tests.rs +++ b/src/parser/if_statement/tests.rs @@ -286,4 +286,29 @@ mod if_statement { fn expression(src: &str) -> String { Maker::new(src).if_statement().expression().to_string() } + + #[test_case("if (expr) return call();" => true; "no else: call in tail position")] + #[test_case("if (expr) call();" => false; "no else: expression in tail position")] + #[test_case("if (expr) first; else return call();" => true; "with else: call in tail position in else clause")] + #[test_case("if (expr) return call(); else second;" => true; "with else: call in tail position in then clause")] + #[test_case("if (expr) first; else call();" => false; "with else: expression in tail position in else clause")] + #[test_case("if (expr) return 1 + call(); else second;" => false; "with else: complex in tail position in then clause")] + fn has_call_in_tail_position(src: &str) -> bool { + let location = find_call(src); + Maker::new(src).if_statement().has_call_in_tail_position(&location) + } + + #[test_case("if (true) return ((x)=>{ let z = x * 2; call() })(0);" => ssome("let z = x * 2 ; call ( ) ;"); "no else: call in fbody in stmt")] + #[test_case("if (true) return call();" => None; "no else: call not in a function body, in stmt")] + #[test_case("if (call()) return 1;" => None; "no else: call not in a function body, in expr")] + #[test_case("if ((function (){ return call(); })()) return 1;" => ssome("return call ( ) ;"); "no else: call in fbody in expr")] + #[test_case("if (true) return 1;" => None; "location not in sequence")] + #[test_case("if ((function() { return call(); })()) return 1; else return 2;" => ssome("return call ( ) ;"); "with else: call in fbody in expr")] + #[test_case("if (true) return ((function() { return call(); })()); else return 2;" => ssome("return call ( ) ;"); "with else: call in fbody in stmt1")] + #[test_case("if (true) return 1; else return ((function() { return call(); })());" => ssome("return call ( ) ;"); "with else: call in fbody in stmt2")] + #[test_case("if (true) return 1; else return ((function() { return (function() { return 'inner' + call(); })(); })());" => ssome("return 'inner' + call ( ) ;"); "with else: call in nested fbody in stmt2")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).if_statement().body_containing_location(&location).map(|node| node.to_string()) + } } diff --git a/src/parser/left_hand_side_expressions/mod.rs b/src/parser/left_hand_side_expressions/mod.rs index e8b4e99f..a52cafcf 100644 --- a/src/parser/left_hand_side_expressions/mod.rs +++ b/src/parser/left_hand_side_expressions/mod.rs @@ -1237,7 +1237,9 @@ impl ArgumentList { if self.location().contains(location) { match self { ArgumentList::FallThru(n) | ArgumentList::Dots(n, _) => n.body_containing_location(location), - ArgumentList::List(n, _) | ArgumentList::ListDots(n, _) => n.body_containing_location(location), + ArgumentList::List(n1, n2) | ArgumentList::ListDots(n1, n2) => { + n1.body_containing_location(location).or_else(|| n2.body_containing_location(location)) + } } } else { None diff --git a/src/parser/left_hand_side_expressions/tests.rs b/src/parser/left_hand_side_expressions/tests.rs index f737a20a..e0ff1e36 100644 --- a/src/parser/left_hand_side_expressions/tests.rs +++ b/src/parser/left_hand_side_expressions/tests.rs @@ -877,6 +877,17 @@ mod arguments { fn location(src: &str) -> Location { Maker::new(src).arguments().location() } + + + #[test_case("(/*call()*/)" => None; "empty")] + #[test_case("(a,b)" => None; "not in production")] + #[test_case("(call())" => None; "call not in body")] + #[test_case("((function(){ return call(); })())" => ssome("return call ( ) ;"); "call in list")] + #[test_case("((function(){ return call(); })(),)" => ssome("return call ( ) ;"); "call in list-comma")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).arguments().body_containing_location(&location).map(|node| node.to_string()) + } } // ARGUMENT LIST @@ -1091,6 +1102,31 @@ mod argument_list { fn contains_arguments(src: &str) -> bool { ArgumentList::parse(&mut newparser(src), Scanner::new(), true, true).unwrap().0.contains_arguments() } + + #[test_case(" a" => Location{ starting_line: 1, starting_column: 3, span: Span{ starting_index: 2, length: 1 }}; "fallthru")] + #[test_case(" ...a" => Location{ starting_line: 1, starting_column: 3, span: Span{ starting_index: 2, length: 4 }}; "dots")] + #[test_case(" a,b" => Location{ starting_line: 1, starting_column: 3, span: Span{ starting_index: 2, length: 3 }}; "list")] + #[test_case(" a,...b" => Location{ starting_line: 1, starting_column: 3, span: Span{ starting_index: 2, length: 6 }}; "list+dots")] + fn location(src: &str) -> Location { + Maker::new(src).argument_list_ast().0.location() + } + #[test_case("a" => None; "call outside of production")] + #[test_case("a=(function() { return call(); })()" => ssome("return call ( ) ;"); "fallthru, matches")] + #[test_case("a=call()" => None; "fallthru, no match")] + #[test_case("...a=(function() { return call(); })()" => ssome("return call ( ) ;"); "dots, matches")] + #[test_case("...a=call()" => None; "dots, no match")] + #[test_case("a,b=(function() { return call(); })()" => ssome("return call ( ) ;"); "list, match in assignment expression")] + #[test_case("a,b=call()" => None; "list, no match (right)")] + #[test_case("a=(function() { return call(); })(), b" => ssome("return call ( ) ;"); "list, match in left list")] + #[test_case("a=call(), b" => None; "list, no match in left list")] + #[test_case("a,...b=(function() { return call(); })()" => ssome("return call ( ) ;"); "list+dots, match in assignment expression")] + #[test_case("a,...b=call()" => None; "list+dots, no match")] + #[test_case("a=(function() { return call(); })(), ...b" => ssome("return call ( ) ;"); "list+dots, match in left list")] + #[test_case("a=call(), ...b" => None; "list+dots, no match in left list")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).argument_list_ast().0.body_containing_location(&location).map(|node| node.to_string()) + } } // NEW EXPRESSION diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 9e9da6dc..6219127a 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1833,6 +1833,15 @@ pub(crate) enum ContainingBody { //AsyncConciseBody(Rc), // Add back when Async goes in } +impl fmt::Display for ContainingBody { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + ContainingBody::FunctionBody(node) => node.fmt(f), + ContainingBody::ConciseBody(node) => node.fmt(f), + } + } +} + pub(crate) mod additive_operators; pub(crate) mod arrow_function_definitions; pub(crate) mod assignment_operators; diff --git a/src/parser/primary_expressions/tests.rs b/src/parser/primary_expressions/tests.rs index d8ff0882..560f95c3 100644 --- a/src/parser/primary_expressions/tests.rs +++ b/src/parser/primary_expressions/tests.rs @@ -1630,6 +1630,22 @@ mod array_literal { fn location(src: &str) -> Location { Maker::new(src).array_literal().location() } + + #[test_case("[]" => None; "location not in body")] + #[test_case("[/* call() */]" => None; "empty list")] + #[test_case("[call()]" => None; "list, but no function body")] + #[test_case("[function () { return call(); }]" => ssome("return call ( ) ;"); "function body in list")] + #[test_case("[function () { return call(); },,,]" => ssome("return call ( ) ;"); "function body in list+elision")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).array_literal().body_containing_location(&location).map(|node| node.to_string()) + } + + #[test_case("[]" => "[ ]"; "empty")] + fn into_primary_expression(src: &str) -> String { + let item = Maker::new(src).array_literal(); + PrimaryExpression::from(item).to_string() + } } // INITIALIZER diff --git a/src/parser/scripts/tests.rs b/src/parser/scripts/tests.rs index 14b2ba77..227e5365 100644 --- a/src/parser/scripts/tests.rs +++ b/src/parser/scripts/tests.rs @@ -185,6 +185,15 @@ mod script { fn location(src: &str) -> Location { Maker::new(src).script().location() } + + #[test_case("" => None; "no body")] + #[test_case("console.log('hello');" => None; "call not in script")] + #[test_case("function f(){ call(); return 34; }" => Some("call ( ) ; return 34 ;".to_string()); "call in function body")] + #[test_case("call();" => None; "call not in function body")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).script().body_containing_location(&location).map(|node| node.to_string()) + } } // SCRIPT BODY @@ -333,4 +342,12 @@ mod script_body { let js_names = names.iter().map(|&name| JSString::from(name)).collect::>(); Maker::new(src).script_body().all_private_identifiers_valid(js_names.as_slice()) } + + #[test_case("a;" => None; "call not in scope")] + #[test_case("function f(){ call(); return 34; }" => Some("call ( ) ; return 34 ;".to_string()); "call in scope")] + #[test_case("call();" => None; "call not in function body")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).script_body().body_containing_location(&location).map(|node| node.to_string()) + } } diff --git a/src/parser/statements_and_declarations/mod.rs b/src/parser/statements_and_declarations/mod.rs index 37290dd5..e608e6b9 100644 --- a/src/parser/statements_and_declarations/mod.rs +++ b/src/parser/statements_and_declarations/mod.rs @@ -454,14 +454,13 @@ impl Statement { Statement::Expression(node) => node.body_containing_location(location), Statement::If(node) => node.body_containing_location(location), Statement::Breakable(node) => node.body_containing_location(location), - Statement::Continue(node) => node.body_containing_location(location), Statement::Break(node) => node.body_containing_location(location), Statement::Return(node) => node.body_containing_location(location), Statement::With(node) => node.body_containing_location(location), Statement::Labelled(node) => node.body_containing_location(location), Statement::Throw(node) => node.body_containing_location(location), Statement::Try(node) => node.body_containing_location(location), - Statement::Empty(_) | Statement::Debugger(_) => None, + Statement::Continue(_) | Statement::Empty(_) | Statement::Debugger(_) => None, } } else { None diff --git a/src/parser/testhelp.rs b/src/parser/testhelp.rs index e8f861b9..a585ffb4 100644 --- a/src/parser/testhelp.rs +++ b/src/parser/testhelp.rs @@ -141,14 +141,14 @@ impl<'a> Maker<'a> { // Self { in_flag, ..self } // } - // /// Set the `return_flag` in the maker object. - // /// - // /// `true` means that `return` statements are currently allowed (generally within function bodies); `false` means - // /// they are not. - // #[must_use] - // pub(crate) fn return_ok(self, return_flag: bool) -> Self { - // Self { return_flag, ..self } - // } + /// Set the `return_flag` in the maker object. + /// + /// `true` means that `return` statements are currently allowed (generally within function bodies); `false` means + /// they are not. + #[must_use] + pub(crate) fn return_ok(self, return_flag: bool) -> Self { + Self { return_flag, ..self } + } /// Set the `tagged` flag in the maker object. /// @@ -2843,6 +2843,19 @@ impl<'a> Maker<'a> { //} } +pub(crate) fn find_call(src: &str) -> Location { + let loc_default = Location { + starting_line: 10, + starting_column: 1, + span: Span { starting_index: src.len(), length: 6 }, + }; + src.find("call()").map_or(loc_default, |starting_index| Location { + starting_line: 1, + starting_column: u32::try_from(starting_index).unwrap() + 1, + span: Span { starting_index, length: 6 }, + }) +} + pub(crate) const PACKAGE_NOT_ALLOWED: &str = "‘package’ not allowed as an identifier in strict mode"; pub(crate) const INTERFACE_NOT_ALLOWED: &str = "‘interface’ not allowed as an identifier in strict mode"; pub(crate) const IMPLEMENTS_NOT_ALLOWED: &str = "‘implements’ not allowed as an identifier in strict mode"; diff --git a/src/parser/throw_statement/mod.rs b/src/parser/throw_statement/mod.rs index 495fd84c..f0ffa327 100644 --- a/src/parser/throw_statement/mod.rs +++ b/src/parser/throw_statement/mod.rs @@ -90,10 +90,13 @@ impl ThrowStatement { self.exp.early_errors(errs, strict); } - #[expect(unused_variables)] pub(crate) fn body_containing_location(&self, location: &Location) -> Option { // Finds the FunctionBody, ConciseBody, or AsyncConciseBody that contains location most closely. - todo!() + if self.location().contains(location) { + self.exp.body_containing_location(location) + } else { + None + } } } diff --git a/src/parser/update_expressions/tests.rs b/src/parser/update_expressions/tests.rs index 1c1cb15c..74ebf759 100644 --- a/src/parser/update_expressions/tests.rs +++ b/src/parser/update_expressions/tests.rs @@ -299,4 +299,31 @@ mod update_expression { fn location(src: &str) -> Location { Maker::new(src).update_expression().location() } + + #[test_case("call++" => false; "postinc, no")] + #[test_case("++call" => false; "preinc, no")] + #[test_case("call--" => false; "postdec, no")] + #[test_case("--call" => false; "predec, no")] + #[test_case("call()" => true; "fall-thru, yes")] + #[test_case("432" => false; "fall-thru, no")] + fn has_call_in_tail_position(src: &str) -> bool { + let location = find_call(src); + Maker::new(src).update_expression().has_call_in_tail_position(&location) + } + + #[test_case("(function () { return call(); })()" => Some("return call ( ) ;".to_string()); "fall-thru; call in function body")] + #[test_case("call()" => None; "fall-thru; call, but not in body")] + #[test_case("a++" => None; "call not in expression")] + #[test_case("(function () { return call(); })()++" => Some("return call ( ) ;".to_string()); "postinc; call in function body")] + #[test_case("call()++" => None; "postinc; call, but not in body")] + #[test_case("(function () { return call(); })()--" => Some("return call ( ) ;".to_string()); "postdec; call in function body")] + #[test_case("call()--" => None; "postdec; call, but not in body")] + #[test_case("++(function () { return call(); })()" => Some("return call ( ) ;".to_string()); "preinc; call in function body")] + #[test_case("++call()" => None; "preinc; call, but not in body")] + #[test_case("--(function () { return call(); })()" => Some("return call ( ) ;".to_string()); "predec; call in function body")] + #[test_case("--call()" => None; "predec; call, but not in body")] + fn body_containing_location(src: &str) -> Option { + let location = find_call(src); + Maker::new(src).return_ok(true).update_expression().body_containing_location(&location).map(|node| node.to_string()) + } } diff --git a/src/values/mod.rs b/src/values/mod.rs index ecc83d0f..2398fd44 100644 --- a/src/values/mod.rs +++ b/src/values/mod.rs @@ -57,8 +57,9 @@ impl PartialEq for PrimitiveValue { impl Eq for PrimitiveValue {} -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub(crate) enum ECMAScriptValue { + #[default] Undefined, Null, Boolean(bool), @@ -398,12 +399,6 @@ impl TryFrom for Rc { } } -impl Default for ECMAScriptValue { - fn default() -> Self { - Self::Undefined - } -} - #[derive(Debug, Clone, PartialEq, Eq, Hash)] pub(crate) enum PropertyKey { String(JSString), diff --git a/t b/t index 84370eb1..6da66127 100755 --- a/t +++ b/t @@ -408,6 +408,7 @@ test_defs[CatchParameter]="CatchParameter catch_parameter parser::try_statement" test_defs[UnaryExpression]="UnaryExpression unary_expression parser::unary_operators" test_defs[UpdateExpression]="UpdateExpression update_expression parser::update_expressions" test_defs[WithStatement]="WithStatement with_statement parser::with_statement" +test_defs[AdditiveExpression]="AdditiveExpression additive_expression parser::additive_operators" test_defs[ParsedText]="ParsedText parsed_text parser" test_defs[ParsedItem]="ParsedItem parsed_item parser" test_defs[direct_parse]="direct_parse direct_parse parser" From 9d2bda65286ec603e7feabfc00045899d202cc05 Mon Sep 17 00:00:00 2001 From: Steven Cole Date: Fri, 14 Nov 2025 00:08:44 -0500 Subject: [PATCH 2/3] formatting --- src/arguments_object/tests.rs | 16 +++++++++++++++- src/parser/additive_operators/tests.rs | 6 +++++- src/parser/arrow_function_definitions/tests.rs | 6 +++++- src/parser/expression_statement/tests.rs | 7 +++++-- src/parser/left_hand_side_expressions/tests.rs | 8 ++++++-- src/parser/testhelp.rs | 7 ++----- src/parser/throw_statement/mod.rs | 6 +----- src/parser/update_expressions/tests.rs | 6 +++++- 8 files changed, 44 insertions(+), 18 deletions(-) diff --git a/src/arguments_object/tests.rs b/src/arguments_object/tests.rs index 252722b0..7ea8cde1 100644 --- a/src/arguments_object/tests.rs +++ b/src/arguments_object/tests.rs @@ -686,6 +686,20 @@ mod arguments_object { let keys = obj.o.own_property_keys().unwrap(); - assert_eq!(keys, vec!["0".into(), "1".into(), "2".into(), "6".into(), "60".into(), "100".into(), "zebra".into(), "alpha".into(), to_prim.into(), species.into()]); + assert_eq!( + keys, + vec![ + "0".into(), + "1".into(), + "2".into(), + "6".into(), + "60".into(), + "100".into(), + "zebra".into(), + "alpha".into(), + to_prim.into(), + species.into() + ] + ); } } diff --git a/src/parser/additive_operators/tests.rs b/src/parser/additive_operators/tests.rs index cb504406..c704d1b9 100644 --- a/src/parser/additive_operators/tests.rs +++ b/src/parser/additive_operators/tests.rs @@ -210,6 +210,10 @@ mod additive_expression { #[test_case("10 - (function(){ return call(); })()" => ssome("return call ( ) ;"); "right-side subtract; location in function body")] fn body_containing_location(src: &str) -> Option { let location = find_call(src); - Maker::new(src).return_ok(true).additive_expression().body_containing_location(&location).map(|node| node.to_string()) + Maker::new(src) + .return_ok(true) + .additive_expression() + .body_containing_location(&location) + .map(|node| node.to_string()) } } diff --git a/src/parser/arrow_function_definitions/tests.rs b/src/parser/arrow_function_definitions/tests.rs index 7fc5455b..3df96ea3 100644 --- a/src/parser/arrow_function_definitions/tests.rs +++ b/src/parser/arrow_function_definitions/tests.rs @@ -150,7 +150,11 @@ mod arrow_function { #[test_case("(x = (function(z) { return call(); })()) => { return x; }" => ssome("return call ( ) ;"); "location in params")] fn body_containing_location(src: &str) -> Option { let location = find_call(src); - Maker::new(src).return_ok(true).arrow_function().body_containing_location(&location).map(|node| node.to_string()) + Maker::new(src) + .return_ok(true) + .arrow_function() + .body_containing_location(&location) + .map(|node| node.to_string()) } } diff --git a/src/parser/expression_statement/tests.rs b/src/parser/expression_statement/tests.rs index 2c1e3ca1..a236dc1f 100644 --- a/src/parser/expression_statement/tests.rs +++ b/src/parser/expression_statement/tests.rs @@ -152,7 +152,10 @@ mod expression_statement { #[test_case("call();" => None; "call, but not in body")] fn body_containing_location(src: &str) -> Option { let location = find_call(src); - Maker::new(src).return_ok(true).expression_statement().body_containing_location(&location).map(|node| node.to_string()) + Maker::new(src) + .return_ok(true) + .expression_statement() + .body_containing_location(&location) + .map(|node| node.to_string()) } - } diff --git a/src/parser/left_hand_side_expressions/tests.rs b/src/parser/left_hand_side_expressions/tests.rs index e0ff1e36..1a303a45 100644 --- a/src/parser/left_hand_side_expressions/tests.rs +++ b/src/parser/left_hand_side_expressions/tests.rs @@ -878,7 +878,6 @@ mod arguments { Maker::new(src).arguments().location() } - #[test_case("(/*call()*/)" => None; "empty")] #[test_case("(a,b)" => None; "not in production")] #[test_case("(call())" => None; "call not in body")] @@ -1125,7 +1124,12 @@ mod argument_list { #[test_case("a=call(), ...b" => None; "list+dots, no match in left list")] fn body_containing_location(src: &str) -> Option { let location = find_call(src); - Maker::new(src).return_ok(true).argument_list_ast().0.body_containing_location(&location).map(|node| node.to_string()) + Maker::new(src) + .return_ok(true) + .argument_list_ast() + .0 + .body_containing_location(&location) + .map(|node| node.to_string()) } } diff --git a/src/parser/testhelp.rs b/src/parser/testhelp.rs index a585ffb4..43815937 100644 --- a/src/parser/testhelp.rs +++ b/src/parser/testhelp.rs @@ -2844,11 +2844,8 @@ impl<'a> Maker<'a> { } pub(crate) fn find_call(src: &str) -> Location { - let loc_default = Location { - starting_line: 10, - starting_column: 1, - span: Span { starting_index: src.len(), length: 6 }, - }; + let loc_default = + Location { starting_line: 10, starting_column: 1, span: Span { starting_index: src.len(), length: 6 } }; src.find("call()").map_or(loc_default, |starting_index| Location { starting_line: 1, starting_column: u32::try_from(starting_index).unwrap() + 1, diff --git a/src/parser/throw_statement/mod.rs b/src/parser/throw_statement/mod.rs index f0ffa327..68d770e7 100644 --- a/src/parser/throw_statement/mod.rs +++ b/src/parser/throw_statement/mod.rs @@ -92,11 +92,7 @@ impl ThrowStatement { pub(crate) fn body_containing_location(&self, location: &Location) -> Option { // Finds the FunctionBody, ConciseBody, or AsyncConciseBody that contains location most closely. - if self.location().contains(location) { - self.exp.body_containing_location(location) - } else { - None - } + if self.location().contains(location) { self.exp.body_containing_location(location) } else { None } } } diff --git a/src/parser/update_expressions/tests.rs b/src/parser/update_expressions/tests.rs index 74ebf759..91c22377 100644 --- a/src/parser/update_expressions/tests.rs +++ b/src/parser/update_expressions/tests.rs @@ -324,6 +324,10 @@ mod update_expression { #[test_case("--call()" => None; "predec; call, but not in body")] fn body_containing_location(src: &str) -> Option { let location = find_call(src); - Maker::new(src).return_ok(true).update_expression().body_containing_location(&location).map(|node| node.to_string()) + Maker::new(src) + .return_ok(true) + .update_expression() + .body_containing_location(&location) + .map(|node| node.to_string()) } } From 9be41823e84d6979c415eec02a5cd8a4c3f43505 Mon Sep 17 00:00:00 2001 From: Steven Cole Date: Fri, 14 Nov 2025 00:15:32 -0500 Subject: [PATCH 3/3] lints for the test code --- src/object/objtests.rs | 8 ++------ src/parser/testhelp.rs | 2 +- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/src/object/objtests.rs b/src/object/objtests.rs index 3509fa23..9bb84d24 100644 --- a/src/object/objtests.rs +++ b/src/object/objtests.rs @@ -1314,17 +1314,13 @@ fn validate_and_apply_property_descriptor_06() { ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// // ONE BIG TEST to check all different PotentialPropertyDescriptors against all different existing PropertyDescriptors. // (There are 23,328 different tests included in this.) -#[derive(PartialEq, Eq)] +#[derive(PartialEq, Eq, Default)] enum Stage { + #[default] Data, Accessor, Done, } -impl Default for Stage { - fn default() -> Self { - Self::Data - } -} #[expect(clippy::struct_excessive_bools)] #[derive(Default)] struct VAPDIter { diff --git a/src/parser/testhelp.rs b/src/parser/testhelp.rs index 43815937..4ddc8b0f 100644 --- a/src/parser/testhelp.rs +++ b/src/parser/testhelp.rs @@ -536,7 +536,7 @@ impl<'a> Maker<'a> { ) .unwrap() .0; - (node.clone(), SourceTree { text: source.to_string(), ast: ParsedText::AsyncFunctionDeclaration(node) }) + (node.clone(), SourceTree { text: source.clone(), ast: ParsedText::AsyncFunctionDeclaration(node) }) } /// Use the configs in the [`Maker`] object to make a [`AsyncFunctionExpression`] parse node. pub(crate) fn async_function_expression(self) -> Rc {