1use crate::TypeDatabase;
2use crate::types::{
3 IndexSignature, IntrinsicKind, ObjectFlags, ObjectShape, PropertyInfo, TypeId, Visibility,
4};
5
6pub enum ApparentMemberKind {
7 Value(TypeId),
8 Method(TypeId),
9}
10
11pub struct ApparentMember {
12 pub name: &'static str,
13 pub kind: ApparentMemberKind,
14}
15
16const STRING_METHODS_RETURN_STRING: &[&str] = &[
17 "anchor",
18 "at",
19 "big",
20 "blink",
21 "bold",
22 "charAt",
23 "concat",
24 "fixed",
25 "fontcolor",
26 "fontsize",
27 "italics",
28 "link",
29 "normalize",
30 "padEnd",
31 "padStart",
32 "repeat",
33 "replace",
34 "replaceAll",
35 "slice",
36 "small",
37 "strike",
38 "sub",
39 "substr",
40 "substring",
41 "sup",
42 "toLocaleLowerCase",
43 "toLocaleUpperCase",
44 "toLowerCase",
45 "toString",
46 "toUpperCase",
47 "trim",
48 "trimEnd",
49 "trimLeft",
50 "trimRight",
51 "trimStart",
52 "toWellFormed",
53 "valueOf",
54];
55const STRING_METHODS_RETURN_NUMBER: &[&str] = &[
56 "charCodeAt",
57 "codePointAt",
58 "indexOf",
59 "lastIndexOf",
60 "localeCompare",
61 "search",
62];
63const STRING_METHODS_RETURN_BOOLEAN: &[&str] =
64 &["endsWith", "includes", "isWellFormed", "startsWith"];
65const STRING_METHODS_RETURN_ANY: &[&str] = &["match", "matchAll"];
66const STRING_METHODS_RETURN_STRING_ARRAY: &[&str] = &["split"];
67
68const NUMBER_METHODS_RETURN_STRING: &[&str] = &[
69 "toExponential",
70 "toFixed",
71 "toLocaleString",
72 "toPrecision",
73 "toString",
74];
75
76const BOOLEAN_METHODS_RETURN_STRING: &[&str] = &["toLocaleString", "toString"];
77
78const BIGINT_METHODS_RETURN_STRING: &[&str] = &["toLocaleString", "toString"];
79
80const OBJECT_METHODS_RETURN_BOOLEAN: &[&str] =
81 &["hasOwnProperty", "isPrototypeOf", "propertyIsEnumerable"];
82const OBJECT_METHODS_RETURN_STRING: &[&str] = &["toString"];
83const OBJECT_METHODS_RETURN_ANY: &[&str] = &["valueOf"];
84
85fn is_member(name: &str, list: &[&str]) -> bool {
86 list.contains(&name)
87}
88
89fn object_member_kind(name: &str, include_to_locale: bool) -> Option<ApparentMemberKind> {
90 if name == "constructor" {
91 return Some(ApparentMemberKind::Value(TypeId::FUNCTION));
92 }
93 if name == "toString" {
94 return Some(ApparentMemberKind::Method(TypeId::STRING));
95 }
96 if name == "valueOf" {
97 return Some(ApparentMemberKind::Method(TypeId::ANY));
98 }
99 if is_member(name, OBJECT_METHODS_RETURN_BOOLEAN) {
100 return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
101 }
102 if is_member(name, OBJECT_METHODS_RETURN_STRING) {
103 return Some(ApparentMemberKind::Method(TypeId::STRING));
104 }
105 if is_member(name, OBJECT_METHODS_RETURN_ANY) {
106 return Some(ApparentMemberKind::Method(TypeId::ANY));
107 }
108 if include_to_locale && name == "toLocaleString" {
109 return Some(ApparentMemberKind::Method(TypeId::STRING));
110 }
111 None
112}
113
114fn push_object_members(members: &mut Vec<ApparentMember>, include_to_locale: bool) {
115 members.push(ApparentMember {
116 name: "constructor",
117 kind: ApparentMemberKind::Value(TypeId::ANY),
118 });
119 members.push(ApparentMember {
120 name: "toString",
121 kind: ApparentMemberKind::Method(TypeId::STRING),
122 });
123 members.push(ApparentMember {
124 name: "valueOf",
125 kind: ApparentMemberKind::Method(TypeId::ANY),
126 });
127 for &name in OBJECT_METHODS_RETURN_BOOLEAN {
128 members.push(ApparentMember {
129 name,
130 kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
131 });
132 }
133 for &name in OBJECT_METHODS_RETURN_STRING {
134 members.push(ApparentMember {
135 name,
136 kind: ApparentMemberKind::Method(TypeId::STRING),
137 });
138 }
139 for &name in OBJECT_METHODS_RETURN_ANY {
140 members.push(ApparentMember {
141 name,
142 kind: ApparentMemberKind::Method(TypeId::ANY),
143 });
144 }
145 if include_to_locale {
146 members.push(ApparentMember {
147 name: "toLocaleString",
148 kind: ApparentMemberKind::Method(TypeId::STRING),
149 });
150 }
151}
152
153pub fn apparent_object_member_kind(name: &str) -> Option<ApparentMemberKind> {
154 object_member_kind(name, true)
155}
156
157pub fn apparent_primitive_member_kind(
158 interner: &dyn TypeDatabase,
159 kind: IntrinsicKind,
160 name: &str,
161) -> Option<ApparentMemberKind> {
162 match kind {
163 IntrinsicKind::String => {
164 if name == "length" {
165 return Some(ApparentMemberKind::Value(TypeId::NUMBER));
166 }
167 if is_member(name, STRING_METHODS_RETURN_STRING) {
168 return Some(ApparentMemberKind::Method(TypeId::STRING));
169 }
170 if is_member(name, STRING_METHODS_RETURN_NUMBER) {
171 return Some(ApparentMemberKind::Method(TypeId::NUMBER));
172 }
173 if is_member(name, STRING_METHODS_RETURN_BOOLEAN) {
174 return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
175 }
176 if is_member(name, STRING_METHODS_RETURN_ANY) {
177 return Some(ApparentMemberKind::Method(TypeId::ANY));
178 }
179 if is_member(name, STRING_METHODS_RETURN_STRING_ARRAY) {
180 let string_array = interner.array(TypeId::STRING);
181 return Some(ApparentMemberKind::Method(string_array));
182 }
183 object_member_kind(name, true)
184 }
185 IntrinsicKind::Number => {
186 if is_member(name, NUMBER_METHODS_RETURN_STRING) {
187 return Some(ApparentMemberKind::Method(TypeId::STRING));
188 }
189 if name == "valueOf" {
190 return Some(ApparentMemberKind::Method(TypeId::NUMBER));
191 }
192 object_member_kind(name, false)
193 }
194 IntrinsicKind::Boolean => {
195 if is_member(name, BOOLEAN_METHODS_RETURN_STRING) {
196 return Some(ApparentMemberKind::Method(TypeId::STRING));
197 }
198 if name == "valueOf" {
199 return Some(ApparentMemberKind::Method(TypeId::BOOLEAN));
200 }
201 object_member_kind(name, false)
202 }
203 IntrinsicKind::Bigint => {
204 if is_member(name, BIGINT_METHODS_RETURN_STRING) {
205 return Some(ApparentMemberKind::Method(TypeId::STRING));
206 }
207 if name == "valueOf" {
208 return Some(ApparentMemberKind::Method(TypeId::BIGINT));
209 }
210 object_member_kind(name, false)
211 }
212 IntrinsicKind::Symbol => {
213 if name == "description" {
214 let description = interner.union2(TypeId::STRING, TypeId::UNDEFINED);
215 return Some(ApparentMemberKind::Value(description));
216 }
217 if name == "toString" {
218 return Some(ApparentMemberKind::Method(TypeId::STRING));
219 }
220 if name == "valueOf" {
221 return Some(ApparentMemberKind::Method(TypeId::SYMBOL));
222 }
223 object_member_kind(name, true)
224 }
225 IntrinsicKind::Object => object_member_kind(name, true),
226 _ => None,
227 }
228}
229
230pub fn apparent_primitive_members(
231 interner: &dyn TypeDatabase,
232 kind: IntrinsicKind,
233) -> Vec<ApparentMember> {
234 let mut members = Vec::new();
235
236 match kind {
237 IntrinsicKind::String => {
238 members.push(ApparentMember {
239 name: "length",
240 kind: ApparentMemberKind::Value(TypeId::NUMBER),
241 });
242 for &name in STRING_METHODS_RETURN_STRING {
243 members.push(ApparentMember {
244 name,
245 kind: ApparentMemberKind::Method(TypeId::STRING),
246 });
247 }
248 for &name in STRING_METHODS_RETURN_NUMBER {
249 members.push(ApparentMember {
250 name,
251 kind: ApparentMemberKind::Method(TypeId::NUMBER),
252 });
253 }
254 for &name in STRING_METHODS_RETURN_BOOLEAN {
255 members.push(ApparentMember {
256 name,
257 kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
258 });
259 }
260 for &name in STRING_METHODS_RETURN_ANY {
261 members.push(ApparentMember {
262 name,
263 kind: ApparentMemberKind::Method(TypeId::ANY),
264 });
265 }
266 let string_array = interner.array(TypeId::STRING);
267 for &name in STRING_METHODS_RETURN_STRING_ARRAY {
268 members.push(ApparentMember {
269 name,
270 kind: ApparentMemberKind::Method(string_array),
271 });
272 }
273 push_object_members(&mut members, true);
274 }
275 IntrinsicKind::Number => {
276 for &name in NUMBER_METHODS_RETURN_STRING {
277 members.push(ApparentMember {
278 name,
279 kind: ApparentMemberKind::Method(TypeId::STRING),
280 });
281 }
282 members.push(ApparentMember {
283 name: "valueOf",
284 kind: ApparentMemberKind::Method(TypeId::NUMBER),
285 });
286 push_object_members(&mut members, false);
287 }
288 IntrinsicKind::Boolean => {
289 for &name in BOOLEAN_METHODS_RETURN_STRING {
290 members.push(ApparentMember {
291 name,
292 kind: ApparentMemberKind::Method(TypeId::STRING),
293 });
294 }
295 members.push(ApparentMember {
296 name: "valueOf",
297 kind: ApparentMemberKind::Method(TypeId::BOOLEAN),
298 });
299 push_object_members(&mut members, false);
300 }
301 IntrinsicKind::Bigint => {
302 for &name in BIGINT_METHODS_RETURN_STRING {
303 members.push(ApparentMember {
304 name,
305 kind: ApparentMemberKind::Method(TypeId::STRING),
306 });
307 }
308 members.push(ApparentMember {
309 name: "valueOf",
310 kind: ApparentMemberKind::Method(TypeId::BIGINT),
311 });
312 push_object_members(&mut members, false);
313 }
314 IntrinsicKind::Symbol => {
315 let description = interner.union2(TypeId::STRING, TypeId::UNDEFINED);
316 members.push(ApparentMember {
317 name: "description",
318 kind: ApparentMemberKind::Value(description),
319 });
320 members.push(ApparentMember {
321 name: "toString",
322 kind: ApparentMemberKind::Method(TypeId::STRING),
323 });
324 members.push(ApparentMember {
325 name: "valueOf",
326 kind: ApparentMemberKind::Method(TypeId::SYMBOL),
327 });
328 push_object_members(&mut members, true);
329 }
330 IntrinsicKind::Object => {
331 push_object_members(&mut members, true);
332 }
333 _ => {}
334 }
335
336 members
337}
338
339pub fn apparent_primitive_shape(
347 db: &dyn TypeDatabase,
348 kind: IntrinsicKind,
349 make_method_type: impl Fn(&dyn TypeDatabase, TypeId) -> TypeId,
350) -> ObjectShape {
351 let members = apparent_primitive_members(db, kind);
352 let mut properties = Vec::with_capacity(members.len());
353
354 for member in members {
355 let name = db.intern_string(member.name);
356 match member.kind {
357 ApparentMemberKind::Value(type_id) => properties.push(PropertyInfo {
358 name,
359 type_id,
360 write_type: type_id,
361 optional: false,
362 readonly: false,
363 is_method: false,
364 visibility: Visibility::Public,
365 parent_id: None,
366 }),
367 ApparentMemberKind::Method(return_type) => {
368 let method_ty = make_method_type(db, return_type);
369 properties.push(PropertyInfo {
370 name,
371 type_id: method_ty,
372 write_type: method_ty,
373 optional: false,
374 readonly: false,
375 is_method: true,
376 visibility: Visibility::Public,
377 parent_id: None,
378 });
379 }
380 }
381 }
382 properties.sort_by_key(|a| a.name);
383
384 let number_index = (kind == IntrinsicKind::String).then_some(IndexSignature {
385 key_type: TypeId::NUMBER,
386 value_type: TypeId::STRING,
387 readonly: false,
388 });
389
390 ObjectShape {
391 flags: ObjectFlags::empty(),
392 properties,
393 string_index: None,
394 number_index,
395 symbol: None,
396 }
397}