1use std::path::{Path, PathBuf};
2
3use super::variables::{VariableMutability, VariableOrImport};
4use crate::{
5 context::{
6 information::{get_value_of_constant_import_variable, LocalInformation},
7 VariableRegisterArguments,
8 },
9 parse_source, CheckingData, Environment, Instance, Map, Scope, TypeId, TypeMappings,
10 VariableId,
11};
12
13use simple_json_parser::{JSONKey, RootJSONValue};
14use source_map::{FileSystem, Span};
15
16#[derive(Debug)]
18pub struct NamePair<'a> {
19 pub value: &'a str,
20 pub r#as: &'a str,
21 pub position: Span,
22}
23
24pub enum ImportKind<'a, T: Iterator<Item = NamePair<'a>>> {
25 Parts(T),
26 All {
27 under: &'a str,
28 position: Span,
29 },
30 Everything,
32}
33
34pub struct SynthesisedModule<M> {
36 pub content: M,
37 pub exported: Exported,
38 pub info: LocalInformation,
40 pub mappings: TypeMappings,
41}
42
43impl<M> SynthesisedModule<M> {
44 pub fn get_instance_at_position(&self, pos: u32) -> Option<&Instance> {
45 self.mappings.expressions_to_instances.get(pos)
46 }
47
48 pub fn get_instance_at_position_with_span(
50 &self,
51 pos: u32,
52 ) -> Option<(&Instance, std::ops::Range<u32>)> {
53 self.mappings.expressions_to_instances.get_with_range(pos)
54 }
55}
56
57#[derive(Clone, Debug, Default, binary_serialize_derive::BinarySerializable)]
59pub struct Exported {
60 pub default: Option<TypeId>,
61 pub named: Map<String, (VariableId, VariableMutability)>,
63 pub named_types: Map<String, TypeId>,
64}
65
66pub type ExportedVariable = (VariableId, VariableMutability);
67
68impl Exported {
69 #[must_use]
70 pub fn get_export(
71 &self,
72 want: &str,
73 type_only: bool,
74 ) -> (Option<ExportedVariable>, Option<TypeId>) {
75 let variable = if type_only {
76 None
77 } else {
78 self.named.get(want).map(|(name, mutability)| (*name, *mutability))
79 };
80
81 let r#type = self.named_types.get(want).copied();
82
83 (variable, r#type)
84 }
85
86 pub fn keys(&self) -> impl Iterator<Item = &str> {
87 self.named.keys().chain(self.named_types.keys()).map(AsRef::as_ref)
88 }
89}
90
91pub struct InvalidModule;
93
94pub type FinalModule<M> = Result<SynthesisedModule<M>, InvalidModule>;
96
97#[derive(Debug, Clone)]
98pub struct CouldNotOpenFile(pub PathBuf);
99
100#[allow(clippy::too_many_arguments)]
105pub fn import_items<
106 'b,
107 P: Iterator<Item = NamePair<'b>>,
108 T: crate::ReadFromFS,
109 A: crate::ASTImplementation,
110>(
111 environment: &mut Environment,
112 partial_import_path: &str,
113 import_position: Span,
114 default_import: Option<(&str, Span)>,
115 kind: ImportKind<'b, P>,
116 checking_data: &mut CheckingData<T, A>,
117 also_export: bool,
118 type_only: bool,
119) {
120 if !matches!(environment.context_type.scope, crate::Scope::Module { .. }) {
121 checking_data.diagnostics_container.add_error(
122 crate::diagnostics::TypeCheckError::NotTopLevelImport(
123 import_position.with_source(environment.get_source()),
124 ),
125 );
126 return;
127 }
128
129 let exports = import_file(partial_import_path, environment, checking_data);
130
131 if let Err(ref err) = exports {
132 checking_data.diagnostics_container.add_error(
133 crate::diagnostics::TypeCheckError::CannotOpenFile {
134 file: err.clone(),
135 import_position: Some(import_position.with_source(environment.get_source())),
136 possibles: checking_data
137 .modules
138 .files
139 .get_paths()
140 .keys()
141 .filter_map(|path| path.to_str())
142 .collect(),
143 partial_import_path,
144 },
145 );
146 }
147
148 let current_source = environment.get_source();
149
150 if let Some((default_name, position)) = default_import {
151 if let Ok(Ok(ref exports)) = exports {
152 if let Some(item) = &exports.default {
153 let id = crate::VariableId(current_source, position.start);
154 let v = VariableOrImport::ConstantImport {
155 to: None,
156 import_specified_at: position.with_source(current_source),
157 };
158 environment.info.variable_current_value.insert(id, *item);
159 let existing = environment.variables.insert(default_name.to_owned(), v);
160 if let Some(existing) = existing {
161 checking_data.diagnostics_container.add_error(
162 crate::diagnostics::TypeCheckError::DuplicateImportName {
163 import_position: position.with_source(current_source),
164 existing_position: match existing {
165 VariableOrImport::Variable { declared_at, .. } => declared_at,
166 VariableOrImport::MutableImport { import_specified_at, .. }
167 | VariableOrImport::ConstantImport {
168 import_specified_at, ..
169 } => import_specified_at,
170 },
171 },
172 );
173 }
174 } else {
175 checking_data.diagnostics_container.add_error(
176 crate::diagnostics::TypeCheckError::NoDefaultExport {
177 position: position.with_source(current_source),
178 partial_import_path,
179 },
180 );
181 }
182 } else {
183 environment.register_variable_handle_error(
184 default_name,
185 VariableRegisterArguments {
186 constant: true,
187 initial_value: Some(TypeId::ERROR_TYPE),
188 space: None,
189 allow_reregistration: false,
190 },
191 position.with_source(current_source),
192 &mut checking_data.diagnostics_container,
193 &mut checking_data.local_type_mappings,
194 checking_data.options.record_all_assignments_and_reads,
195 );
196 }
197 }
198
199 match kind {
200 ImportKind::Parts(parts) => {
201 for part in parts {
202 if let Ok(Ok(ref exports)) = exports {
204 crate::utilities::notify!("{:?}", part);
205 let (exported_variable, exported_type) =
206 exports.get_export(part.value, type_only);
207
208 if exported_variable.is_none() && exported_type.is_none() {
209 let possibles = {
210 let mut possibles =
211 crate::get_closest(exports.keys(), part.value).unwrap_or(vec![]);
212 possibles.sort_unstable();
213 possibles
214 };
215 let position = part.position.with_source(current_source);
216 checking_data.diagnostics_container.add_error(
217 crate::diagnostics::TypeCheckError::FieldNotExported {
218 file: partial_import_path,
219 position,
220 importing: part.value,
221 possibles,
222 },
223 );
224
225 environment.register_variable_handle_error(
227 part.r#as,
228 VariableRegisterArguments {
229 constant: true,
230 space: None,
231 initial_value: Some(TypeId::ERROR_TYPE),
232 allow_reregistration: false,
233 },
234 position,
235 &mut checking_data.diagnostics_container,
236 &mut checking_data.local_type_mappings,
237 checking_data.options.record_all_assignments_and_reads,
238 );
239 }
240
241 if let Some((variable, mutability)) = exported_variable {
243 let constant = match mutability {
244 VariableMutability::Constant => {
245 let k = crate::VariableId(current_source, part.position.start);
246 let v =
247 get_value_of_constant_import_variable(variable, environment);
248 environment.info.variable_current_value.insert(k, v);
249 true
250 }
251 VariableMutability::Mutable { reassignment_constraint: _ } => false,
252 };
253
254 let v = VariableOrImport::MutableImport {
255 of: variable,
256 constant,
257 import_specified_at: part
258 .position
259 .with_source(environment.get_source()),
260 };
261 crate::utilities::notify!("{:?}", part.r#as.to_owned());
262 let existing = environment.variables.insert(part.r#as.to_owned(), v);
263 if let Some(existing) = existing {
264 checking_data.diagnostics_container.add_error(
265 crate::diagnostics::TypeCheckError::DuplicateImportName {
266 import_position: part
267 .position
268 .with_source(environment.get_source()),
269 existing_position: match existing {
270 VariableOrImport::Variable { declared_at, .. } => {
271 declared_at
272 }
273 VariableOrImport::MutableImport {
274 import_specified_at,
275 ..
276 }
277 | VariableOrImport::ConstantImport {
278 import_specified_at,
279 ..
280 } => import_specified_at,
281 },
282 },
283 );
284 }
285 if also_export {
286 if let Scope::Module { ref mut exported, .. } =
287 environment.context_type.scope
288 {
289 exported.named.insert(part.r#as.to_owned(), (variable, mutability));
290 }
291 }
292 }
293
294 if let Some(ty) = exported_type {
296 let existing = environment.named_types.insert(part.r#as.to_owned(), ty);
297 assert!(existing.is_none(), "TODO exception");
298 }
299 } else {
300 let declared_at = part.position.with_source(environment.get_source());
303 environment.register_variable_handle_error(
304 part.r#as,
305 VariableRegisterArguments {
306 constant: true,
307 space: None,
308 initial_value: Some(TypeId::ERROR_TYPE),
309 allow_reregistration: false,
310 },
311 declared_at,
312 &mut checking_data.diagnostics_container,
313 &mut checking_data.local_type_mappings,
314 checking_data.options.record_all_assignments_and_reads,
315 );
316 }
317 }
318 }
319 ImportKind::All { under, position } => {
320 let value = if let Ok(Ok(ref exports)) = exports {
321 let import_object = crate::Type::SpecialObject(
322 crate::features::objects::SpecialObject::Import(exports.clone()),
323 );
324 checking_data.types.register_type(import_object)
325 } else {
326 crate::utilities::notify!("TODO :?");
327 TypeId::UNIMPLEMENTED_ERROR_TYPE
328 };
329 environment.register_variable_handle_error(
330 under,
331 VariableRegisterArguments {
332 constant: true,
333 space: None,
334 initial_value: Some(value),
335 allow_reregistration: false,
336 },
337 position.with_source(current_source),
338 &mut checking_data.diagnostics_container,
339 &mut checking_data.local_type_mappings,
340 checking_data.options.record_all_assignments_and_reads,
341 );
342 }
343 ImportKind::Everything => {
344 if let Ok(Ok(ref exports)) = exports {
345 for (name, (variable, mutability)) in exports.named.iter() {
346 if let Scope::Module { ref mut exported, .. } = environment.context_type.scope {
348 exported.named.insert(name.clone(), (*variable, *mutability));
349 }
350 }
351 } else {
352 }
354 }
355 }
356}
357
358pub fn import_file<T: crate::ReadFromFS, A: crate::ASTImplementation>(
360 to_import: &str,
361 environment: &mut Environment,
362 checking_data: &mut CheckingData<T, A>,
363) -> Result<Result<Exported, InvalidModule>, CouldNotOpenFile> {
364 fn get_module<'a, T: crate::ReadFromFS, A: crate::ASTImplementation>(
365 full_importer: &Path,
366 _definition_file: Option<&Path>,
367 environment: &mut Environment,
368 checking_data: &'a mut CheckingData<T, A>,
369 ) -> Option<Result<&'a SynthesisedModule<A::OwnedModule>, A::ParseError>> {
370 let existing = checking_data.modules.files.get_source_at_path(full_importer);
371 if let Some(existing) = existing {
372 Some(Ok(checking_data
373 .modules
374 .synthesised_modules
375 .get(&existing)
376 .expect("existing file, but not synthesised")))
377 } else {
378 let content = checking_data.modules.file_reader.read_file(full_importer);
379 if let Some(content) = content {
380 let content = String::from_utf8(content).expect("invalid entry point encoding");
381 let source = checking_data
382 .modules
383 .files
384 .new_source_id(full_importer.to_path_buf(), content.clone());
385 let module = parse_source(full_importer, source, content, checking_data);
386
387 match module {
388 Ok(module) => {
389 let root = &environment.get_root();
390 let new_module_context =
391 root.new_module_context(source, module, checking_data);
392 Some(Ok(new_module_context))
393 }
394 Err(err) => Some(Err(err)),
395 }
396 } else {
397 None
398 }
399 }
400 }
401
402 fn get_package_from_node_modules<T: crate::ReadFromFS>(
403 name: &str,
404 cwd: &Path,
405 fs_reader: &T,
406 ) -> Result<(PathBuf, Option<PathBuf>), ()> {
407 let package_directory = cwd.join("node_modules");
409 let package_root = package_directory.join(name);
410 let package_json_path = package_root.join("package.json");
411 let package_json = fs_reader.read_file(&PathBuf::from(&package_json_path)).ok_or(())?;
413 let package_json = String::from_utf8(package_json).unwrap();
414
415 let (mut file_path, mut definition_file_path) = (None::<PathBuf>, None::<PathBuf>);
416
417 let _res = simple_json_parser::parse_with_exit_signal(&package_json, |path, value| {
419 if let [JSONKey::Slice("main")] = path {
423 if let RootJSONValue::String(s) = value {
424 file_path = Some(s.to_owned().into());
425 } else {
426 }
428 } else if let [JSONKey::Slice("types")] = path {
429 if let RootJSONValue::String(s) = value {
430 definition_file_path = Some(s.to_owned().into());
431 } else {
432 }
434 }
435 file_path.is_some() && definition_file_path.is_some()
437 });
438
439 file_path.ok_or(()).map(|entry| {
440 (package_root.join(entry), definition_file_path.map(|dfp| package_root.join(dfp)))
441 })
442 }
443
444 let result = if to_import.starts_with('.') {
445 let from_path = checking_data.modules.files.get_file_path(environment.get_source());
446 let from = PathBuf::from(to_import);
447 let mut full_importer =
448 path_absolutize::Absolutize::absolutize_from(&from, from_path.parent().unwrap())
449 .unwrap()
450 .to_path_buf();
451
452 if full_importer.extension().is_some() {
453 get_module(&full_importer, None, environment, checking_data)
454 } else {
455 let mut result = None;
456 for ext in ["ts", "tsx", "js"] {
457 full_importer.set_extension(ext);
458 result = get_module(&full_importer, None, environment, checking_data);
460 if result.is_some() {
461 break;
462 }
463 }
464 result
465 }
466 } else {
467 crate::utilities::notify!("Here {}", to_import);
468 let result = get_package_from_node_modules(
469 to_import,
470 &checking_data.modules.current_working_directory,
471 checking_data.modules.file_reader,
472 );
473 if let Ok((path, definition_file)) = result {
474 crate::utilities::notify!("Reading path from package {}", path.display());
475 get_module(&path, definition_file.as_deref(), environment, checking_data)
476 } else {
477 None
478 }
479 };
480
481 match result {
482 Some(Ok(synthesised_module)) => {
483 environment.info.extend_ref(&synthesised_module.info);
484 Ok(Ok(synthesised_module.exported.clone()))
485 }
486 Some(Err(error)) => {
487 checking_data.diagnostics_container.add_error(error);
488 Ok(Err(InvalidModule))
489 }
490 None => Err(CouldNotOpenFile(PathBuf::from(to_import.to_owned()))),
491 }
492}
493
494