use super::*;
#[test]
fn parse_this() {
assert_eq!(SubjectExpr::parse("$this"), SubjectExpr::This);
}
#[test]
fn parse_self() {
assert_eq!(SubjectExpr::parse("self"), SubjectExpr::SelfKw);
}
#[test]
fn parse_static() {
assert_eq!(SubjectExpr::parse("static"), SubjectExpr::StaticKw);
}
#[test]
fn parse_parent() {
assert_eq!(SubjectExpr::parse("parent"), SubjectExpr::Parent);
}
#[test]
fn parse_bare_variable() {
assert_eq!(
SubjectExpr::parse("$user"),
SubjectExpr::Variable("$user".to_string())
);
}
#[test]
fn parse_bare_variable_underscore() {
assert_eq!(
SubjectExpr::parse("$my_var"),
SubjectExpr::Variable("$my_var".to_string())
);
}
#[test]
fn parse_bare_class_name() {
assert_eq!(
SubjectExpr::parse("User"),
SubjectExpr::ClassName("User".to_string())
);
}
#[test]
fn parse_fqn_class_name() {
assert_eq!(
SubjectExpr::parse("App\\Models\\User"),
SubjectExpr::ClassName("App\\Models\\User".to_string())
);
}
#[test]
fn parse_this_property() {
assert_eq!(
SubjectExpr::parse("$this->name"),
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::This),
property: "name".to_string(),
}
);
}
#[test]
fn parse_nullsafe_this_property() {
assert_eq!(
SubjectExpr::parse("$this?->name"),
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::This),
property: "name".to_string(),
}
);
}
#[test]
fn parse_var_property() {
assert_eq!(
SubjectExpr::parse("$user->address"),
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::Variable("$user".to_string())),
property: "address".to_string(),
}
);
}
#[test]
fn parse_nested_property_chain() {
assert_eq!(
SubjectExpr::parse("$user->address->city"),
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::Variable("$user".to_string())),
property: "address".to_string(),
}),
property: "city".to_string(),
}
);
}
#[test]
fn parse_nullsafe_var_property() {
assert_eq!(
SubjectExpr::parse("$user?->address"),
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::Variable("$user".to_string())),
property: "address".to_string(),
}
);
}
#[test]
fn parse_static_access_enum_case() {
assert_eq!(
SubjectExpr::parse("Status::Active"),
SubjectExpr::StaticAccess {
class: "Status".to_string(),
member: "Active".to_string(),
}
);
}
#[test]
fn parse_static_access_constant() {
assert_eq!(
SubjectExpr::parse("MyClass::SOME_CONST"),
SubjectExpr::StaticAccess {
class: "MyClass".to_string(),
member: "SOME_CONST".to_string(),
}
);
}
#[test]
fn parse_function_call_no_args() {
assert_eq!(
SubjectExpr::parse("app()"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::FunctionCall("app".to_string())),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_function_call_with_args() {
assert_eq!(
SubjectExpr::parse("app(User::class)"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::FunctionCall("app".to_string())),
args_text: "User::class".to_string(),
}
);
}
#[test]
fn parse_method_call() {
assert_eq!(
SubjectExpr::parse("$this->getUser()"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::This),
method: "getUser".to_string(),
}),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_var_method_call() {
assert_eq!(
SubjectExpr::parse("$service->process()"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::Variable("$service".to_string())),
method: "process".to_string(),
}),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_static_method_call() {
assert_eq!(
SubjectExpr::parse("User::find(1)"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::StaticMethodCall {
class: "User".to_string(),
method: "find".to_string(),
}),
args_text: "1".to_string(),
}
);
}
#[test]
fn parse_self_static_method_call() {
assert_eq!(
SubjectExpr::parse("self::create()"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::StaticMethodCall {
class: "self".to_string(),
method: "create".to_string(),
}),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_parent_method_call() {
assert_eq!(
SubjectExpr::parse("parent::build()"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::StaticMethodCall {
class: "parent".to_string(),
method: "build".to_string(),
}),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_chained_method_call() {
let parsed = SubjectExpr::parse("$this->getFactory()->create()");
assert_eq!(
parsed,
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::This),
method: "getFactory".to_string(),
}),
args_text: "".to_string(),
}),
method: "create".to_string(),
}),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_chained_method_then_property() {
assert_eq!(
SubjectExpr::parse("$this->getFactory()->config"),
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::This),
method: "getFactory".to_string(),
}),
args_text: "".to_string(),
}),
property: "config".to_string(),
}
);
}
#[test]
fn parse_static_call_chain_then_property() {
let parsed = SubjectExpr::parse("BlogAuthor::whereIn('id', [1])->first()->posts");
assert_eq!(
parsed,
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::StaticMethodCall {
class: "BlogAuthor".to_string(),
method: "whereIn".to_string(),
}),
args_text: "'id', [1]".to_string(),
}),
method: "first".to_string(),
}),
args_text: "".to_string(),
}),
property: "posts".to_string(),
}
);
}
#[test]
fn parse_nested_call_args() {
assert_eq!(
SubjectExpr::parse("Environment::get(self::country())"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::StaticMethodCall {
class: "Environment".to_string(),
method: "get".to_string(),
}),
args_text: "self::country()".to_string(),
}
);
}
#[test]
fn parse_new_expression_bare() {
assert_eq!(
SubjectExpr::parse("new Builder()"),
SubjectExpr::NewExpr {
class_name: "Builder".to_string(),
}
);
}
#[test]
fn parse_new_expression_parenthesized() {
assert_eq!(
SubjectExpr::parse("(new Builder())"),
SubjectExpr::NewExpr {
class_name: "Builder".to_string(),
}
);
}
#[test]
fn parse_new_expression_no_parens() {
assert_eq!(
SubjectExpr::parse("(new Builder)"),
SubjectExpr::NewExpr {
class_name: "Builder".to_string(),
}
);
}
#[test]
fn parse_new_expression_fqn() {
assert_eq!(
SubjectExpr::parse("(new \\App\\Builder())"),
SubjectExpr::NewExpr {
class_name: "App\\Builder".to_string(),
}
);
}
#[test]
fn parse_variable_call() {
assert_eq!(
SubjectExpr::parse("$fn()"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::Variable("$fn".to_string())),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_variable_call_with_args() {
assert_eq!(
SubjectExpr::parse("$fn(42, 'hello')"),
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::Variable("$fn".to_string())),
args_text: "42, 'hello'".to_string(),
}
);
}
#[test]
fn parse_variable_string_key_access() {
assert_eq!(
SubjectExpr::parse("$response['items']"),
SubjectExpr::ArrayAccess {
base: Box::new(SubjectExpr::Variable("$response".to_string())),
segments: vec![BracketSegment::StringKey("items".to_string())],
}
);
}
#[test]
fn parse_variable_element_access() {
assert_eq!(
SubjectExpr::parse("$list[0]"),
SubjectExpr::ArrayAccess {
base: Box::new(SubjectExpr::Variable("$list".to_string())),
segments: vec![BracketSegment::ElementAccess],
}
);
}
#[test]
fn parse_variable_chained_bracket_access() {
assert_eq!(
SubjectExpr::parse("$response['items'][0]"),
SubjectExpr::ArrayAccess {
base: Box::new(SubjectExpr::Variable("$response".to_string())),
segments: vec![
BracketSegment::StringKey("items".to_string()),
BracketSegment::ElementAccess,
],
}
);
}
#[test]
fn parse_variable_empty_bracket() {
assert_eq!(
SubjectExpr::parse("$arr[]"),
SubjectExpr::ArrayAccess {
base: Box::new(SubjectExpr::Variable("$arr".to_string())),
segments: vec![BracketSegment::ElementAccess],
}
);
}
#[test]
fn parse_inline_array_literal() {
assert_eq!(
SubjectExpr::parse("[Customer::first()][0]"),
SubjectExpr::InlineArray {
elements: vec!["Customer::first()".to_string()],
index_segments: vec![BracketSegment::ElementAccess],
}
);
}
#[test]
fn parse_inline_array_literal_multiple_elements() {
assert_eq!(
SubjectExpr::parse("[$a, $b][0]"),
SubjectExpr::InlineArray {
elements: vec!["$a".to_string(), "$b".to_string()],
index_segments: vec![BracketSegment::ElementAccess],
}
);
}
#[test]
fn is_self_like_keywords() {
assert!(SubjectExpr::This.is_self_like());
assert!(SubjectExpr::SelfKw.is_self_like());
assert!(SubjectExpr::StaticKw.is_self_like());
}
#[test]
fn is_self_like_non_keywords() {
assert!(!SubjectExpr::Parent.is_self_like());
assert!(!SubjectExpr::Variable("$x".to_string()).is_self_like());
assert!(!SubjectExpr::ClassName("User".to_string()).is_self_like());
}
#[test]
fn round_trip_this() {
assert_eq!(SubjectExpr::parse("$this").to_subject_text(), "$this");
}
#[test]
fn round_trip_self() {
assert_eq!(SubjectExpr::parse("self").to_subject_text(), "self");
}
#[test]
fn round_trip_variable() {
assert_eq!(SubjectExpr::parse("$user").to_subject_text(), "$user");
}
#[test]
fn round_trip_property_chain() {
assert_eq!(
SubjectExpr::parse("$this->name").to_subject_text(),
"$this->name"
);
}
#[test]
fn round_trip_nested_property_chain() {
assert_eq!(
SubjectExpr::parse("$user->address->city").to_subject_text(),
"$user->address->city"
);
}
#[test]
fn round_trip_function_call() {
assert_eq!(SubjectExpr::parse("app()").to_subject_text(), "app()");
}
#[test]
fn round_trip_method_call() {
assert_eq!(
SubjectExpr::parse("$this->getUser()").to_subject_text(),
"$this->getUser()"
);
}
#[test]
fn round_trip_static_method_call() {
assert_eq!(
SubjectExpr::parse("User::find(1)").to_subject_text(),
"User::find(1)"
);
}
#[test]
fn round_trip_static_access() {
assert_eq!(
SubjectExpr::parse("Status::Active").to_subject_text(),
"Status::Active"
);
}
#[test]
fn round_trip_class_name() {
assert_eq!(SubjectExpr::parse("User").to_subject_text(), "User");
}
#[test]
fn round_trip_chained_call_then_property() {
assert_eq!(
SubjectExpr::parse("$this->getFactory()->config").to_subject_text(),
"$this->getFactory()->config"
);
}
#[test]
fn round_trip_chained_method_calls() {
assert_eq!(
SubjectExpr::parse("$this->getFactory()->create()").to_subject_text(),
"$this->getFactory()->create()"
);
}
#[test]
fn round_trip_array_access() {
assert_eq!(
SubjectExpr::parse("$response['items'][0]").to_subject_text(),
"$response['items'][]"
);
}
#[test]
fn round_trip_interleaved_bracket_arrow_bracket() {
assert_eq!(
SubjectExpr::parse("$results[]->activities[]").to_subject_text(),
"$results[]->activities[]"
);
}
#[test]
fn round_trip_triple_interleaved() {
assert_eq!(
SubjectExpr::parse("$a[]->b[]->c[]").to_subject_text(),
"$a[]->b[]->c[]"
);
}
#[test]
fn parse_trims_whitespace() {
assert_eq!(SubjectExpr::parse(" $this "), SubjectExpr::This);
}
#[test]
fn parse_empty_string() {
assert_eq!(
SubjectExpr::parse(""),
SubjectExpr::ClassName(String::new())
);
}
#[test]
fn parse_complex_chain() {
let parsed = SubjectExpr::parse("ClassName::make()->process()->result");
assert_eq!(
parsed,
SubjectExpr::PropertyChain {
base: Box::new(SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::StaticMethodCall {
class: "ClassName".to_string(),
method: "make".to_string(),
}),
args_text: "".to_string(),
}),
method: "process".to_string(),
}),
args_text: "".to_string(),
}),
property: "result".to_string(),
}
);
}
#[test]
fn parse_call_with_nested_parens_in_args() {
let parsed = SubjectExpr::parse("app(config('key'))");
assert_eq!(
parsed,
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::FunctionCall("app".to_string())),
args_text: "config('key')".to_string(),
}
);
}
#[test]
fn parse_double_quoted_array_key() {
assert_eq!(
SubjectExpr::parse("$data[\"name\"]"),
SubjectExpr::ArrayAccess {
base: Box::new(SubjectExpr::Variable("$data".to_string())),
segments: vec![BracketSegment::StringKey("name".to_string())],
}
);
}
#[test]
fn parse_function_call_then_method() {
let parsed = SubjectExpr::parse("app()->make()");
assert_eq!(
parsed,
SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::MethodCall {
base: Box::new(SubjectExpr::CallExpr {
callee: Box::new(SubjectExpr::FunctionCall("app".to_string())),
args_text: "".to_string(),
}),
method: "make".to_string(),
}),
args_text: "".to_string(),
}
);
}
#[test]
fn parse_this_method_vs_property() {
let parsed = SubjectExpr::parse("$this->getFactory()");
assert!(matches!(parsed, SubjectExpr::CallExpr { .. }));
let parsed = SubjectExpr::parse("$this->factory");
assert!(matches!(parsed, SubjectExpr::PropertyChain { .. }));
}
#[test]
fn parse_static_access_vs_call() {
assert!(matches!(
SubjectExpr::parse("Status::Active"),
SubjectExpr::StaticAccess { .. }
));
assert!(matches!(
SubjectExpr::parse("User::find(1)"),
SubjectExpr::CallExpr { .. }
));
}
#[test]
fn parse_static_then_arrow_chain() {
let parsed = SubjectExpr::parse("ClassName::make()->config");
assert!(matches!(parsed, SubjectExpr::PropertyChain { .. }));
}
#[test]
fn parse_parenthesized_property_invocation() {
let parsed = SubjectExpr::parse("($this->formatter)()->write");
match &parsed {
SubjectExpr::PropertyChain { base, property } => {
assert_eq!(property, "write");
match base.as_ref() {
SubjectExpr::CallExpr { callee, args_text } => {
assert_eq!(args_text, "");
match callee.as_ref() {
SubjectExpr::PropertyChain {
base: inner_base,
property: inner_prop,
} => {
assert!(matches!(inner_base.as_ref(), SubjectExpr::This));
assert_eq!(inner_prop, "formatter");
}
other => panic!("Expected PropertyChain callee, got: {other:?}"),
}
}
other => panic!("Expected CallExpr base, got: {other:?}"),
}
}
other => panic!("Expected PropertyChain, got: {other:?}"),
}
}
#[test]
fn parse_parenthesized_variable_invocation() {
let parsed = SubjectExpr::parse("($var)()");
match &parsed {
SubjectExpr::CallExpr { callee, args_text } => {
assert_eq!(args_text, "");
assert!(
matches!(callee.as_ref(), SubjectExpr::Variable(v) if v == "$var"),
"Expected Variable($var), got: {callee:?}"
);
}
other => panic!("Expected CallExpr, got: {other:?}"),
}
}
#[test]
fn parse_instance_method_call_array_access() {
let parsed = SubjectExpr::parse("$c->items()[]");
match &parsed {
SubjectExpr::ArrayAccess { base, segments } => {
assert!(
matches!(base.as_ref(), SubjectExpr::CallExpr { .. }),
"Expected CallExpr base, got: {base:?}"
);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::ElementAccess);
}
other => panic!("Expected ArrayAccess, got: {other:?}"),
}
}
#[test]
fn parse_static_method_call_array_access() {
let parsed = SubjectExpr::parse("Collection::all()[]");
match &parsed {
SubjectExpr::ArrayAccess { base, segments } => {
assert!(
matches!(base.as_ref(), SubjectExpr::CallExpr { .. }),
"Expected CallExpr base, got: {base:?}"
);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::ElementAccess);
}
other => panic!("Expected ArrayAccess, got: {other:?}"),
}
}
#[test]
fn parse_function_call_array_access() {
let parsed = SubjectExpr::parse("getItems()[]");
match &parsed {
SubjectExpr::ArrayAccess { base, segments } => {
assert!(
matches!(base.as_ref(), SubjectExpr::CallExpr { .. }),
"Expected CallExpr base, got: {base:?}"
);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::ElementAccess);
}
other => panic!("Expected ArrayAccess, got: {other:?}"),
}
}
#[test]
fn parse_call_array_access_with_string_key() {
let parsed = SubjectExpr::parse("$c->getData()['name']");
match &parsed {
SubjectExpr::ArrayAccess { base, segments } => {
assert!(
matches!(base.as_ref(), SubjectExpr::CallExpr { .. }),
"Expected CallExpr base, got: {base:?}"
);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::StringKey("name".to_string()));
}
other => panic!("Expected ArrayAccess, got: {other:?}"),
}
}
#[test]
fn parse_interleaved_bracket_arrow_bracket() {
let parsed = SubjectExpr::parse("$results[]->activities[]");
match &parsed {
SubjectExpr::ArrayAccess { base, segments } => {
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::ElementAccess);
match base.as_ref() {
SubjectExpr::PropertyChain {
base: inner_base,
property,
} => {
assert_eq!(property, "activities");
match inner_base.as_ref() {
SubjectExpr::ArrayAccess {
base: var_base,
segments: inner_segs,
} => {
assert_eq!(
*var_base.as_ref(),
SubjectExpr::Variable("$results".to_string())
);
assert_eq!(inner_segs.len(), 1);
assert_eq!(inner_segs[0], BracketSegment::ElementAccess);
}
other => panic!("Expected inner ArrayAccess, got: {other:?}"),
}
}
other => panic!("Expected PropertyChain, got: {other:?}"),
}
}
other => panic!("Expected ArrayAccess, got: {other:?}"),
}
}
#[test]
fn parse_interleaved_bracket_arrow_no_trailing_bracket() {
let parsed = SubjectExpr::parse("$results[]->activities");
match &parsed {
SubjectExpr::PropertyChain { base, property } => {
assert_eq!(property, "activities");
match base.as_ref() {
SubjectExpr::ArrayAccess {
base: var_base,
segments,
} => {
assert_eq!(
*var_base.as_ref(),
SubjectExpr::Variable("$results".to_string())
);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::ElementAccess);
}
other => panic!("Expected ArrayAccess base, got: {other:?}"),
}
}
other => panic!("Expected PropertyChain, got: {other:?}"),
}
}
#[test]
fn parse_interleaved_string_key_arrow_bracket() {
let parsed = SubjectExpr::parse("$data['items']->name");
match &parsed {
SubjectExpr::PropertyChain { base, property } => {
assert_eq!(property, "name");
match base.as_ref() {
SubjectExpr::ArrayAccess {
base: var_base,
segments,
} => {
assert_eq!(
*var_base.as_ref(),
SubjectExpr::Variable("$data".to_string())
);
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::StringKey("items".to_string()));
}
other => panic!("Expected ArrayAccess base, got: {other:?}"),
}
}
other => panic!("Expected PropertyChain, got: {other:?}"),
}
}
#[test]
fn parse_triple_interleaved_bracket_arrow() {
let parsed = SubjectExpr::parse("$a[]->b[]->c[]");
match &parsed {
SubjectExpr::ArrayAccess { base, segments } => {
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::ElementAccess);
match base.as_ref() {
SubjectExpr::PropertyChain {
base: mid_base,
property,
} => {
assert_eq!(property, "c");
match mid_base.as_ref() {
SubjectExpr::ArrayAccess {
base: inner_base,
segments: mid_segs,
} => {
assert_eq!(mid_segs.len(), 1);
assert_eq!(mid_segs[0], BracketSegment::ElementAccess);
match inner_base.as_ref() {
SubjectExpr::PropertyChain {
base: deepest,
property: prop_b,
} => {
assert_eq!(prop_b, "b");
match deepest.as_ref() {
SubjectExpr::ArrayAccess {
base: var_base,
segments: deep_segs,
} => {
assert_eq!(
*var_base.as_ref(),
SubjectExpr::Variable("$a".to_string())
);
assert_eq!(deep_segs.len(), 1);
assert_eq!(deep_segs[0], BracketSegment::ElementAccess);
}
other => {
panic!("Expected deepest ArrayAccess, got: {other:?}")
}
}
}
other => panic!("Expected inner PropertyChain, got: {other:?}"),
}
}
other => panic!("Expected mid ArrayAccess, got: {other:?}"),
}
}
other => panic!("Expected outer PropertyChain, got: {other:?}"),
}
}
other => panic!("Expected outer ArrayAccess, got: {other:?}"),
}
}
#[test]
fn parse_this_prop_bracket_arrow_bracket() {
let parsed = SubjectExpr::parse("$this->items[]->name");
match &parsed {
SubjectExpr::PropertyChain { base, property } => {
assert_eq!(property, "name");
match base.as_ref() {
SubjectExpr::ArrayAccess {
base: prop_base,
segments,
} => {
assert_eq!(segments.len(), 1);
assert_eq!(segments[0], BracketSegment::ElementAccess);
match prop_base.as_ref() {
SubjectExpr::PropertyChain {
base: this_base,
property: items_prop,
} => {
assert_eq!(items_prop, "items");
assert_eq!(*this_base.as_ref(), SubjectExpr::This);
}
other => panic!("Expected PropertyChain for $this->items, got: {other:?}"),
}
}
other => panic!("Expected ArrayAccess base, got: {other:?}"),
}
}
other => panic!("Expected PropertyChain, got: {other:?}"),
}
}