mago_analyzer/plugin/libraries/stdlib/url/
parse_url.rs1use std::collections::BTreeMap;
4
5use mago_atom::Atom;
6use mago_codex::ttype::atomic::TAtomic;
7use mago_codex::ttype::atomic::array::TArray;
8use mago_codex::ttype::atomic::array::key::ArrayKey;
9use mago_codex::ttype::atomic::array::keyed::TKeyedArray;
10use mago_codex::ttype::atomic::scalar::TScalar;
11use mago_codex::ttype::atomic::scalar::bool::TBool;
12use mago_codex::ttype::atomic::scalar::int::TInteger;
13use mago_codex::ttype::atomic::scalar::string::TString;
14use mago_codex::ttype::get_int_range;
15use mago_codex::ttype::get_non_empty_string;
16use mago_codex::ttype::get_string;
17use mago_codex::ttype::union::TUnion;
18
19use crate::plugin::context::InvocationInfo;
20use crate::plugin::context::ProviderContext;
21use crate::plugin::provider::Provider;
22use crate::plugin::provider::ProviderMeta;
23use crate::plugin::provider::function::FunctionReturnTypeProvider;
24use crate::plugin::provider::function::FunctionTarget;
25
26const PHP_URL_SCHEME: i64 = 0;
27const PHP_URL_HOST: i64 = 1;
28const PHP_URL_PORT: i64 = 2;
29const PHP_URL_USER: i64 = 3;
30const PHP_URL_PASS: i64 = 4;
31const PHP_URL_PATH: i64 = 5;
32const PHP_URL_QUERY: i64 = 6;
33const PHP_URL_FRAGMENT: i64 = 7;
34
35static META: ProviderMeta = ProviderMeta::new(
36 "php::url::parse_url",
37 "parse_url",
38 "Returns typed array or component value based on component argument",
39);
40
41#[derive(Default)]
46pub struct ParseUrlProvider;
47
48impl Provider for ParseUrlProvider {
49 fn meta() -> &'static ProviderMeta {
50 &META
51 }
52}
53
54impl FunctionReturnTypeProvider for ParseUrlProvider {
55 fn targets() -> FunctionTarget {
56 FunctionTarget::Exact("parse_url")
57 }
58
59 fn get_return_type(
60 &self,
61 context: &ProviderContext<'_, '_, '_>,
62 invocation: &InvocationInfo<'_, '_, '_>,
63 ) -> Option<TUnion> {
64 let component_arg = invocation.get_argument(1, &["component"]);
65
66 if let Some(arg) = component_arg {
67 if let Some(component_type) = context.get_expression_type(arg) {
68 let values = collect_component_values(component_type);
69
70 if let Some(values) = values {
71 if values.is_empty() {
72 } else {
74 let mut result_types: Vec<TAtomic> = Vec::new();
75 for value in values {
76 let component_ret = get_component_return_type(value);
77 for atomic in component_ret.types.iter() {
78 if !result_types.contains(atomic) {
79 result_types.push(atomic.clone());
80 }
81 }
82 }
83
84 return Some(TUnion::from_vec(result_types));
85 }
86 }
87 }
88
89 return Some(get_all_components_return_type());
91 }
92
93 Some(get_full_array_return_type())
95 }
96}
97
98fn collect_component_values(component_type: &TUnion) -> Option<Vec<i64>> {
102 let mut values = Vec::new();
103
104 for atomic in component_type.types.iter() {
105 if let TAtomic::Scalar(TScalar::Integer(int_type)) = atomic {
106 match *int_type {
107 TInteger::Literal(v) => {
108 if !values.contains(&v) {
109 values.push(v);
110 }
111 }
112 TInteger::Range(from, to) => {
113 let effective_from = from.max(-1);
114 let effective_to = to.min(7);
115
116 if effective_from <= effective_to {
117 for v in effective_from..=effective_to {
118 if !values.contains(&v) {
119 values.push(v);
120 }
121 }
122 }
123 }
124 TInteger::From(from) => {
125 let effective_from = from.max(-1);
126 if effective_from <= 7 {
127 for v in effective_from..=7 {
128 if !values.contains(&v) {
129 values.push(v);
130 }
131 }
132 }
133 }
134 TInteger::To(to) => {
135 let effective_to = to.min(7);
136 if -1 <= effective_to {
137 for v in -1..=effective_to {
138 if !values.contains(&v) {
139 values.push(v);
140 }
141 }
142 }
143 }
144 TInteger::Unspecified | TInteger::UnspecifiedLiteral => {
145 return None;
146 }
147 }
148 }
149 }
150
151 Some(values)
152}
153
154fn get_component_return_type(component: i64) -> TUnion {
156 match component {
157 PHP_URL_SCHEME | PHP_URL_HOST | PHP_URL_USER | PHP_URL_PASS | PHP_URL_QUERY | PHP_URL_FRAGMENT => {
158 TUnion::from_vec(vec![TAtomic::Null, TAtomic::Scalar(TScalar::String(TString::non_empty()))])
160 }
161 PHP_URL_PORT => {
162 TUnion::from_vec(vec![TAtomic::Null, TAtomic::Scalar(TScalar::Integer(TInteger::Range(0, 65535)))])
164 }
165 PHP_URL_PATH => {
166 TUnion::from_vec(vec![TAtomic::Null, TAtomic::Scalar(TScalar::String(TString::general()))])
168 }
169 -1 => {
170 get_full_array_return_type()
172 }
173 _ => TUnion::from_vec(vec![TAtomic::Scalar(TScalar::Bool(TBool::r#false()))]),
174 }
175}
176
177fn get_all_components_return_type() -> TUnion {
181 let mut all_components_return_type = get_full_array_return_type();
182 all_components_return_type.types.to_mut().push(TAtomic::Null);
183 all_components_return_type.types.to_mut().push(TAtomic::Scalar(TScalar::Integer(TInteger::Range(0, 65535))));
184 all_components_return_type.types.to_mut().push(TAtomic::Scalar(TScalar::String(TString::general())));
185
186 all_components_return_type
187}
188
189fn get_full_array_return_type() -> TUnion {
191 let mut known_items: BTreeMap<ArrayKey, (bool, TUnion)> = BTreeMap::new();
192
193 let optional_string_fields = ["scheme", "user", "pass", "host", "query", "fragment"];
194 for field in optional_string_fields {
195 known_items.insert(ArrayKey::String(Atom::from(field)), (true, get_non_empty_string()));
196 }
197
198 known_items.insert(ArrayKey::String(Atom::from("port")), (true, get_int_range(Some(0), Some(65535))));
199 known_items.insert(ArrayKey::String(Atom::from("path")), (false, get_string()));
200
201 let keyed_array = TKeyedArray::new().with_known_items(known_items);
202
203 TUnion::from_vec(vec![TAtomic::Scalar(TScalar::Bool(TBool::r#false())), TAtomic::Array(TArray::Keyed(keyed_array))])
204}