1use skif_core::{HashMap, HashSet};
4use std::cell::{Cell, RefCell};
5use std::path::{Component, Path, PathBuf};
6use std::rc::Rc;
7use std::sync::LazyLock;
8
9use oxc_allocator::Allocator;
10use oxc_ast::ast::{
11 Argument, ArrayExpressionElement, ArrowFunctionExpression, AssignmentOperator, AssignmentTarget,
12 AssignmentTargetPropertyIdentifier, BinaryExpression, BinaryOperator, BindingIdentifier, BindingPattern,
13 BlockStatement, CallExpression, CatchClause, ChainElement, ChainExpression, ComputedMemberExpression,
14 ConditionalExpression, Declaration, ExportDefaultDeclarationKind, Expression, ExpressionStatement, ForInStatement,
15 ForOfStatement, ForStatement, Function, IdentifierReference, IfStatement, ImportDeclarationSpecifier,
16 ImportOrExportKind, LogicalExpression, LogicalOperator, MemberExpression, NewExpression, ObjectProperty,
17 ObjectPropertyKind, Program, ReturnStatement, SimpleAssignmentTarget, Statement, StaticMemberExpression,
18 TaggedTemplateExpression, TemplateLiteral, UnaryExpression, UnaryOperator, VariableDeclaration,
19 VariableDeclarationKind, VariableDeclarator,
20};
21use oxc_ast_visit::{Visit, walk};
22use oxc_parser::{ParseOptions as OxcParseOptions, Parser};
23use oxc_span::{SourceType, Span};
24use serde::{Deserialize, Serialize};
25
26use crate::finder::static_string;
27use crate::sharing::{
28 Change, Imports, Unstable, Write, alias_names, alias_source, changed_by_call, given, given_parts, property_defined,
29 reach, statement_call,
30};
31
32const MAX_DEPTH: u32 = 32;
33
34const MAX_CONST_CHAIN: usize = 64;
36
37const MAX_CONST_NODES: usize = 10_000;
40
41const MAX_CALLS: usize = 256;
44
45const MAX_IMPORT_DEPTH: usize = 8;
48
49const MAX_MODULE_LOADS: usize = 64;
51
52const MAX_MODULE_ANALYSES: usize = 4 * MAX_MODULE_LOADS;
55
56const MAX_MODULE_EVALUATIONS: usize = 1024;
59
60const GLOB_PACKAGES: &[&str] = &["glob", "fast-glob", "globby", "tinyglobby"];
62
63const MERGE_PACKAGES: &[&str] = &[
66 "lodash",
67 "lodash-es",
68 "lodash/merge",
69 "lodash/mergeWith",
70 "lodash.merge",
71 "lodash.mergewith",
72 "deepmerge",
73 "deepmerge-ts",
74 "ts-deepmerge",
75 "merge",
76 "merge-deep",
77 "webpack-merge",
78 "defu",
79];
80
81const AMBIGUOUS_MERGES: &[&str] = &["merge", "mergeWith"];
83
84const GLOB_MEMBERS: &[&str] = &["sync", "async", "glob", "globSync", "globbySync", "stream"];
86
87const OBJECT_PROTOTYPE_MEMBERS: &[&str] = &[
89 "__defineGetter__",
90 "__defineSetter__",
91 "__lookupGetter__",
92 "__lookupSetter__",
93 "__proto__",
94 "constructor",
95 "hasOwnProperty",
96 "isPrototypeOf",
97 "propertyIsEnumerable",
98 "toLocaleString",
99 "toString",
100 "valueOf",
101];
102
103#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105#[serde(rename_all = "kebab-case")]
106pub enum ConfigValue {
107 Null,
109 Bool(bool),
111 Number(f64),
113 String(String),
115 Array(Vec<ConfigValue>),
117 Object(Vec<(String, ConfigValue)>),
120 Regex {
122 pattern: String,
124 flags: String,
126 },
127 Unknown,
129 OneOf(Vec<ConfigValue>),
135}
136
137pub const UNKNOWN_SPREAD: &str = "...";
140
141pub const OWN_SPREAD: &str = "...own";
145
146impl ConfigValue {
147 #[must_use]
149 pub fn is_spread_key(key: &str) -> bool {
150 key == UNKNOWN_SPREAD || key == OWN_SPREAD
151 }
152
153 #[must_use]
155 pub fn get(&self, key: &str) -> Option<&ConfigValue> {
156 match self {
157 Self::Object(entries) => entries.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v),
158 _ => None,
159 }
160 }
161
162 #[must_use]
164 pub fn as_str(&self) -> Option<&str> {
165 match self {
166 Self::String(s) => Some(s),
167 _ => None,
168 }
169 }
170
171 #[must_use]
173 pub fn as_array(&self) -> Option<&[ConfigValue]> {
174 match self {
175 Self::Array(items) => Some(items),
176 _ => None,
177 }
178 }
179
180 #[must_use]
182 pub fn as_object(&self) -> Option<&[(String, ConfigValue)]> {
183 match self {
184 Self::Object(entries) => Some(entries),
185 _ => None,
186 }
187 }
188
189 #[must_use]
191 pub fn as_regex(&self) -> Option<&str> {
192 match self {
193 Self::Regex { pattern, .. } => Some(pattern),
194 _ => None,
195 }
196 }
197
198 #[must_use]
200 pub fn as_bool(&self) -> Option<bool> {
201 match self {
202 Self::Bool(b) => Some(*b),
203 _ => None,
204 }
205 }
206
207 #[must_use]
209 pub fn is_unknown(&self) -> bool {
210 matches!(self, Self::Unknown | Self::OneOf(_))
211 }
212
213 #[must_use]
215 pub fn alternatives(&self) -> &[ConfigValue] {
216 match self {
217 Self::OneOf(alternatives) => alternatives,
218 other => std::slice::from_ref(other),
219 }
220 }
221
222 #[must_use]
224 pub fn has_unknown(&self) -> bool {
225 match self {
226 Self::Unknown | Self::OneOf(_) => true,
227 Self::Array(items) => items.iter().any(Self::has_unknown),
228 Self::Object(entries) => entries.iter().any(|(_, v)| v.has_unknown()),
229 _ => false,
230 }
231 }
232}
233
234#[derive(Debug, Clone, PartialEq)]
237enum Value {
238 Null,
239 Undefined,
242 Bool(bool),
243 Number(f64),
244 String(String),
245 Array(Vec<Value>),
246 Object(Vec<(String, Value)>),
247 Regex {
248 pattern: String,
249 flags: String,
250 },
251 Unknown,
252 OneOf(Vec<Value>),
254 Maybe(Box<Value>),
260}
261
262impl Value {
263 fn get(&self, key: &str) -> Option<&Value> {
265 match self {
266 Self::Object(entries) => entries.iter().rev().find(|(k, _)| k == key).map(|(_, v)| v),
267 _ => None,
268 }
269 }
270
271 fn as_str(&self) -> Option<&str> {
273 match self {
274 Self::String(s) => Some(s),
275 _ => None,
276 }
277 }
278
279 fn doubtful(value: Self, definite: bool) -> Self {
281 if definite || matches!(value, Self::Unknown | Self::Maybe(_)) {
282 value
283 } else {
284 Self::Maybe(Box::new(value))
285 }
286 }
287
288 fn split(self) -> (Self, bool) {
290 match self {
291 Self::Maybe(value) => (*value, false),
292 value => (value, true),
293 }
294 }
295
296 fn is_definite(&self) -> bool {
298 !matches!(self, Self::Maybe(_))
299 }
300
301 fn definite(&self) -> &Self {
303 match self {
304 Self::Maybe(value) => value,
305 value => value,
306 }
307 }
308
309 fn definite_mut(&mut self) -> &mut Self {
311 match self {
312 Self::Maybe(value) => value,
313 value => value,
314 }
315 }
316}
317
318impl From<ConfigValue> for Value {
319 fn from(value: ConfigValue) -> Self {
320 match value {
321 ConfigValue::Null => Self::Null,
322 ConfigValue::Bool(b) => Self::Bool(b),
323 ConfigValue::Number(n) => Self::Number(n),
324 ConfigValue::String(s) => Self::String(s),
325 ConfigValue::Array(items) => Self::Array(items.into_iter().map(Self::from).collect()),
326 ConfigValue::Object(entries) => Self::Object(
327 entries
328 .into_iter()
329 .map(|(key, value)| (key, Self::from(value)))
330 .collect(),
331 ),
332 ConfigValue::Regex { pattern, flags } => Self::Regex { pattern, flags },
333 ConfigValue::Unknown => Self::Unknown,
334 ConfigValue::OneOf(alternatives) => Self::OneOf(alternatives.into_iter().map(Self::from).collect()),
335 }
336 }
337}
338
339impl From<Value> for ConfigValue {
340 fn from(value: Value) -> Self {
341 match value {
342 Value::Null | Value::Undefined => Self::Null,
343 Value::Bool(b) => Self::Bool(b),
344 Value::Number(n) => Self::Number(n),
345 Value::String(s) => Self::String(s),
346 Value::Array(items) => Self::Array(items.into_iter().map(Self::from).collect()),
347 Value::Object(entries) => Self::Object(
348 entries
349 .into_iter()
350 .map(|(key, value)| (key, Self::from(value)))
351 .collect(),
352 ),
353 Value::Regex { pattern, flags } => Self::Regex { pattern, flags },
354 Value::Unknown => Self::Unknown,
355 Value::OneOf(alternatives) => {
356 let mut out: Vec<Self> = Vec::new();
358 for alternative in alternatives.into_iter().map(Self::from) {
359 if !out.contains(&alternative) {
360 out.push(alternative);
361 }
362 }
363 match out.len() {
364 1 => out.pop().unwrap_or(Self::Unknown),
365 _ => Self::OneOf(out),
366 }
367 }
368 Value::Maybe(value) => Self::from(*value),
369 }
370 }
371}
372
373pub trait ModuleLoader {
380 fn load(&self, from: &Path, specifier: &str) -> Option<(PathBuf, String)>;
382
383 fn is_installed(&self, _from: &Path, _specifier: &str) -> bool {
391 false
392 }
393
394 fn holds_no_code(&self, _from: &Path, _specifier: &str) -> bool {
399 false
400 }
401}
402
403#[must_use]
469pub fn extract_config_object(source: &str, path: &Path) -> Option<ConfigValue> {
470 evaluate_config(source, path, None).map(|config| config.value)
471}
472
473#[derive(Debug, Clone, PartialEq)]
475#[non_exhaustive]
476pub struct ExtractedConfig {
477 pub value: ConfigValue,
479 pub call_arguments: Vec<ConfigValue>,
491}
492
493#[must_use]
504pub fn extract_config(source: &str, path: &Path, loader: Option<&dyn ModuleLoader>) -> Option<ExtractedConfig> {
505 let Some(loader) = loader else {
506 return evaluate_config(source, path, None);
507 };
508 let modules = Modules {
509 loader,
510 root: path.parent().map_or_default(Path::to_path_buf),
511 active: RefCell::new(vec![(path.to_path_buf(), Imported::Default)]),
512 loads: RefCell::new(HashSet::default()),
513 evaluations: Cell::new(0),
514 cuts: Cell::new(0),
515 values: RefCell::new(HashMap::default()),
516 loaded: RefCell::new(HashMap::default()),
517 changing: RefCell::new(HashMap::default()),
518 pending: RefCell::new(Vec::new()),
519 shallowest_cut: Cell::new(usize::MAX),
520 analyses: Cell::new(0),
521 installed: RefCell::new(HashMap::default()),
522 };
523 evaluate_config(source, path, Some(&modules))
524}
525
526#[must_use]
528pub fn extract_config_object_with(source: &str, path: &Path, loader: &dyn ModuleLoader) -> Option<ConfigValue> {
529 extract_config(source, path, Some(loader)).map(|config| config.value)
530}
531
532fn evaluate_config(source: &str, path: &Path, modules: Option<&Modules<'_>>) -> Option<ExtractedConfig> {
533 crate::with_arena(|allocator| {
534 let program = parse_program(allocator, source, path)?;
535 let followed = modules.map(|modules| FollowedImports { modules, from: path });
536 let module_imports: &dyn Imports = match &followed {
537 Some(followed) => followed,
538 None => &NoImports,
539 };
540 let top = top_level(&program, module_imports);
541 let bindings = module_bindings(&program);
542 let imports = import_bindings(&program);
543 let file = path.file_name().and_then(|name| name.to_str()).unwrap_or_default();
544 let eval = Evaluator::new(&top, &bindings, &imports, file, ".", path, modules);
545 let exported = exported(&program)?;
546 let value = eval.exported_value(exported, exports_changed(&program));
547 let call_arguments = eval.exported_call_arguments(exported, &value);
548 Some(ExtractedConfig {
549 value: ConfigValue::from(value),
550 call_arguments: call_arguments.into_iter().map(ConfigValue::from).collect(),
551 })
552 })
553}
554
555#[derive(Debug, Clone, PartialEq, Eq, Hash)]
557enum Imported {
558 Default,
559 Named(String),
560 Namespace,
561}
562
563impl Imported {
564 fn named(name: String) -> Self {
565 if name == "default" {
566 Self::Default
567 } else {
568 Self::Named(name)
569 }
570 }
571}
572
573struct ImportBinding {
575 specifier: String,
576 imported: Imported,
577}
578
579fn import_bindings(program: &Program<'_>) -> HashMap<String, ImportBinding> {
581 let mut imports = HashMap::default();
582 for stmt in &program.body {
583 let Statement::ImportDeclaration(d) = stmt else {
584 continue;
585 };
586 if d.import_kind.is_type() {
587 continue;
588 }
589 let specifier = crate::js_string(d.source.value);
590 for item in d.specifiers.iter().flatten() {
591 let (local, imported) = match item {
592 ImportDeclarationSpecifier::ImportSpecifier(s) if s.import_kind == ImportOrExportKind::Value => (
593 s.local.name.as_str(),
594 Imported::named(crate::collect::module_export_name(&s.imported)),
595 ),
596 ImportDeclarationSpecifier::ImportSpecifier(_) => continue,
597 ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => (s.local.name.as_str(), Imported::Default),
598 ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => (s.local.name.as_str(), Imported::Namespace),
599 };
600 imports.insert(
601 local.to_owned(),
602 ImportBinding {
603 specifier: specifier.clone(),
604 imported,
605 },
606 );
607 }
608 }
609 imports
610}
611
612enum ExportSource {
614 Local(String),
616 From(String, Imported),
618}
619
620fn export_table(program: &Program<'_>) -> (HashMap<String, ExportSource>, Vec<String>) {
622 let mut table = HashMap::default();
623 let mut stars = Vec::new();
624 for stmt in &program.body {
625 match stmt {
626 Statement::ExportDeclaration(e) => match &e.declaration {
627 Declaration::VariableDeclaration(v) => {
628 for d in &v.declarations {
629 if let BindingPattern::BindingIdentifier(id) = &d.id {
630 let name = id.name.as_str().to_owned();
631 table.insert(name.clone(), ExportSource::Local(name));
632 }
633 }
634 }
635 Declaration::FunctionDeclaration(f) => {
636 if let Some(id) = &f.id {
637 let name = id.name.as_str().to_owned();
638 table.insert(name.clone(), ExportSource::Local(name));
639 }
640 }
641 _ => {}
642 },
643 Statement::ExportNamedDeclaration(e) if !e.export_kind.is_type() => {
644 for s in e.specifiers.iter().filter(|s| !s.export_kind.is_type()) {
645 table.insert(
646 crate::collect::module_export_name(&s.exported),
647 ExportSource::Local(crate::collect::module_export_name(&s.local)),
648 );
649 }
650 }
651 Statement::ExportFromDeclaration(e) if !e.export_kind.is_type() => {
652 let from = crate::js_string(e.source.value);
653 for s in e.specifiers.iter().filter(|s| !s.export_kind.is_type()) {
654 table.insert(
655 crate::collect::module_export_name(&s.exported),
656 ExportSource::From(
657 from.clone(),
658 Imported::named(crate::collect::module_export_name(&s.local)),
659 ),
660 );
661 }
662 }
663 Statement::ExportAllDeclaration(e) if !e.export_kind.is_type() => {
664 let from = crate::js_string(e.source.value);
665 match &e.exported {
666 Some(name) => {
667 table.insert(
668 crate::collect::module_export_name(name),
669 ExportSource::From(from, Imported::Namespace),
670 );
671 }
672 None => stars.push(from),
673 }
674 }
675 _ => {}
676 }
677 }
678 (table, stars)
679}
680
681type LoadedModule = (PathBuf, Rc<str>);
683
684struct Modules<'l> {
686 loader: &'l dyn ModuleLoader,
687 root: PathBuf,
689 active: RefCell<Vec<(PathBuf, Imported)>>,
692 loads: RefCell<HashSet<PathBuf>>,
694 evaluations: Cell<usize>,
696 cuts: Cell<usize>,
700 values: RefCell<HashMap<(PathBuf, Imported), Option<Value>>>,
702 loaded: RefCell<HashMap<(PathBuf, String), Option<LoadedModule>>>,
704 changing: RefCell<HashMap<PathBuf, Option<Rc<ModuleFacts>>>>,
706 pending: RefCell<Vec<PathBuf>>,
708 shallowest_cut: Cell<usize>,
711 analyses: Cell<usize>,
713 installed: RefCell<HashMap<(PathBuf, String), bool>>,
715}
716
717impl Modules<'_> {
718 fn load(&self, from: &Path, specifier: &str) -> Option<LoadedModule> {
720 let key = (from.parent().unwrap_or(from).to_path_buf(), specifier.to_owned());
721 if let Some(known) = self.loaded.borrow().get(&key) {
722 return known.clone();
723 }
724 let module = self
725 .loader
726 .load(from, specifier)
727 .map(|(path, source)| (path, Rc::from(source)));
728 self.loaded.borrow_mut().insert(key, module.clone());
729 module
730 }
731
732 fn follows(&self, from: &Path, specifier: &str) -> bool {
734 self.load(from, specifier).is_some()
735 }
736
737 fn is_installed(&self, from: &Path, specifier: &str) -> bool {
740 let key = (from.parent().unwrap_or(from).to_path_buf(), specifier.to_owned());
741 if let Some(&known) = self.installed.borrow().get(&key) {
742 return known;
743 }
744 let installed = self.loader.is_installed(from, specifier);
745 self.installed.borrow_mut().insert(key, installed);
746 installed
747 }
748
749 fn changes_modules(&self, from: &Path, specifier: &str) -> bool {
756 self.facts(from, specifier).is_some_and(|(_, facts)| facts.changes)
757 }
758
759 fn result_places(&self, from: &Path, specifier: &str, export: &str) -> Option<Vec<Write>> {
763 match self.export_result_places(from, specifier, export, 0) {
764 ExportResult::Places(places) => Some(places),
765 ExportResult::Missing | ExportResult::Unknown => None,
766 }
767 }
768
769 fn export_result_places(&self, from: &Path, specifier: &str, export: &str, depth: usize) -> ExportResult {
771 if depth >= MAX_IMPORT_DEPTH {
772 return ExportResult::Unknown;
773 }
774 let Some((path, facts)) = self.facts(from, specifier) else {
775 return ExportResult::Missing;
776 };
777 let local = match facts.exports.get(export) {
778 Some(ExportSource::Local(local)) => Some(local.as_str()),
779 Some(ExportSource::From(next, imported)) => {
780 let name = match imported {
781 Imported::Default => "default",
782 Imported::Named(name) => name.as_str(),
783 Imported::Namespace => return ExportResult::Unknown,
784 };
785 return match self.export_result_places(&path, next, name, depth + 1) {
786 ExportResult::Missing => ExportResult::Unknown,
787 found => found,
788 };
789 }
790 None if export == "default" => facts.default.as_deref(),
791 None => {
792 return facts
793 .stars
794 .iter()
795 .map(|next| self.export_result_places(&path, next, export, depth + 1))
796 .find(|found| !matches!(found, ExportResult::Missing))
797 .unwrap_or(ExportResult::Missing);
798 }
799 };
800 local
801 .and_then(|local| facts.results.get(local).cloned())
802 .map_or(ExportResult::Unknown, ExportResult::Places)
803 }
804
805 fn facts(&self, from: &Path, specifier: &str) -> Option<(PathBuf, Rc<ModuleFacts>)> {
810 let (path, source) = self.load(from, specifier)?;
811 if let Some(known) = self.changing.borrow().get(&path) {
812 let facts = known.clone().unwrap_or_else(|| {
813 let at = self.pending.borrow().iter().position(|pending| *pending == path);
814 self.shallowest_cut.set(self.shallowest_cut.get().min(at.unwrap_or(0)));
815 Rc::new(ModuleFacts::unknown())
816 });
817 return Some((path, facts));
818 }
819 let depth = self.pending.borrow().len();
820 if depth >= MAX_IMPORT_DEPTH
821 || self.changing.borrow().len() >= MAX_MODULE_LOADS
822 || self.analyses.get() >= MAX_MODULE_ANALYSES
823 {
824 self.shallowest_cut.set(0);
825 return Some((path, Rc::new(ModuleFacts::unknown())));
826 }
827 self.analyses.set(self.analyses.get() + 1);
828 self.changing.borrow_mut().insert(path.clone(), None);
829 self.pending.borrow_mut().push(path.clone());
830 let outer = self.shallowest_cut.replace(usize::MAX);
831 let facts = Rc::new(self.module_facts(&path, &source));
832 self.pending.borrow_mut().pop();
833 let cut = self.shallowest_cut.get();
834 if cut < depth {
835 self.changing.borrow_mut().remove(&path);
836 self.shallowest_cut.set(outer.min(cut));
837 } else {
838 self.changing.borrow_mut().insert(path.clone(), Some(Rc::clone(&facts)));
839 self.shallowest_cut.set(outer);
840 }
841 Some((path, facts))
842 }
843
844 fn module_facts(&self, path: &Path, source: &str) -> ModuleFacts {
846 let Ok(source_type) = SourceType::from_path(path) else {
847 return ModuleFacts::unknown();
848 };
849 let allocator = Allocator::default();
850 let ret = Parser::new(&allocator, source, source_type).parse();
851 if ret.diagnostics.errors().next().is_some() {
852 return ModuleFacts::unknown();
853 }
854 let unstable = Unstable::of(
855 &ret.program,
856 &FollowedImports {
857 modules: self,
858 from: path,
859 },
860 );
861 let changes = unstable.changes_modules()
862 || unstable
863 .exposed_modules()
864 .iter()
865 .any(|specifier| self.changes_modules(path, specifier))
866 || self.reexports_change_modules(path, &ret.program);
867 let (exports, stars) = export_table(&ret.program);
868 let default = exported(&ret.program).and_then(|exported| match exported {
869 Exported::Function(function) => function.id.as_ref().map(|id| id.name.as_str().to_owned()),
870 Exported::Expression(expr) => match expr.get_inner_expression() {
871 Expression::Identifier(id) => Some(id.name.as_str().to_owned()),
872 _ => None,
873 },
874 });
875 ModuleFacts {
876 changes,
877 results: unstable.result_places().clone(),
878 exports,
879 stars,
880 default,
881 }
882 }
883
884 fn reexports_change_modules(&self, path: &Path, program: &Program<'_>) -> bool {
889 reexported(program).into_iter().any(|specifier| {
890 if self.follows(path, &specifier) {
891 self.changes_modules(path, &specifier)
892 } else {
893 !self.is_installed(path, &specifier) && !self.loader.holds_no_code(path, &specifier)
894 }
895 })
896 }
897
898 fn evaluate(&self, from: &Path, specifier: &str, imported: &Imported, args: Option<&[Value]>) -> Option<Value> {
901 let (path, source) = self.load(from, specifier)?;
902 let key = (path.clone(), imported.clone());
903 if args.is_none()
904 && let Some(known) = self.values.borrow().get(&key)
905 {
906 return known.clone();
907 }
908 if self.cut(&key) {
909 self.cuts.set(self.cuts.get() + 1);
910 return Some(Value::Unknown);
911 }
912 self.loads.borrow_mut().insert(path.clone());
913 self.evaluations.set(self.evaluations.get() + 1);
914 let cuts = self.cuts.get();
915 self.active.borrow_mut().push(key.clone());
916 let value = self.evaluate_module(&path, &source, imported, args).map(as_imported);
917 self.active.borrow_mut().pop();
918 if args.is_none() && self.cuts.get() == cuts {
919 self.values.borrow_mut().insert(key, value.clone());
920 }
921 value
922 }
923
924 fn cut(&self, key: &(PathBuf, Imported)) -> bool {
927 let active = self.active.borrow();
928 let loads = self.loads.borrow();
929 active.contains(key)
930 || active.len() >= MAX_IMPORT_DEPTH
931 || (!loads.contains(&key.0) && loads.len() >= MAX_MODULE_LOADS)
932 || self.evaluations.get() >= MAX_MODULE_EVALUATIONS
933 }
934
935 fn evaluate_module(&self, path: &Path, source: &str, imported: &Imported, args: Option<&[Value]>) -> Option<Value> {
936 let source_type = SourceType::from_path(path).ok()?;
937 let allocator = Allocator::default();
938 let ret = Parser::new(&allocator, source, source_type)
939 .with_options(OxcParseOptions {
940 preserve_parens: false,
941 ..OxcParseOptions::default()
942 })
943 .parse();
944 if ret.diagnostics.errors().next().is_some() {
945 return Some(Value::Unknown);
946 }
947 let program = &ret.program;
948 let top = top_level(
949 program,
950 &FollowedImports {
951 modules: self,
952 from: path,
953 },
954 );
955 let bindings = module_bindings(program);
956 let imports = import_bindings(program);
957 let dir = relative_dir(&self.root, path.parent().unwrap_or(path));
958 let file = path.file_name().and_then(|name| name.to_str()).unwrap_or_default();
959 let eval = Evaluator::new(&top, &bindings, &imports, file, &dir, path, Some(self));
960 match imported {
961 Imported::Namespace => Some(Value::Unknown),
962 Imported::Default => Some(match exported(program)? {
963 Exported::Function(function) => eval.invoke(Callable::Function(function), args, true, 0),
964 Exported::Expression(expr) => {
965 match (args, Callable::of_expression(expr), expr.get_inner_expression()) {
966 (None, _, _) => eval.expr(expr, 0),
967 (Some(args), Some(callable), _) => eval.invoke(callable, Some(args), true, 0),
968 (Some(args), None, Expression::Identifier(id)) => eval.call_named(id.name.as_str(), args),
969 (Some(_), None, _) => Value::Unknown,
970 }
971 }
972 })
973 .map(|value| {
974 if exports_changed(program) {
975 changed(value)
976 } else {
977 value
978 }
979 }),
980 Imported::Named(name) => {
981 let (table, stars) = export_table(program);
982 match table.get(name) {
983 Some(ExportSource::Local(local)) => Some(match args {
984 None => eval.identifier(local, 0),
985 Some(args) => eval.call_named(local, args),
986 }),
987 Some(ExportSource::From(from, imported)) => self.evaluate(path, from, imported, args),
988 None => stars.iter().find_map(|from| self.evaluate(path, from, imported, args)),
989 }
990 }
991 }
992 }
993}
994
995fn reexported(program: &Program<'_>) -> Vec<String> {
998 program
999 .body
1000 .iter()
1001 .filter_map(|stmt| match stmt {
1002 Statement::ExportFromDeclaration(e) if !e.export_kind.is_type() => Some(&e.source),
1003 Statement::ExportAllDeclaration(e) if !e.export_kind.is_type() => Some(&e.source),
1004 _ => None,
1005 })
1006 .map(|source| crate::js_string(source.value))
1007 .collect()
1008}
1009
1010enum ExportResult {
1012 Missing,
1014 Unknown,
1016 Places(Vec<Write>),
1018}
1019
1020struct ModuleFacts {
1022 changes: bool,
1024 results: HashMap<String, Vec<Write>>,
1027 exports: HashMap<String, ExportSource>,
1029 stars: Vec<String>,
1031 default: Option<String>,
1033}
1034
1035impl ModuleFacts {
1036 fn unknown() -> Self {
1039 Self {
1040 changes: true,
1041 results: HashMap::default(),
1042 exports: HashMap::default(),
1043 stars: Vec::new(),
1044 default: None,
1045 }
1046 }
1047}
1048
1049fn as_imported(value: Value) -> Value {
1052 match value {
1053 Value::Object(entries) => Value::Object(
1054 entries
1055 .into_iter()
1056 .map(|(key, value)| {
1057 let key = if key == OWN_SPREAD {
1058 UNKNOWN_SPREAD.to_owned()
1059 } else {
1060 key
1061 };
1062 (key, as_imported(value))
1063 })
1064 .collect(),
1065 ),
1066 Value::Array(items) => Value::Array(items.into_iter().map(as_imported).collect()),
1067 Value::OneOf(alternatives) => Value::OneOf(alternatives.into_iter().map(as_imported).collect()),
1068 Value::Maybe(value) => Value::Maybe(Box::new(as_imported(*value))),
1069 other => other,
1070 }
1071}
1072
1073fn relative_dir(from: &Path, to: &Path) -> String {
1075 let from: Vec<Component<'_>> = from.components().collect();
1076 let to: Vec<Component<'_>> = to.components().collect();
1077 let common = from.iter().zip(&to).take_while(|(a, b)| a == b).count();
1078 let mut parts: Vec<String> = vec!["..".to_owned(); from.len() - common];
1079 parts.extend(
1080 to[common..]
1081 .iter()
1082 .map(|part| part.as_os_str().to_string_lossy().into_owned()),
1083 );
1084 let parts: Vec<&str> = parts.iter().map(String::as_str).collect();
1085 normalized(&parts)
1086}
1087
1088fn first_parameter(callable: Callable<'_, '_>) -> Option<String> {
1090 let params = match callable {
1091 Callable::Function(f) => &f.params,
1092 Callable::Arrow(f) => &f.params,
1093 };
1094 match ¶ms.items.first()?.pattern {
1095 BindingPattern::BindingIdentifier(id) => Some(id.name.as_str().to_owned()),
1096 _ => None,
1097 }
1098}
1099
1100struct TopLevel<'p, 'a> {
1102 consts: HashMap<String, &'p Expression<'a>>,
1103 functions: HashMap<String, Callable<'p, 'a>>,
1104 changed: HashMap<String, TopChange>,
1107 unstable: Unstable,
1109}
1110
1111#[derive(Clone, Copy)]
1113enum Callable<'p, 'a> {
1114 Function(&'p Function<'a>),
1115 Arrow(&'p ArrowFunctionExpression<'a>),
1116}
1117
1118impl<'p, 'a> Callable<'p, 'a> {
1119 fn same(self, other: Self) -> bool {
1121 match (self, other) {
1122 (Self::Function(a), Self::Function(b)) => std::ptr::eq(a, b),
1123 (Self::Arrow(a), Self::Arrow(b)) => std::ptr::eq(a, b),
1124 _ => false,
1125 }
1126 }
1127
1128 fn of_expression(expr: &'p Expression<'a>) -> Option<Self> {
1129 match expr.get_inner_expression() {
1130 Expression::ArrowFunctionExpression(f) => Some(Self::Arrow(f)),
1131 Expression::FunctionExpression(f) => Some(Self::Function(f)),
1132 _ => None,
1133 }
1134 }
1135}
1136
1137#[derive(Clone, Copy)]
1139enum LocalBinding<'p, 'a> {
1140 Function(Callable<'p, 'a>),
1141 Other,
1142}
1143
1144#[derive(Clone)]
1146enum Binding<'p, 'a> {
1147 Value(Value),
1148 Callable(Callable<'p, 'a>),
1149 Alias(Alias),
1152}
1153
1154#[derive(Clone)]
1157struct Alias {
1158 value: Value,
1159 root: String,
1160 frame: usize,
1161 keys: Vec<String>,
1162}
1163
1164impl Alias {
1165 fn same_part(&self, other: &Self) -> bool {
1167 self.root == other.root && self.frame == other.frame && self.keys == other.keys
1168 }
1169}
1170
1171enum Edit {
1173 Set(String, Value),
1175 Insert { front: bool, items: Vec<Value> },
1177 Taint,
1179}
1180
1181enum TopChange {
1183 Assigned,
1185 Written(Vec<Write>),
1187}
1188
1189type Frame<'p, 'a> = HashMap<String, Binding<'p, 'a>>;
1190
1191struct NoImports;
1193
1194impl Imports for NoImports {
1195 fn follows(&self, _: &str) -> bool {
1196 false
1197 }
1198
1199 fn changes_modules(&self, _: &str) -> bool {
1200 false
1201 }
1202
1203 fn is_installed(&self, _: &str) -> bool {
1205 true
1206 }
1207}
1208
1209struct FollowedImports<'m, 'l> {
1211 modules: &'m Modules<'l>,
1212 from: &'m Path,
1213}
1214
1215impl Imports for FollowedImports<'_, '_> {
1216 fn follows(&self, specifier: &str) -> bool {
1217 self.modules.follows(self.from, specifier)
1218 }
1219
1220 fn changes_modules(&self, specifier: &str) -> bool {
1221 self.modules.changes_modules(self.from, specifier)
1222 }
1223
1224 fn result_places(&self, specifier: &str, export: &str) -> Option<Vec<Write>> {
1225 self.modules.result_places(self.from, specifier, export)
1226 }
1227
1228 fn is_installed(&self, specifier: &str) -> bool {
1229 self.modules.is_installed(self.from, specifier)
1230 }
1231}
1232
1233fn top_level<'p, 'a>(program: &'p Program<'a>, imports: &dyn Imports) -> TopLevel<'p, 'a> {
1235 let unstable = Unstable::of(program, imports);
1236 let mut top = TopLevel {
1237 consts: HashMap::default(),
1238 functions: HashMap::default(),
1239 changed: HashMap::default(),
1240 unstable: Unstable::default(),
1241 };
1242 for stmt in &program.body {
1243 let decl = match stmt {
1244 Statement::ExportDeclaration(e) => Some(&e.declaration),
1245 other => other.as_declaration(),
1246 };
1247 let writes = match stmt {
1248 Statement::ExpressionStatement(s) => Some(Writes::of_effect(&s.expression, &unstable)),
1251 Statement::IfStatement(_)
1252 | Statement::BlockStatement(_)
1253 | Statement::ForStatement(_)
1254 | Statement::ForInStatement(_)
1255 | Statement::ForOfStatement(_)
1256 | Statement::WhileStatement(_)
1257 | Statement::DoWhileStatement(_)
1258 | Statement::SwitchStatement(_)
1259 | Statement::TryStatement(_)
1260 | Statement::LabeledStatement(_)
1261 | Statement::WithStatement(_)
1262 | Statement::VariableDeclaration(_)
1263 | Statement::ExportNamedDeclaration(_)
1264 | Statement::ExportDefaultDeclaration(_) => Some(Writes::of_statement(stmt, &unstable)),
1265 _ => None,
1266 };
1267 if let Some(writes) = writes {
1268 top.record(writes);
1269 }
1270 match decl {
1271 Some(Declaration::VariableDeclaration(v)) if v.kind == VariableDeclarationKind::Const => {
1272 for d in &v.declarations {
1273 if let (BindingPattern::BindingIdentifier(id), Some(init)) = (&d.id, &d.init) {
1274 top.consts.insert(id.name.as_str().to_string(), init);
1275 if let Some(callable) = Callable::of_expression(init) {
1276 top.functions.insert(id.name.as_str().to_string(), callable);
1277 }
1278 }
1279 }
1280 }
1281 Some(Declaration::FunctionDeclaration(f)) => {
1282 if let Some(id) = &f.id {
1283 top.functions
1284 .insert(id.name.as_str().to_string(), Callable::Function(f));
1285 }
1286 }
1287 _ => {}
1288 }
1289 }
1290 top.unstable = unstable;
1291 top
1292}
1293
1294const ARRAY_CALLBACK_METHODS: &[&str] = &["map", "flatMap", "filter"];
1296
1297impl TopLevel<'_, '_> {
1298 fn record(&mut self, writes: Writes<'_>) {
1300 for name in writes.assigned {
1301 self.changed.insert(name, TopChange::Assigned);
1302 }
1303 for (name, write) in writes.changed {
1304 if let TopChange::Written(list) = self
1305 .changed
1306 .entry(name)
1307 .or_insert_with(|| TopChange::Written(Vec::new()))
1308 {
1309 list.push(write);
1310 }
1311 }
1312 }
1313}
1314
1315const PATH_FUNCTIONS: &[&str] = &["resolve", "join", "dirname", "normalize"];
1317
1318#[derive(Default)]
1320struct ModuleBindings {
1321 path_modules: HashSet<String>,
1322 path_functions: HashMap<String, String>,
1323 url_modules: HashSet<String>,
1324 file_url_to_path: HashSet<String>,
1325 glob_names: HashSet<String>,
1327 merge_names: HashSet<String>,
1329 foreign_merges: HashSet<String>,
1331 flat_config_functions: HashSet<String>,
1334 flat_config_modules: HashSet<String>,
1336}
1337
1338impl ModuleBindings {
1339 fn bind(&mut self, module: &str, member: Option<&str>, local: &str) {
1341 let local = local.to_owned();
1342 if MERGE_PACKAGES.contains(&module) {
1343 self.merge_names.insert(local.clone());
1344 } else if AMBIGUOUS_MERGES.contains(&local.as_str()) {
1345 self.foreign_merges.insert(local.clone());
1346 }
1347 match (module.strip_prefix("node:").unwrap_or(module), member) {
1348 ("path" | "path/posix", None | Some("default")) => {
1349 self.path_modules.insert(local);
1350 }
1351 ("path" | "path/posix", Some(name)) if PATH_FUNCTIONS.contains(&name) => {
1352 self.path_functions.insert(local, name.to_owned());
1353 }
1354 ("url", None | Some("default")) => {
1355 self.url_modules.insert(local);
1356 }
1357 ("url", Some("fileURLToPath")) => {
1358 self.file_url_to_path.insert(local);
1359 }
1360 (package, _) if GLOB_PACKAGES.contains(&package) => {
1361 self.glob_names.insert(local);
1362 }
1363 ("fs", Some("globSync")) | ("fs/promises", Some("glob")) => {
1364 self.glob_names.insert(local);
1365 }
1366 ("eslint/config", Some("defineConfig")) | ("typescript-eslint", Some("config")) => {
1367 self.flat_config_functions.insert(local);
1368 }
1369 ("typescript-eslint", None | Some("default")) => {
1370 self.flat_config_modules.insert(local);
1371 }
1372 _ => {}
1373 }
1374 }
1375}
1376
1377fn module_bindings(program: &Program<'_>) -> ModuleBindings {
1378 let mut bindings = ModuleBindings::default();
1379 for stmt in &program.body {
1380 if let Statement::ImportDeclaration(d) = stmt {
1381 let module = d.source.value.as_str().unwrap_or_default();
1382 for specifier in d.specifiers.iter().flatten() {
1383 match specifier {
1384 ImportDeclarationSpecifier::ImportSpecifier(s) => {
1385 let imported = crate::collect::module_export_name(&s.imported);
1386 bindings.bind(module, Some(&imported), s.local.name.as_str());
1387 }
1388 ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => {
1389 bindings.bind(module, None, s.local.name.as_str());
1390 }
1391 ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => {
1392 bindings.bind(module, None, s.local.name.as_str());
1393 }
1394 }
1395 }
1396 } else if let Some(Declaration::VariableDeclaration(v)) = stmt.as_declaration() {
1397 for declarator in &v.declarations {
1398 if let Some(module) = declarator.init.as_ref().and_then(required_module) {
1399 bind_required(&mut bindings, &module, &declarator.id);
1400 } else if let Some(Expression::Identifier(source)) =
1401 declarator.init.as_ref().map(Expression::get_inner_expression)
1402 {
1403 let module = if bindings.path_modules.contains(source.name.as_str()) {
1405 Some("path")
1406 } else if bindings.url_modules.contains(source.name.as_str()) {
1407 Some("url")
1408 } else {
1409 None
1410 };
1411 if let Some(module) = module {
1412 bind_required(&mut bindings, module, &declarator.id);
1413 }
1414 }
1415 }
1416 }
1417 }
1418 bindings
1419}
1420
1421fn required_module(init: &Expression<'_>) -> Option<String> {
1423 let Expression::CallExpression(call) = init.get_inner_expression() else {
1424 return None;
1425 };
1426 match &call.callee {
1427 Expression::Identifier(id) if id.name.as_str() == "require" => call
1428 .arguments
1429 .first()
1430 .and_then(Argument::as_expression)
1431 .and_then(static_string),
1432 _ => None,
1433 }
1434}
1435
1436fn bind_required(bindings: &mut ModuleBindings, module: &str, id: &BindingPattern<'_>) {
1437 match id {
1438 BindingPattern::BindingIdentifier(local) => bindings.bind(module, None, local.name.as_str()),
1439 BindingPattern::ObjectPattern(pattern) => {
1440 for property in &pattern.properties {
1441 if let (Some(key), BindingPattern::BindingIdentifier(local)) =
1442 (property.key.static_name(), &property.value)
1443 {
1444 bindings.bind(module, Some(&crate::js_string(key.as_js_str())), local.name.as_str());
1445 }
1446 }
1447 }
1448 _ => {}
1449 }
1450}
1451
1452fn normalized(parts: &[&str]) -> String {
1454 let mut segments: Vec<&str> = Vec::new();
1455 for part in parts.iter().flat_map(|part| part.split(['/', '\\'])) {
1456 match part {
1457 "" | "." => {}
1458 ".." if segments.last().is_some_and(|last| *last != "..") => {
1459 segments.pop();
1460 }
1461 other => segments.push(other),
1462 }
1463 }
1464 match segments.first() {
1465 None => ".".to_owned(),
1466 Some(&"..") => segments.join("/"),
1467 Some(_) => format!("./{}", segments.join("/")),
1468 }
1469}
1470
1471fn apply_path_function(function: &str, args: &[Value]) -> Value {
1473 let Some(parts) = args.iter().map(Value::as_str).collect::<Option<Vec<&str>>>() else {
1474 return Value::Unknown;
1475 };
1476 let absolute = |part: &&str| part.starts_with(['/', '\\']);
1477 let path = match (function, parts.as_slice()) {
1478 ("fileURLToPath", [file]) if !file.contains(':') => (*file).to_owned(),
1480 ("dirname", [path]) => normalized(&[*path, ".."]),
1481 ("normalize", [path]) => normalized(&[*path]),
1482 ("resolve", parts) if !parts.iter().any(absolute) => normalized(parts),
1483 ("join", parts) if !parts.first().is_some_and(absolute) => normalized(parts),
1484 _ => return Value::Unknown,
1485 };
1486 Value::String(path)
1487}
1488
1489#[must_use]
1493pub fn extract_constructor_options(source: &str, path: &Path, constructor: &str, key: &str) -> Vec<ConfigValue> {
1494 let Ok(source_type) = SourceType::from_path(path) else {
1495 return Vec::new();
1496 };
1497 crate::with_arena(|allocator| {
1498 let ret = Parser::new(allocator, source, source_type)
1499 .with_options(OxcParseOptions {
1500 preserve_parens: false,
1501 ..OxcParseOptions::default()
1502 })
1503 .parse();
1504 if ret.diagnostics.errors().next().is_some() {
1505 return Vec::new();
1506 }
1507 let program = &ret.program;
1508 let top = top_level(program, &NoImports);
1509 let bindings = module_bindings(program);
1510 let mut finder = OptionFinder {
1511 top: &top,
1512 bindings: &bindings,
1513 file: path.file_name().and_then(|name| name.to_str()).unwrap_or_default(),
1514 constructor,
1515 key,
1516 found: Vec::new(),
1517 };
1518 finder.visit_program(program);
1519 finder.found.into_iter().map(ConfigValue::from).collect()
1520 })
1521}
1522
1523#[must_use]
1529pub fn extract_property_values(source: &str, path: &Path, key: &str) -> Vec<ConfigValue> {
1530 find_properties(source, path, key, None)
1531}
1532
1533#[must_use]
1540pub fn extract_property_calls(source: &str, path: &Path, key: &str, argument: &ConfigValue) -> Vec<ConfigValue> {
1541 find_properties(source, path, key, Some(argument))
1542}
1543
1544fn find_properties(source: &str, path: &Path, key: &str, call_with: Option<&ConfigValue>) -> Vec<ConfigValue> {
1546 crate::with_arena(|allocator| {
1547 let Some(program) = parse_program(allocator, source, path) else {
1548 return Vec::new();
1549 };
1550 let top = top_level(&program, &NoImports);
1551 let bindings = module_bindings(&program);
1552 let mut finder = PropertyFinder {
1553 top: &top,
1554 bindings: &bindings,
1555 file: path.file_name().and_then(|name| name.to_str()).unwrap_or_default(),
1556 key,
1557 call_with,
1558 evaluated: HashMap::default(),
1559 shadowed: Vec::new(),
1560 found: Vec::new(),
1561 };
1562 finder.visit_program(&program);
1563 finder.found.into_iter().map(ConfigValue::from).collect()
1564 })
1565}
1566
1567#[must_use]
1572pub fn extract_method_calls(source: &str, path: &Path, method: &str) -> Vec<Vec<ConfigValue>> {
1573 crate::with_arena(|allocator| {
1574 let Some(program) = parse_program(allocator, source, path) else {
1575 return Vec::new();
1576 };
1577 let top = top_level(&program, &NoImports);
1578 let bindings = module_bindings(&program);
1579 let mut finder = MethodCallFinder {
1580 top: &top,
1581 bindings: &bindings,
1582 file: path.file_name().and_then(|name| name.to_str()).unwrap_or_default(),
1583 method,
1584 found: Vec::new(),
1585 };
1586 finder.visit_program(&program);
1587 finder
1588 .found
1589 .into_iter()
1590 .map(|arguments| arguments.into_iter().map(ConfigValue::from).collect())
1591 .collect()
1592 })
1593}
1594
1595fn parse_program<'a>(allocator: &'a Allocator, source: &'a str, path: &Path) -> Option<Program<'a>> {
1598 let source_type = SourceType::from_path(path).ok()?;
1599 let ret = Parser::new(allocator, source, source_type)
1600 .with_options(OxcParseOptions {
1601 preserve_parens: false,
1602 ..OxcParseOptions::default()
1603 })
1604 .parse();
1605 if ret.diagnostics.errors().next().is_some() {
1606 return None;
1607 }
1608 Some(ret.program)
1609}
1610
1611#[derive(Debug, Clone, PartialEq)]
1613pub struct ImportedCall {
1614 pub module: String,
1616 pub export: String,
1619 pub arguments: Vec<ConfigValue>,
1628 pub template: Option<String>,
1631}
1632
1633pub const TEMPLATE_SUBSTITUTION: &str = "$__skif__";
1635
1636pub const FILE_DIR: &str = "$__skif_file_dir__";
1641
1642const PROBE_DIR: &str = "__skif__/probe";
1645
1646#[must_use]
1653pub fn extract_imported_calls(
1654 source: &str,
1655 path: &Path,
1656 modules: &[&str],
1657 globals: &[(&str, &str)],
1658) -> Vec<ImportedCall> {
1659 parse_imported_calls(source, path, modules, globals, false)
1660}
1661
1662#[must_use]
1665pub fn extract_command_calls(
1666 source: &str,
1667 path: &Path,
1668 modules: &[&str],
1669 globals: &[(&str, &str)],
1670) -> Vec<ImportedCall> {
1671 parse_imported_calls(source, path, modules, globals, true)
1672}
1673
1674fn parse_imported_calls(
1675 source: &str,
1676 path: &Path,
1677 modules: &[&str],
1678 globals: &[(&str, &str)],
1679 commands: bool,
1680) -> Vec<ImportedCall> {
1681 let Ok(source_type) = SourceType::from_path(path) else {
1682 return Vec::new();
1683 };
1684 crate::with_arena(|allocator| {
1685 let ret = Parser::new(allocator, source, source_type)
1686 .with_options(OxcParseOptions {
1687 preserve_parens: false,
1688 ..OxcParseOptions::default()
1689 })
1690 .parse();
1691 if ret.diagnostics.errors().next().is_some() {
1692 return Vec::new();
1693 }
1694 let file = path.file_name().and_then(|name| name.to_str()).unwrap_or_default();
1695 imported_calls_in(&ret.program, file, modules, globals, commands)
1696 })
1697}
1698
1699pub(crate) fn imported_calls_in(
1703 program: &Program<'_>,
1704 file: &str,
1705 modules: &[&str],
1706 globals: &[(&str, &str)],
1707 commands: bool,
1708) -> Vec<ImportedCall> {
1709 let imported = imported_functions(program, modules, globals);
1710 if imported.is_empty() {
1711 return Vec::new();
1712 }
1713 let top = top_level(program, &NoImports);
1714 let bindings = module_bindings(program);
1715 let mut finder = CallFinder {
1716 top: &top,
1717 bindings: &bindings,
1718 file,
1719 imported: &imported,
1720 commands,
1721 chained_cwd: HashMap::default(),
1722 found: Vec::new(),
1723 };
1724 finder.visit_program(program);
1725 finder.found
1726}
1727
1728#[must_use]
1732pub fn literal_command(arguments: &[ConfigValue]) -> Option<String> {
1733 let is_safe = |word: &str| {
1734 !word.is_empty()
1735 && word
1736 .chars()
1737 .all(|c| c.is_alphanumeric() || matches!(c, '_' | '@' | '.' | ',' | ':' | '/' | '=' | '+' | '~' | '-'))
1738 };
1739 let file = arguments.first()?.as_str().filter(|file| is_safe(file))?;
1740 let mut words = vec![file];
1741 if let Some(ConfigValue::Array(items)) = arguments.get(1) {
1742 for word in items.iter().filter_map(ConfigValue::as_str) {
1743 if !is_safe(word) {
1744 return None;
1745 }
1746 words.push(word);
1747 }
1748 }
1749 Some(words.join(" "))
1750}
1751
1752#[must_use]
1755pub fn command_line_call(arguments: &[ConfigValue]) -> Option<String> {
1756 let command = arguments.first().and_then(ConfigValue::as_str)?;
1757 Some(command_in_call_dir(command.to_owned(), arguments.get(1)))
1758}
1759
1760#[must_use]
1763pub fn file_call_options(arguments: &[ConfigValue]) -> Option<&ConfigValue> {
1764 match arguments.get(1) {
1765 Some(ConfigValue::Array(_)) => arguments.get(2),
1766 other => other,
1767 }
1768}
1769
1770#[must_use]
1776pub fn command_in_call_dir(command: String, options: Option<&ConfigValue>) -> String {
1777 match options.and_then(|options| options.get("cwd")) {
1778 None => command,
1779 Some(ConfigValue::String(dir)) if dir.starts_with(FILE_DIR) => format!("cd {dir} && {command}"),
1780 Some(_) => format!("cd {TEMPLATE_SUBSTITUTION} && {command}"),
1781 }
1782}
1783
1784#[must_use]
1788pub fn place_file_dir(command: &str, file: &str) -> String {
1789 let dir = file.rsplit_once('/').map_or("", |(dir, _)| dir);
1790 let quoted = if dir.is_empty() {
1791 ".".to_owned()
1792 } else {
1793 format!("'{}'", dir.replace('\'', r"'\''"))
1794 };
1795 command.replace(FILE_DIR, "ed)
1796}
1797
1798fn mark_cwd<'p, 'a>(options: &mut ConfigValue, expression: &'p Expression<'a>, probe: &Evaluator<'_, 'p, 'a>) {
1803 let ConfigValue::Object(entries) = options else {
1804 return;
1805 };
1806 let Some(index) = entries.iter().rposition(|(key, _)| key == "cwd") else {
1807 return;
1808 };
1809 let probed = ConfigValue::from(probe.expr(expression, 0));
1810 let cwd = std::mem::replace(&mut entries[index].1, ConfigValue::Unknown);
1811 match marked_cwd(cwd, probed.get("cwd"), sets_undefined(expression, "cwd")) {
1812 Some(cwd) => entries[index].1 = cwd,
1813 None => entries.retain(|(key, _)| key != "cwd"),
1814 }
1815}
1816
1817const ENTRYPOINTS: &str = "entrypoints";
1820
1821fn mark_entrypoints<'p, 'a>(options: &mut ConfigValue, expression: &'p Expression<'a>, probe: &Evaluator<'_, 'p, 'a>) {
1825 let ConfigValue::Object(entries) = options else {
1826 return;
1827 };
1828 let Some(index) = entries.iter().rposition(|(key, _)| key == ENTRYPOINTS) else {
1829 return;
1830 };
1831 let ConfigValue::Array(items) = &mut entries[index].1 else {
1832 return;
1833 };
1834 let probed = ConfigValue::from(probe.expr(expression, 0));
1835 let Some(ConfigValue::Array(probed)) = probed.get(ENTRYPOINTS) else {
1836 return;
1837 };
1838 for (item, probed) in items.iter_mut().zip(probed) {
1839 let file_relative = probed != &*item;
1840 if file_relative && let ConfigValue::String(path) = item {
1841 *path = format!("{FILE_DIR}/{path}");
1842 }
1843 }
1844}
1845
1846fn marked_cwd(cwd: ConfigValue, probed: Option<&ConfigValue>, undefined: bool) -> Option<ConfigValue> {
1850 let file_relative = matches!(cwd, ConfigValue::String(_)) && probed != Some(&cwd);
1851 let unchanged = !file_relative && matches!(cwd.as_str(), Some("." | "./"));
1852 if unchanged || undefined {
1853 return None;
1854 }
1855 match cwd {
1856 ConfigValue::String(path)
1857 if file_relative
1858 && path
1859 .chars()
1860 .all(|c| c.is_alphanumeric() || matches!(c, '_' | '@' | '.' | '/' | '+' | '-')) =>
1861 {
1862 Some(ConfigValue::String(format!("{FILE_DIR}/{path}")))
1863 }
1864 other => Some(other),
1865 }
1866}
1867
1868fn is_undefined(expression: &Expression<'_>) -> bool {
1870 match expression.get_inner_expression() {
1871 Expression::Identifier(id) => id.name.as_str() == "undefined",
1872 Expression::UnaryExpression(u) => u.operator == UnaryOperator::Void,
1873 _ => false,
1874 }
1875}
1876
1877fn sets_undefined(expression: &Expression<'_>, key: &str) -> bool {
1879 let Expression::ObjectExpression(object) = expression.get_inner_expression() else {
1880 return false;
1881 };
1882 let last = object.properties.iter().rev().find_map(|property| match property {
1883 ObjectPropertyKind::ObjectProperty(p)
1884 if p.key
1885 .static_name()
1886 .is_some_and(|name| crate::js_string(name.as_js_str()) == key) =>
1887 {
1888 Some(&p.value)
1889 }
1890 _ => None,
1891 });
1892 last.is_some_and(is_undefined)
1893}
1894
1895type ImportedFunctions = HashMap<String, (String, Option<String>)>;
1898
1899type Entries<'v> = &'v [(String, Value)];
1901
1902fn imported_functions(program: &Program<'_>, modules: &[&str], globals: &[(&str, &str)]) -> ImportedFunctions {
1903 let mut imported: ImportedFunctions = globals
1904 .iter()
1905 .map(|(name, module)| match name.strip_suffix(".*") {
1906 Some(object) => (object.to_owned(), ((*module).to_owned(), None)),
1907 None => ((*name).to_owned(), ((*module).to_owned(), Some((*name).to_owned()))),
1908 })
1909 .collect();
1910 let named = |export: String| Some(export).filter(|e| e != "default");
1911 for stmt in &program.body {
1912 if let Statement::ImportDeclaration(d) = stmt {
1913 let module = d.source.value.as_str().unwrap_or_default();
1914 if !modules.contains(&module) {
1915 continue;
1916 }
1917 for specifier in d.specifiers.iter().flatten() {
1918 let (local, export) = match specifier {
1919 ImportDeclarationSpecifier::ImportSpecifier(s) => (
1920 s.local.name.as_str(),
1921 named(crate::collect::module_export_name(&s.imported)),
1922 ),
1923 ImportDeclarationSpecifier::ImportDefaultSpecifier(s) => (s.local.name.as_str(), None),
1924 ImportDeclarationSpecifier::ImportNamespaceSpecifier(s) => (s.local.name.as_str(), None),
1925 };
1926 imported.insert(local.to_owned(), (module.to_owned(), export));
1927 }
1928 } else if let Some(Declaration::VariableDeclaration(v)) = stmt.as_declaration() {
1929 for declarator in &v.declarations {
1930 if let Some(module) = declarator.init.as_ref().and_then(required_module)
1931 && modules.contains(&module.as_str())
1932 {
1933 for (local, export) in required_names(&declarator.id) {
1934 imported.insert(local, (module.clone(), export.and_then(named)));
1935 }
1936 }
1937 }
1938 }
1939 }
1940 imported
1941}
1942
1943fn required_names(id: &BindingPattern<'_>) -> Vec<(String, Option<String>)> {
1945 match id {
1946 BindingPattern::BindingIdentifier(local) => vec![(local.name.as_str().to_owned(), None)],
1947 BindingPattern::ObjectPattern(pattern) => pattern
1948 .properties
1949 .iter()
1950 .filter_map(|property| match (property.key.static_name(), &property.value) {
1951 (Some(key), BindingPattern::BindingIdentifier(local)) => {
1952 Some((local.name.as_str().to_owned(), Some(crate::js_string(key.as_js_str()))))
1953 }
1954 _ => None,
1955 })
1956 .collect(),
1957 _ => Vec::new(),
1958 }
1959}
1960
1961struct CallFinder<'t, 'p, 'a> {
1962 top: &'t TopLevel<'p, 'a>,
1963 bindings: &'t ModuleBindings,
1964 file: &'t str,
1965 imported: &'t ImportedFunctions,
1967 commands: bool,
1969 chained_cwd: HashMap<Span, Option<ConfigValue>>,
1972 found: Vec<ImportedCall>,
1973}
1974
1975fn template_tag<'e, 'a>(template: &'e TaggedTemplateExpression<'a>) -> &'e Expression<'a> {
1977 match template.tag.get_inner_expression() {
1978 Expression::CallExpression(call) => &call.callee,
1979 other => other,
1980 }
1981}
1982
1983impl<'a> CallFinder<'_, '_, 'a> {
1984 fn target(&self, callee: &Expression<'a>) -> Option<(String, String)> {
1986 match callee.get_inner_expression() {
1987 Expression::Identifier(id) => self
1988 .imported
1989 .get(id.name.as_str())
1990 .map(|(module, export)| (module.clone(), export.clone().unwrap_or_else(|| "default".to_owned()))),
1991 Expression::StaticMemberExpression(member) => match member.object.get_inner_expression() {
1992 Expression::Identifier(id) => self
1993 .imported
1994 .get(id.name.as_str())
1995 .filter(|(_, export)| export.is_none())
1996 .map(|(module, _)| (module.clone(), member.property.name.as_str().to_owned())),
1997 _ => None,
1998 },
1999 _ => None,
2000 }
2001 }
2002
2003 fn chained_template<'e>(&self, mut object: &'e Expression<'a>) -> Option<&'e TaggedTemplateExpression<'a>> {
2006 loop {
2007 match object.get_inner_expression() {
2008 Expression::TaggedTemplateExpression(template) => {
2009 return self.target(template_tag(template)).is_some().then_some(&**template);
2010 }
2011 Expression::CallExpression(call) => match call.callee.get_inner_expression() {
2012 Expression::StaticMemberExpression(member) => object = &member.object,
2013 _ => return None,
2014 },
2015 _ => return None,
2016 }
2017 }
2018 }
2019
2020 fn record_chained_cwd(&mut self, it: &CallExpression<'a>) {
2023 let Expression::StaticMemberExpression(member) = it.callee.get_inner_expression() else {
2024 return;
2025 };
2026 if member.property.name.as_str() != "cwd" {
2027 return;
2028 }
2029 let Some(template) = self.chained_template(&member.object) else {
2030 return;
2031 };
2032 let Some(directory) = it.arguments.first().and_then(Argument::as_expression) else {
2033 return;
2034 };
2035 let eval = Evaluator::in_file(self.top, self.bindings, self.file, HashMap::default());
2036 let probe = Evaluator {
2037 dir: PROBE_DIR,
2038 ..Evaluator::in_file(self.top, self.bindings, self.file, HashMap::default())
2039 };
2040 let probed = ConfigValue::from(probe.expr(directory, 0));
2041 let cwd = marked_cwd(
2042 ConfigValue::from(eval.expr(directory, 0)),
2043 Some(&probed),
2044 is_undefined(directory),
2045 );
2046 self.chained_cwd.entry(template.span).or_insert(cwd);
2047 }
2048}
2049
2050impl<'a> Visit<'a> for CallFinder<'_, '_, 'a> {
2051 fn visit_call_expression(&mut self, it: &CallExpression<'a>) {
2052 if self.commands {
2053 self.record_chained_cwd(it);
2054 }
2055 if let Some((module, export)) = self.target(&it.callee) {
2056 let eval = Evaluator::in_file(self.top, self.bindings, self.file, HashMap::default());
2057 let mut arguments: Vec<ConfigValue> = it
2058 .arguments
2059 .iter()
2060 .map(|argument| {
2061 argument
2062 .as_expression()
2063 .map_or(ConfigValue::Unknown, |e| ConfigValue::from(eval.expr(e, 0)))
2064 })
2065 .collect();
2066 if self.commands
2067 && arguments
2068 .iter()
2069 .any(|argument| argument.get("cwd").is_some() || argument.get(ENTRYPOINTS).is_some())
2070 {
2071 let probe = Evaluator {
2072 dir: PROBE_DIR,
2073 ..Evaluator::in_file(self.top, self.bindings, self.file, HashMap::default())
2074 };
2075 for (argument, expression) in arguments.iter_mut().zip(&it.arguments) {
2076 if let Some(expression) = expression.as_expression() {
2077 mark_entrypoints(argument, expression, &probe);
2078 mark_cwd(argument, expression, &probe);
2079 }
2080 }
2081 }
2082 self.found.push(ImportedCall {
2083 module,
2084 export,
2085 arguments,
2086 template: None,
2087 });
2088 }
2089 walk::walk_call_expression(self, it);
2090 }
2091
2092 fn visit_tagged_template_expression(&mut self, it: &TaggedTemplateExpression<'a>) {
2093 if let Some((module, export)) = self.target(template_tag(it)) {
2094 let quasis: Vec<&str> = it.quasi.quasis.iter().map(|q| q.value.raw.as_str()).collect();
2095 let arguments = self
2096 .chained_cwd
2097 .remove(&it.span)
2098 .flatten()
2099 .map_or_default(|cwd| vec![ConfigValue::Object(vec![("cwd".to_owned(), cwd)])]);
2100 self.found.push(ImportedCall {
2101 module,
2102 export,
2103 arguments,
2104 template: Some(quasis.join(TEMPLATE_SUBSTITUTION)),
2105 });
2106 }
2107 walk::walk_tagged_template_expression(self, it);
2108 }
2109}
2110
2111struct OptionFinder<'t, 'p, 'a> {
2112 top: &'t TopLevel<'p, 'a>,
2113 bindings: &'t ModuleBindings,
2114 file: &'t str,
2115 constructor: &'t str,
2116 key: &'t str,
2117 found: Vec<Value>,
2118}
2119
2120impl<'a> Visit<'a> for OptionFinder<'_, '_, 'a> {
2121 fn visit_new_expression(&mut self, it: &NewExpression<'a>) {
2122 let name = match it.callee.get_inner_expression() {
2123 Expression::Identifier(id) => Some(id.name.as_str()),
2124 Expression::StaticMemberExpression(member) => Some(member.property.name.as_str()),
2125 _ => None,
2126 };
2127 if name == Some(self.constructor) {
2128 let eval = Evaluator::in_file(self.top, self.bindings, self.file, HashMap::default());
2129 for argument in &it.arguments {
2130 let Some(Expression::ObjectExpression(options)) =
2131 argument.as_expression().map(Expression::get_inner_expression)
2132 else {
2133 continue;
2134 };
2135 for property in &options.properties {
2136 if let ObjectPropertyKind::ObjectProperty(p) = property
2137 && !p.method
2138 && p.key
2139 .static_name()
2140 .is_some_and(|name| crate::js_string(name.as_js_str()) == self.key)
2141 {
2142 self.found.push(eval.expr(&p.value, 0));
2143 }
2144 }
2145 }
2146 }
2147 walk::walk_new_expression(self, it);
2148 }
2149}
2150
2151struct MethodCallFinder<'t, 'p, 'a> {
2152 top: &'t TopLevel<'p, 'a>,
2153 bindings: &'t ModuleBindings,
2154 file: &'t str,
2155 method: &'t str,
2156 found: Vec<Vec<Value>>,
2157}
2158
2159impl<'a> Visit<'a> for MethodCallFinder<'_, '_, 'a> {
2160 fn visit_call_expression(&mut self, it: &CallExpression<'a>) {
2161 if let Expression::StaticMemberExpression(member) = it.callee.get_inner_expression()
2162 && member.property.name.as_str() == self.method
2163 {
2164 let eval = Evaluator::in_file(self.top, self.bindings, self.file, HashMap::default());
2165 let arguments = it
2166 .arguments
2167 .iter()
2168 .map(|argument| argument.as_expression().map_or(Value::Unknown, |e| eval.expr(e, 0)))
2169 .collect();
2170 self.found.push(arguments);
2171 }
2172 walk::walk_call_expression(self, it);
2173 }
2174}
2175
2176struct PropertyFinder<'t, 'p, 'a> {
2177 top: &'t TopLevel<'p, 'a>,
2178 bindings: &'t ModuleBindings,
2179 file: &'t str,
2180 key: &'t str,
2181 call_with: Option<&'t ConfigValue>,
2184 evaluated: HashMap<String, Value>,
2186 shadowed: Vec<Vec<String>>,
2188 found: Vec<Value>,
2189}
2190
2191impl PropertyFinder<'_, '_, '_> {
2192 fn in_scope(&mut self, declared: DeclaredNames, walk: impl FnOnce(&mut Self)) {
2194 self.shadowed.push(declared.0);
2195 walk(self);
2196 self.shadowed.pop();
2197 }
2198}
2199
2200#[derive(Default)]
2202struct DeclaredNames(Vec<String>);
2203
2204impl<'a> Visit<'a> for DeclaredNames {
2205 fn visit_binding_identifier(&mut self, it: &BindingIdentifier<'a>) {
2206 self.0.push(it.name.as_str().to_owned());
2207 }
2208}
2209
2210fn declared(visit: impl FnOnce(&mut DeclaredNames)) -> DeclaredNames {
2212 let mut names = DeclaredNames::default();
2213 visit(&mut names);
2214 names
2215}
2216
2217impl<'a> Visit<'a> for PropertyFinder<'_, '_, 'a> {
2218 fn visit_function(&mut self, it: &Function<'a>, flags: oxc_semantic::ScopeFlags) {
2219 let names = declared(|names| names.visit_function(it, flags));
2220 self.in_scope(names, |finder| walk::walk_function(finder, it, flags));
2221 }
2222
2223 fn visit_arrow_function_expression(&mut self, it: &ArrowFunctionExpression<'a>) {
2224 let names = declared(|names| names.visit_arrow_function_expression(it));
2225 self.in_scope(names, |finder| walk::walk_arrow_function_expression(finder, it));
2226 }
2227
2228 fn visit_block_statement(&mut self, it: &BlockStatement<'a>) {
2229 let names = declared(|names| names.visit_block_statement(it));
2230 self.in_scope(names, |finder| walk::walk_block_statement(finder, it));
2231 }
2232
2233 fn visit_for_statement(&mut self, it: &ForStatement<'a>) {
2234 let names = declared(|names| names.visit_for_statement(it));
2235 self.in_scope(names, |finder| walk::walk_for_statement(finder, it));
2236 }
2237
2238 fn visit_for_in_statement(&mut self, it: &ForInStatement<'a>) {
2239 let names = declared(|names| names.visit_for_in_statement(it));
2240 self.in_scope(names, |finder| walk::walk_for_in_statement(finder, it));
2241 }
2242
2243 fn visit_for_of_statement(&mut self, it: &ForOfStatement<'a>) {
2244 let names = declared(|names| names.visit_for_of_statement(it));
2245 self.in_scope(names, |finder| walk::walk_for_of_statement(finder, it));
2246 }
2247
2248 fn visit_catch_clause(&mut self, it: &CatchClause<'a>) {
2249 let names = declared(|names| names.visit_catch_clause(it));
2250 self.in_scope(names, |finder| walk::walk_catch_clause(finder, it));
2251 }
2252
2253 fn visit_object_property(&mut self, it: &ObjectProperty<'a>) {
2254 let callable = self.call_with.and_then(|_| Callable::of_expression(&it.value));
2255 let wanted = match self.call_with {
2256 None => !it.method,
2257 Some(_) => callable.is_some() && it.kind == oxc_ast::ast::PropertyKind::Init,
2258 };
2259 if wanted
2260 && it
2261 .key
2262 .static_name()
2263 .is_some_and(|name| crate::js_string(name.as_js_str()) == self.key)
2264 {
2265 let eval = Evaluator::in_file(self.top, self.bindings, self.file, std::mem::take(&mut self.evaluated));
2266 let shadowed: Frame<'_, '_> = self
2267 .shadowed
2268 .iter()
2269 .flatten()
2270 .map(|name| (name.clone(), Binding::Value(Value::Unknown)))
2271 .collect();
2272 if !shadowed.is_empty() {
2273 eval.frames.borrow_mut().push(shadowed);
2274 }
2275 let value = match (self.call_with, callable) {
2276 (Some(argument), Some(callable)) => eval.call_with(callable, Value::from(argument.clone()), 0),
2277 _ => eval.expr(&it.value, 0),
2278 };
2279 self.found.push(value);
2280 self.evaluated = eval.evaluated.into_inner();
2281 }
2282 walk::walk_object_property(self, it);
2283 }
2284}
2285
2286#[derive(Clone, Copy)]
2288enum Exported<'p, 'a> {
2289 Expression(&'p Expression<'a>),
2290 Function(&'p Function<'a>),
2291}
2292
2293fn exported<'p, 'a>(program: &'p Program<'a>) -> Option<Exported<'p, 'a>> {
2294 for stmt in &program.body {
2295 match stmt {
2296 Statement::ExportDefaultDeclaration(e) => {
2297 return match &e.declaration {
2298 ExportDefaultDeclarationKind::FunctionDeclaration(f) => Some(Exported::Function(f)),
2299 ExportDefaultDeclarationKind::ClassDeclaration(_)
2300 | ExportDefaultDeclarationKind::TSInterfaceDeclaration(_) => None,
2301 other => other.as_expression().map(Exported::Expression),
2302 };
2303 }
2304 Statement::TSExportAssignment(e) => return Some(Exported::Expression(&e.expression)),
2305 Statement::ExpressionStatement(s) if is_module_exports_assignment(stmt) => {
2306 if let Expression::AssignmentExpression(a) = &s.expression {
2307 return Some(Exported::Expression(&a.right));
2308 }
2309 }
2310 _ => {}
2311 }
2312 }
2313 None
2314}
2315
2316fn exports_changed(program: &Program<'_>) -> bool {
2319 let mut statements = program
2320 .body
2321 .iter()
2322 .skip_while(|stmt| !is_module_exports_assignment(stmt));
2323 let unstable = Unstable::default();
2325 statements.next().is_some()
2326 && statements.any(|stmt| {
2327 let writes = Writes::of_statement(stmt, &unstable);
2328 writes
2329 .assigned
2330 .iter()
2331 .chain(writes.changed.iter().map(|(name, _)| name))
2332 .any(|name| name == "module" || name == "exports")
2333 })
2334}
2335
2336fn is_module_exports_assignment(stmt: &Statement<'_>) -> bool {
2337 if let Statement::ExpressionStatement(s) = stmt
2338 && let Expression::AssignmentExpression(a) = &s.expression
2339 && let AssignmentTarget::StaticMemberExpression(m) = &a.left
2340 {
2341 matches!(&m.object, Expression::Identifier(id) if id.name.as_str() == "module")
2342 && m.property.name.as_str() == "exports"
2343 } else {
2344 false
2345 }
2346}
2347
2348fn node_count(value: &Value) -> usize {
2350 1 + match value {
2351 Value::Array(items) | Value::OneOf(items) => items.iter().map(node_count).sum(),
2352 Value::Object(entries) => entries.iter().map(|(_, item)| node_count(item)).sum(),
2353 Value::Maybe(value) => node_count(value),
2354 _ => 0,
2355 }
2356}
2357
2358struct Evaluator<'c, 'p, 'a> {
2359 consts: &'c HashMap<String, &'p Expression<'a>>,
2360 changed: &'c HashMap<String, TopChange>,
2362 unstable: &'c Unstable,
2364 functions: &'c HashMap<String, Callable<'p, 'a>>,
2365 bindings: &'c ModuleBindings,
2366 file: &'c str,
2368 dir: &'c str,
2370 path: &'c Path,
2372 imports: &'c HashMap<String, ImportBinding>,
2374 modules: Option<&'c Modules<'c>>,
2376 evaluated: RefCell<HashMap<String, Value>>,
2378 chain: RefCell<Vec<String>>,
2380 frames: RefCell<Vec<Frame<'p, 'a>>>,
2382 calls: Cell<usize>,
2384 config_setter: RefCell<Option<String>>,
2387 captured: RefCell<Option<Value>>,
2389 arguments_of: RefCell<HashMap<Span, Vec<Value>>>,
2391}
2392
2393static NO_IMPORTS: LazyLock<HashMap<String, ImportBinding>> = LazyLock::new(HashMap::default);
2395
2396impl<'c, 'p, 'a> Evaluator<'c, 'p, 'a> {
2397 fn new(
2400 top: &'c TopLevel<'p, 'a>,
2401 bindings: &'c ModuleBindings,
2402 imports: &'c HashMap<String, ImportBinding>,
2403 file: &'c str,
2404 dir: &'c str,
2405 path: &'c Path,
2406 modules: Option<&'c Modules<'c>>,
2407 ) -> Self {
2408 Self {
2409 consts: &top.consts,
2410 changed: &top.changed,
2411 unstable: &top.unstable,
2412 functions: &top.functions,
2413 bindings,
2414 file,
2415 dir,
2416 path,
2417 imports,
2418 modules,
2419 evaluated: RefCell::new(HashMap::default()),
2420 chain: RefCell::new(Vec::new()),
2421 frames: RefCell::new(Vec::new()),
2422 calls: Cell::new(0),
2423 config_setter: RefCell::new(None),
2424 captured: RefCell::new(None),
2425 arguments_of: RefCell::new(HashMap::default()),
2426 }
2427 }
2428
2429 fn in_file(
2432 top: &'c TopLevel<'p, 'a>,
2433 bindings: &'c ModuleBindings,
2434 file: &'c str,
2435 evaluated: HashMap<String, Value>,
2436 ) -> Self {
2437 let eval = Self::new(top, bindings, &NO_IMPORTS, file, ".", Path::new(file), None);
2438 *eval.evaluated.borrow_mut() = evaluated;
2439 eval
2440 }
2441}
2442
2443impl<'p, 'a> Evaluator<'_, 'p, 'a> {
2444 fn exported_value(&self, exported: Exported<'p, 'a>, changed_later: bool) -> Value {
2447 let callable = match exported {
2448 Exported::Expression(expr) => Callable::of_expression(expr),
2449 Exported::Function(function) => Some(Callable::Function(function)),
2450 };
2451 *self.config_setter.borrow_mut() = callable.and_then(first_parameter);
2452 let value = match exported {
2453 Exported::Expression(expr) => self.expr(expr, 0),
2454 Exported::Function(function) => self.invoke(Callable::Function(function), None, false, 0),
2455 };
2456 let value = if changed_later { changed(value) } else { value };
2457 self.captured.borrow_mut().take().unwrap_or(value)
2458 }
2459
2460 fn exported_call_arguments(&self, exported: Exported<'p, 'a>, value: &Value) -> Vec<Value> {
2463 match exported {
2464 Exported::Expression(expr) => self.unknown_call_arguments(expr, value, 0),
2465 Exported::Function(function) => self.returned_call_arguments(Callable::Function(function), value, 0),
2466 }
2467 }
2468
2469 fn expr(&self, expr: &'p Expression<'a>, depth: u32) -> Value {
2470 if depth > MAX_DEPTH {
2471 return Value::Unknown;
2472 }
2473 let depth = depth + 1;
2474 let inner = expr.get_inner_expression();
2475 match inner {
2476 Expression::NullLiteral(_) => Value::Null,
2477 Expression::BooleanLiteral(b) => Value::Bool(b.value),
2478 Expression::NumericLiteral(n) => Value::Number(n.value),
2479 Expression::StringLiteral(_) => static_string(inner).map_or(Value::Unknown, Value::String),
2480 Expression::RegExpLiteral(r) => Value::Regex {
2481 pattern: r.regex.pattern.text.as_str().to_owned(),
2482 flags: r.regex.flags.to_string(),
2483 },
2484 Expression::TemplateLiteral(template) => self.template(template, depth),
2485 Expression::BinaryExpression(b) => self.binary(b, depth),
2486 Expression::StaticMemberExpression(member) => self.member(member, depth),
2487 Expression::ComputedMemberExpression(member) => self.computed_member(member, depth),
2488 Expression::ChainExpression(chain) => self.chain(chain, depth),
2489 Expression::NewExpression(new) => self.url(new, depth),
2490 Expression::UnaryExpression(u) if u.operator == UnaryOperator::UnaryNegation => {
2491 match self.expr(&u.argument, depth) {
2492 Value::Number(n) => Value::Number(-n),
2493 _ => Value::Unknown,
2494 }
2495 }
2496 Expression::UnaryExpression(u) if u.operator == UnaryOperator::LogicalNot => self
2497 .truthy(&u.argument, depth)
2498 .map_or(Value::Unknown, |t| Value::Bool(!t)),
2499 Expression::LogicalExpression(l) => self.logical(l, depth),
2500 Expression::ConditionalExpression(c) => self.conditional(c, depth),
2501 Expression::AwaitExpression(a) => self.expr(&a.argument, depth),
2502 Expression::ArrayExpression(a) => self.array(&a.elements, depth),
2503 Expression::ObjectExpression(o) => self.object(&o.properties, depth),
2504 Expression::Identifier(id) => self.reference(id, depth),
2505 Expression::CallExpression(c) => self.call(c, depth),
2506 Expression::ArrowFunctionExpression(f) => self.invoke(Callable::Arrow(f), None, false, depth),
2507 Expression::FunctionExpression(f) => self.invoke(Callable::Function(f), None, false, depth),
2508 _ => Value::Unknown,
2509 }
2510 }
2511
2512 fn reference(&self, id: &IdentifierReference<'a>, depth: u32) -> Value {
2515 let value = self.identifier(id.name.as_str(), depth);
2516 let Some(change) = self.unstable.of_reference(id) else {
2517 return value;
2518 };
2519 let (value, definite) = value.split();
2520 Value::doubtful(unstable(value, change), definite)
2521 }
2522
2523 fn identifier(&self, name: &str, depth: u32) -> Value {
2528 if let Some(binding) = self.lookup(name) {
2529 return match binding {
2530 Binding::Value(value) | Binding::Alias(Alias { value, .. }) => value,
2531 Binding::Callable(_) => Value::Unknown,
2532 };
2533 }
2534 let Some(&init) = self.consts.get(name) else {
2535 return match self.functions.get(name) {
2536 Some(callable) => self.invoke(*callable, None, true, depth),
2537 None => match self.import_value(name, None) {
2538 Some(value) => {
2539 let (value, definite) = value.split();
2540 Value::doubtful(self.changed_top(name, value), definite)
2541 }
2542 None => self.global(name),
2543 },
2544 };
2545 };
2546 if let Some(known) = self.evaluated.borrow().get(name) {
2547 return known.clone();
2548 }
2549 {
2550 let chain = self.chain.borrow();
2551 if chain.len() >= MAX_CONST_CHAIN || chain.iter().any(|active| active == name) {
2552 return Value::Unknown;
2553 }
2554 }
2555 self.chain.borrow_mut().push(name.to_owned());
2556 let saved = std::mem::take(&mut *self.frames.borrow_mut());
2559 let cuts = self.modules.map(|modules| modules.cuts.get());
2560 let (value, definite) = self.expr(init, 0).split();
2561 *self.frames.borrow_mut() = saved;
2562 self.chain.borrow_mut().pop();
2563 let value = if node_count(&value) > MAX_CONST_NODES {
2564 Value::Unknown
2565 } else {
2566 self.changed_top(name, value)
2567 };
2568 let value = Value::doubtful(value, definite);
2569 if self.modules.map(|modules| modules.cuts.get()) == cuts {
2571 self.evaluated.borrow_mut().insert(name.to_owned(), value.clone());
2572 }
2573 value
2574 }
2575
2576 fn changed_top(&self, name: &str, value: Value) -> Value {
2579 match self.changed.get(name) {
2580 Some(TopChange::Assigned) => changed(value),
2581 written => {
2582 let mut value = value;
2583 if let Some(TopChange::Written(writes)) = written {
2584 for write in writes {
2585 edit_at(&mut value, &write.keys, &write.edit());
2586 }
2587 }
2588 match self.unstable.of_top(name) {
2589 Some(change) => unstable(value, change),
2590 None => value,
2591 }
2592 }
2593 }
2594 }
2595
2596 fn lookup(&self, name: &str) -> Option<Binding<'p, 'a>> {
2598 self.frames
2599 .borrow()
2600 .iter()
2601 .rev()
2602 .find_map(|frame| frame.get(name).cloned())
2603 }
2604
2605 fn is_local(&self, name: &str) -> bool {
2607 self.frames.borrow().iter().any(|frame| frame.contains_key(name))
2608 }
2609
2610 fn assign(&self, name: &str, binding: Binding<'p, 'a>) {
2612 let mut frames = self.frames.borrow_mut();
2613 if let Some(held) = frames.iter_mut().rev().find_map(|frame| frame.get_mut(name)) {
2614 *held = binding;
2615 } else if let Some(frame) = frames.last_mut() {
2616 frame.insert(name.to_owned(), binding);
2617 }
2618 }
2619
2620 fn global(&self, name: &str) -> Value {
2622 match name {
2623 "__dirname" => Value::String(normalized(&[self.dir])),
2624 "__filename" => Value::String(self.file_path()),
2625 "undefined" => Value::Undefined,
2626 _ => Value::Unknown,
2627 }
2628 }
2629
2630 fn file_path(&self) -> String {
2632 normalized(&[self.dir, self.file])
2633 }
2634
2635 fn import_value(&self, name: &str, args: Option<&[Value]>) -> Option<Value> {
2638 let binding = self.imports.get(name)?;
2639 self.modules?
2640 .evaluate(self.path, &binding.specifier, &binding.imported, args)
2641 }
2642
2643 fn spread_key(&self, expr: &Expression<'_>) -> &'static str {
2646 if self.computes_itself(expr, 0) {
2647 OWN_SPREAD
2648 } else {
2649 UNKNOWN_SPREAD
2650 }
2651 }
2652
2653 fn computes_itself(&self, expr: &Expression<'_>, depth: usize) -> bool {
2658 if depth > MAX_CONST_CHAIN {
2659 return false;
2660 }
2661 let depth = depth + 1;
2662 match expr.get_inner_expression() {
2663 Expression::AwaitExpression(e) => self.computes_itself(&e.argument, depth),
2664 Expression::StaticMemberExpression(member) => self.computes_itself(&member.object, depth),
2665 Expression::ComputedMemberExpression(member) => self.computes_itself(&member.object, depth),
2666 Expression::ChainExpression(chain) => {
2667 chain_object(chain).is_some_and(|object| self.computes_itself(object, depth))
2668 }
2669 Expression::CallExpression(call) => self.computes_itself(&call.callee, depth),
2670 Expression::ConditionalExpression(conditional) => {
2671 self.computes_itself(&conditional.consequent, depth)
2672 || self.computes_itself(&conditional.alternate, depth)
2673 }
2674 Expression::LogicalExpression(logical) => {
2675 self.computes_itself(&logical.left, depth) || self.computes_itself(&logical.right, depth)
2676 }
2677 Expression::Identifier(id) => {
2678 let name = id.name.as_str();
2679 self.is_local(name)
2680 || self.functions.contains_key(name)
2681 || self
2682 .consts
2683 .get(name)
2684 .is_some_and(|init| !self.takes_imported(init, depth))
2685 }
2686 _ => false,
2687 }
2688 }
2689
2690 fn takes_imported(&self, expr: &Expression<'_>, depth: usize) -> bool {
2693 if depth > MAX_CONST_CHAIN {
2694 return false;
2695 }
2696 let depth = depth + 1;
2697 match expr.get_inner_expression() {
2698 Expression::AwaitExpression(e) => self.takes_imported(&e.argument, depth),
2699 Expression::StaticMemberExpression(member) => self.takes_imported(&member.object, depth),
2700 Expression::ComputedMemberExpression(member) => self.takes_imported(&member.object, depth),
2701 Expression::ChainExpression(chain) => {
2702 chain_object(chain).is_some_and(|object| self.takes_imported(object, depth))
2703 }
2704 Expression::CallExpression(call) => {
2705 let required = matches!(
2706 call.callee.get_inner_expression(),
2707 Expression::Identifier(id) if id.name.as_str() == "require"
2708 );
2709 required || self.takes_imported(&call.callee, depth)
2710 }
2711 Expression::Identifier(id) => {
2712 let name = id.name.as_str();
2713 self.imports.contains_key(name)
2714 || (!self.functions.contains_key(name)
2715 && self
2716 .consts
2717 .get(name)
2718 .is_some_and(|init| self.takes_imported(init, depth)))
2719 }
2720 _ => false,
2721 }
2722 }
2723
2724 fn imported_call(&self, name: &str, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
2726 let binding = self.imports.get(name)?;
2727 let modules = self.modules?;
2728 if !modules.follows(self.path, &binding.specifier) {
2729 return None;
2730 }
2731 let args = self.arguments(call, depth);
2732 modules.evaluate(self.path, &binding.specifier, &binding.imported, Some(&args))
2733 }
2734
2735 fn namespace_member(&self, namespace: &str, name: &str) -> Value {
2737 let (Some(binding), Some(modules)) = (self.imports.get(namespace), self.modules) else {
2738 return Value::Unknown;
2739 };
2740 modules
2741 .evaluate(self.path, &binding.specifier, &Imported::named(name.to_owned()), None)
2742 .unwrap_or(Value::Unknown)
2743 }
2744
2745 fn call_named(&self, name: &str, args: &[Value]) -> Value {
2747 match self.functions.get(name) {
2748 Some(callable) => self.invoke(*callable, Some(args), true, 0),
2749 None => self.import_value(name, Some(args)).unwrap_or(Value::Unknown),
2750 }
2751 }
2752
2753 fn required_value(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
2756 let modules = self.modules?;
2757 if !matches!(call.callee.get_inner_expression(), Expression::Identifier(id) if id.name.as_str() == "require") {
2758 return None;
2759 }
2760 let specifier = single_argument(call).and_then(static_string)?;
2761 if depth >= 2 && !specifier.starts_with('.') {
2762 return None;
2763 }
2764 modules.evaluate(self.path, &specifier, &Imported::Default, None)
2765 }
2766
2767 fn binary(&self, binary: &'p BinaryExpression<'a>, depth: u32) -> Value {
2770 let equal = match binary.operator {
2771 BinaryOperator::Addition => None,
2772 BinaryOperator::StrictEquality => Some(true),
2773 BinaryOperator::StrictInequality => Some(false),
2774 _ => return Value::Unknown,
2775 };
2776 let (left, right) = (self.expr(&binary.left, depth), self.expr(&binary.right, depth));
2777 match equal {
2778 None => each_combination(&[left, right], |pair| match pair {
2779 [left, right] => add(left, right),
2780 _ => Value::Unknown,
2781 }),
2782 Some(equal) => strictly_equal(&left, &right).map_or(Value::Unknown, |same| Value::Bool(same == equal)),
2783 }
2784 }
2785
2786 fn template(&self, template: &'p TemplateLiteral<'a>, depth: u32) -> Value {
2789 let mut substitutions = Vec::with_capacity(template.expressions.len());
2790 for expression in &template.expressions {
2791 let value = self.expr(expression, depth);
2792 if !matches!(value, Value::OneOf(_)) && primitive_text(&value).is_none() {
2793 return Value::Unknown;
2794 }
2795 substitutions.push(value);
2796 }
2797 each_combination(&substitutions, |texts| {
2798 let mut out = String::new();
2799 for (index, quasi) in template.quasis.iter().enumerate() {
2800 out.push_str(
2801 &quasi
2802 .value
2803 .cooked
2804 .map_or_else(|| quasi.value.raw.as_str().to_owned(), crate::js_string),
2805 );
2806 if let Some(value) = texts.get(index) {
2807 let Some(text) = primitive_text(value) else {
2808 return Value::Unknown;
2809 };
2810 out.push_str(&text);
2811 }
2812 }
2813 Value::String(out)
2814 })
2815 }
2816
2817 fn read(&self, expr: &'p Expression<'a>, depth: u32) -> (Value, bool) {
2819 self.expr(expr, depth).split()
2820 }
2821
2822 fn truthy(&self, expr: &'p Expression<'a>, depth: u32) -> Option<bool> {
2824 match self.read(expr, depth) {
2825 (value, true) => truthiness(&value),
2826 (_, false) => None,
2827 }
2828 }
2829
2830 fn member(&self, member: &'p StaticMemberExpression<'a>, depth: u32) -> Value {
2835 self.member_of(&member.object, member.property.name.as_str(), depth)
2836 }
2837
2838 fn computed_member(&self, member: &'p ComputedMemberExpression<'a>, depth: u32) -> Value {
2841 self.member_key(&member.expression, depth)
2842 .map_or(Value::Unknown, |key| self.member_of(&member.object, &key, depth))
2843 }
2844
2845 fn member_of(&self, object: &'p Expression<'a>, property: &str, depth: u32) -> Value {
2847 self.special_member(object, property, depth)
2848 .unwrap_or_else(|| property_of(&self.expr(object, depth), property))
2849 }
2850
2851 fn special_member(&self, object: &'p Expression<'a>, property: &str, depth: u32) -> Option<Value> {
2854 Some(match (object.get_inner_expression(), property) {
2855 (Expression::ImportMeta(_), "dirname" | "dir") => Value::String(normalized(&[self.dir])),
2856 (Expression::ImportMeta(_), "filename" | "url" | "path") => Value::String(self.file_path()),
2857 (Expression::ImportMeta(_), "file") => Value::String(self.file.to_owned()),
2858 (url @ Expression::NewExpression(_), "pathname") => self.expr(url, depth),
2859 (Expression::Identifier(namespace), _) if self.is_namespace(namespace.name.as_str()) => {
2860 let name = namespace.name.as_str();
2863 let exports = Value::Object(vec![(property.to_owned(), self.namespace_member(name, property))]);
2864 let exports = self.changed_top(name, exports);
2865 let exports = match self.unstable.of_reference(namespace) {
2866 Some(change) => unstable(exports, change),
2867 None => exports,
2868 };
2869 property_of(&exports, property)
2870 }
2871 _ => return None,
2872 })
2873 }
2874
2875 fn member_key(&self, key: &'p Expression<'a>, depth: u32) -> Option<String> {
2878 match self.expr(key, depth) {
2879 Value::String(key) => Some(key),
2880 Value::Number(number) => number_text(number),
2881 _ => None,
2882 }
2883 }
2884
2885 fn chain(&self, chain: &'p ChainExpression<'a>, depth: u32) -> Value {
2889 let link = match &chain.expression {
2890 ChainElement::StaticMemberExpression(m) => {
2891 self.chain_member(&m.object, Some(m.property.name.as_str().to_owned()), m.optional, depth)
2892 }
2893 ChainElement::ComputedMemberExpression(m) => {
2894 let key = self.member_key(&m.expression, depth);
2895 self.chain_member(&m.object, key, m.optional, depth)
2896 }
2897 ChainElement::CallExpression(call) => Some(self.call(call, depth)),
2898 ChainElement::TSNonNullExpression(e) => self.chain_link(&e.expression, depth),
2899 ChainElement::PrivateFieldExpression(_) => Some(Value::Unknown),
2900 };
2901 link.unwrap_or(Value::Undefined)
2902 }
2903
2904 fn chain_link(&self, expr: &'p Expression<'a>, depth: u32) -> Option<Value> {
2906 match expr.get_inner_expression() {
2907 Expression::StaticMemberExpression(m) => {
2908 self.chain_member(&m.object, Some(m.property.name.as_str().to_owned()), m.optional, depth)
2909 }
2910 Expression::ComputedMemberExpression(m) => {
2911 let key = self.member_key(&m.expression, depth);
2912 self.chain_member(&m.object, key, m.optional, depth)
2913 }
2914 _ => Some(self.expr(expr, depth)),
2915 }
2916 }
2917
2918 fn chain_member(
2921 &self,
2922 object: &'p Expression<'a>,
2923 key: Option<String>,
2924 optional: bool,
2925 depth: u32,
2926 ) -> Option<Value> {
2927 if let Some(special) = key.as_deref().and_then(|key| self.special_member(object, key, depth)) {
2928 return Some(special);
2929 }
2930 let base = self.chain_link(object, depth)?;
2931 if optional && matches!(base, Value::Null | Value::Undefined) {
2932 return None;
2933 }
2934 Some(key.map_or(Value::Unknown, |key| property_of(&base, &key)))
2935 }
2936
2937 fn is_namespace(&self, name: &str) -> bool {
2939 self.imports
2940 .get(name)
2941 .is_some_and(|binding| binding.imported == Imported::Namespace)
2942 && !self.is_local(name)
2943 }
2944
2945 fn logical(&self, logical: &'p LogicalExpression<'a>, depth: u32) -> Value {
2949 let (left, definite) = self.read(&logical.left, depth);
2950 if !definite {
2951 return match logical.operator {
2952 LogicalOperator::And => Value::Unknown,
2953 LogicalOperator::Or | LogicalOperator::Coalesce => {
2954 let right = self.read(&logical.right, depth).0;
2955 Value::doubtful(any_of(&[Some(left), Some(right)]), false)
2956 }
2957 };
2958 }
2959 match left_is_value(logical.operator, &left) {
2960 Some(true) => left,
2961 Some(false) => self.expr(&logical.right, depth),
2962 None => Value::Unknown,
2963 }
2964 }
2965
2966 fn possibilities(&self, expr: &'p Expression<'a>, depth: u32) -> Vec<Value> {
2973 if depth > MAX_DEPTH {
2974 return vec![Value::Unknown];
2975 }
2976 match expr.get_inner_expression() {
2977 Expression::LogicalExpression(logical) => {
2978 let (left, definite) = self.read(&logical.left, depth);
2979 let primitive = is_primitive(&logical.left);
2980 let decided = match logical.operator {
2981 LogicalOperator::Coalesce if primitive && !is_void(&logical.left) => Some(true),
2982 _ if !definite => None,
2983 operator => left_is_value(operator, &left),
2984 };
2985 let left = if primitive || logical.operator == LogicalOperator::And {
2988 Value::Null
2989 } else {
2990 left
2991 };
2992 match decided {
2993 Some(true) => alternatives(left),
2994 Some(false) => self.possibilities(&logical.right, depth + 1),
2995 None => {
2996 let mut out = alternatives(left);
2997 out.extend(self.possibilities(&logical.right, depth + 1));
2998 out
2999 }
3000 }
3001 }
3002 Expression::ConditionalExpression(conditional) => match self.truthy(&conditional.test, depth) {
3003 Some(true) => self.possibilities(&conditional.consequent, depth + 1),
3004 Some(false) => self.possibilities(&conditional.alternate, depth + 1),
3005 None => {
3006 let mut out = self.possibilities(&conditional.consequent, depth + 1);
3007 out.extend(self.possibilities(&conditional.alternate, depth + 1));
3008 out
3009 }
3010 },
3011 _ => match self.expr(expr, depth) {
3012 Value::Maybe(value) => {
3013 let mut out = vec![Value::Undefined];
3014 out.extend(alternatives(*value));
3015 out
3016 }
3017 value => alternatives(value),
3018 },
3019 }
3020 }
3021
3022 fn spread(&self, argument: &'p Expression<'a>, out: &mut Vec<(String, Value)>, depth: u32) {
3026 let branches: Option<Vec<Vec<(String, Value)>>> = self
3027 .possibilities(argument, depth)
3028 .into_iter()
3029 .map(spread_entries)
3030 .collect();
3031 match branches {
3032 None => out.push((self.spread_key(argument).to_owned(), Value::Unknown)),
3033 Some(mut branches) if branches.len() == 1 => out.extend(branches.pop().unwrap_or_default()),
3034 Some(branches) => {
3035 let added = spread_any(out, &branches);
3036 out.extend(added);
3037 }
3038 }
3039 }
3040
3041 fn conditional(&self, conditional: &'p ConditionalExpression<'a>, depth: u32) -> Value {
3044 match self.truthy(&conditional.test, depth) {
3045 Some(true) => self.expr(&conditional.consequent, depth),
3046 Some(false) => self.expr(&conditional.alternate, depth),
3047 None => either(
3048 &self.expr(&conditional.consequent, depth),
3049 &self.expr(&conditional.alternate, depth),
3050 ),
3051 }
3052 }
3053
3054 fn url(&self, new: &'p NewExpression<'a>, depth: u32) -> Value {
3056 let is_url =
3057 matches!(new.callee.get_inner_expression(), Expression::Identifier(id) if id.name.as_str() == "URL");
3058 let mut args = new
3059 .arguments
3060 .iter()
3061 .map(|arg| arg.as_expression().map_or(Value::Unknown, |e| self.expr(e, depth)));
3062 match (is_url, args.next(), args.next(), args.next()) {
3063 (true, Some(Value::String(relative)), Some(Value::String(base)), None)
3064 if !relative.starts_with('/') && !relative.contains(':') && !base.contains(':') =>
3065 {
3066 Value::String(normalized(&[base.as_str(), "..", relative.as_str()]))
3067 }
3068 _ => Value::Unknown,
3069 }
3070 }
3071
3072 fn is_path_function(&self, callee: &Expression<'_>, wanted: &str) -> bool {
3074 match callee.get_inner_expression() {
3075 Expression::Identifier(id) => self
3076 .bindings
3077 .path_functions
3078 .get(id.name.as_str())
3079 .is_some_and(|function| function == wanted),
3080 Expression::StaticMemberExpression(member) => {
3081 member.property.name.as_str() == wanted
3082 && matches!(
3083 member.object.get_inner_expression(),
3084 Expression::Identifier(object) if self.bindings.path_modules.contains(object.name.as_str())
3085 )
3086 }
3087 _ => false,
3088 }
3089 }
3090
3091 fn path_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3093 let function = match call.callee.get_inner_expression() {
3094 Expression::Identifier(id) => {
3095 let name = id.name.as_str();
3096 if self.bindings.file_url_to_path.contains(name) {
3097 "fileURLToPath"
3098 } else {
3099 self.bindings.path_functions.get(name)?.as_str()
3100 }
3101 }
3102 Expression::StaticMemberExpression(member) => {
3103 let object = match member.object.get_inner_expression() {
3105 Expression::StaticMemberExpression(inner) if inner.property.name.as_str() == "posix" => {
3106 inner.object.get_inner_expression()
3107 }
3108 other => other,
3109 };
3110 let Expression::Identifier(object) = object else {
3111 return None;
3112 };
3113 let (object, property) = (object.name.as_str(), member.property.name.as_str());
3114 let path_function = self.bindings.path_modules.contains(object) && PATH_FUNCTIONS.contains(&property);
3115 let url_function = self.bindings.url_modules.contains(object) && property == "fileURLToPath";
3116 if path_function || url_function {
3117 property
3118 } else if object == "process" && property == "cwd" {
3119 return Some(Value::String(".".to_owned()));
3120 } else {
3121 return None;
3122 }
3123 }
3124 _ => return None,
3125 };
3126 Some(each_combination(&self.arguments(call, depth), |args| {
3127 apply_path_function(function, args)
3128 }))
3129 }
3130
3131 fn arguments(&self, call: &'p CallExpression<'a>, depth: u32) -> Vec<Value> {
3133 let values: Vec<Value> = call
3134 .arguments
3135 .iter()
3136 .map(|arg| arg.as_expression().map_or(Value::Unknown, |e| self.expr(e, depth)))
3137 .collect();
3138 self.arguments_of
3139 .borrow_mut()
3140 .entry(call.span)
3141 .or_insert_with(|| values.clone());
3142 values
3143 }
3144
3145 fn package_directory(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3152 if !self.is_path_function(&call.callee, "dirname") {
3153 return None;
3154 }
3155 let mut resolved = single_argument(call)?.get_inner_expression();
3156 if let Expression::CallExpression(to_path) = resolved
3157 && self.is_file_url_to_path(&to_path.callee)
3158 {
3159 resolved = single_argument(to_path)?.get_inner_expression();
3160 }
3161 let Expression::CallExpression(resolve) = resolved else {
3162 return None;
3163 };
3164 if !is_module_resolve(resolve) {
3165 return None;
3166 }
3167 let manifest = single_argument(resolve)?.get_inner_expression();
3168 if let Expression::CallExpression(join) = manifest
3169 && self.is_path_function(&join.callee, "join")
3170 {
3171 let arguments: Vec<&Expression<'a>> = join
3172 .arguments
3173 .iter()
3174 .map(Argument::as_expression)
3175 .collect::<Option<_>>()?;
3176 let (file, segments) = arguments.split_last()?;
3177 if segments.is_empty() || static_string(file).as_deref() != Some("package.json") {
3178 return None;
3179 }
3180 let segments: Option<Vec<String>> = segments
3181 .iter()
3182 .copied()
3183 .map(|segment| match self.expr(segment, depth) {
3184 Value::String(text) => Some(text),
3185 _ => None,
3186 })
3187 .collect();
3188 return match segments {
3189 Some(segments) => manifest_directory(&segments.join("/")),
3190 None => Some(Value::Unknown),
3191 };
3192 }
3193 let Value::String(manifest) = self.expr(manifest, depth) else {
3194 return None;
3195 };
3196 manifest_directory(manifest.strip_suffix("/package.json")?)
3197 }
3198
3199 fn is_file_url_to_path(&self, callee: &Expression<'_>) -> bool {
3201 match callee.get_inner_expression() {
3202 Expression::Identifier(id) => self.bindings.file_url_to_path.contains(id.name.as_str()),
3203 Expression::StaticMemberExpression(member) => {
3204 member.property.name.as_str() == "fileURLToPath"
3205 && matches!(
3206 member.object.get_inner_expression(),
3207 Expression::Identifier(object) if self.bindings.url_modules.contains(object.name.as_str())
3208 )
3209 }
3210 _ => false,
3211 }
3212 }
3213
3214 fn resolve_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3216 let known = is_module_resolve(call) || is_resolve_of(call, "Promise");
3217 known.then(|| single_argument(call).map_or(Value::Unknown, |arg| self.expr(arg, depth)))
3218 }
3219
3220 fn glob_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3222 let is_glob = match call.callee.get_inner_expression() {
3223 Expression::Identifier(id) => self.bindings.glob_names.contains(id.name.as_str()),
3224 Expression::StaticMemberExpression(member) => {
3225 GLOB_MEMBERS.contains(&member.property.name.as_str())
3226 && matches!(
3227 member.object.get_inner_expression(),
3228 Expression::Identifier(object) if self.bindings.glob_names.contains(object.name.as_str())
3229 )
3230 }
3231 _ => false,
3232 };
3233 if !is_glob {
3234 return None;
3235 }
3236 Some(match call.arguments.first().and_then(Argument::as_expression) {
3237 Some(patterns) => match self.read(patterns, depth).0 {
3238 Value::String(pattern) => Value::Array(vec![Value::String(pattern)]),
3239 array @ Value::Array(_) => array,
3240 _ => Value::Unknown,
3241 },
3242 None => Value::Unknown,
3243 })
3244 }
3245
3246 fn flat_config_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3249 let combines = match call.callee.get_inner_expression() {
3250 Expression::Identifier(id) => self.bindings.flat_config_functions.contains(id.name.as_str()),
3251 Expression::StaticMemberExpression(member) => {
3252 member.property.name.as_str() == "config"
3253 && matches!(
3254 member.object.get_inner_expression(),
3255 Expression::Identifier(object) if self.bindings.flat_config_modules.contains(object.name.as_str())
3256 )
3257 }
3258 _ => false,
3259 };
3260 if !combines {
3261 return None;
3262 }
3263 let mut list = Vec::new();
3264 for argument in &call.arguments {
3265 let value = match argument {
3266 Argument::SpreadElement(spread) => self.expr(&spread.argument, depth),
3267 other => other.as_expression().map_or(Value::Unknown, |e| self.expr(e, depth)),
3268 };
3269 flatten_into(value, &mut list);
3270 }
3271 Some(Value::Array(list))
3272 }
3273
3274 fn local_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3276 let Expression::Identifier(id) = call.callee.get_inner_expression() else {
3277 return None;
3278 };
3279 let name = id.name.as_str();
3280 let (callable, isolate) = match self.local_function(name) {
3281 Some(LocalBinding::Function(callable)) => (callable, false),
3282 Some(LocalBinding::Other) => return None,
3283 None => match self.functions.get(name) {
3284 Some(callable) => (*callable, true),
3285 None => return self.imported_call(name, call, depth),
3286 },
3287 };
3288 Some(self.invoke(callable, Some(self.arguments(call, depth).as_slice()), isolate, depth))
3289 }
3290
3291 fn local_function(&self, name: &str) -> Option<LocalBinding<'p, 'a>> {
3294 self.frames.borrow().iter().rev().find_map(|frame| {
3295 frame.get(name).map(|binding| match binding {
3296 Binding::Callable(callable) => LocalBinding::Function(*callable),
3297 Binding::Value(_) | Binding::Alias(_) => LocalBinding::Other,
3298 })
3299 })
3300 }
3301
3302 fn callback(&self, expr: &'p Expression<'a>) -> Option<(Callable<'p, 'a>, bool)> {
3306 if let Some(callable) = Callable::of_expression(expr) {
3307 return Some((callable, false));
3308 }
3309 let Expression::Identifier(id) = expr.get_inner_expression() else {
3310 return None;
3311 };
3312 let name = id.name.as_str();
3313 match self.local_function(name) {
3314 Some(LocalBinding::Function(callable)) => Some((callable, false)),
3315 Some(LocalBinding::Other) => None,
3316 None => self.functions.get(name).map(|callable| (*callable, true)),
3317 }
3318 }
3319
3320 fn array_callback_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3328 let Expression::StaticMemberExpression(member) = call.callee.get_inner_expression() else {
3329 return None;
3330 };
3331 let method = member.property.name.as_str();
3332 if !ARRAY_CALLBACK_METHODS.contains(&method) {
3333 return None;
3334 }
3335 let (callable, isolate) = self.callback(call.arguments.first()?.as_expression()?)?;
3336 let (Value::Array(items), mut definite) = self.read(&member.object, depth) else {
3337 return None;
3338 };
3339 let mut out = Vec::new();
3340 let mut indexed = true;
3341 for (index, item) in items.into_iter().enumerate() {
3342 if item == Value::Unknown {
3343 indexed = false;
3344 out.push(Value::Unknown);
3345 continue;
3346 }
3347 let index = u32::try_from(index)
3348 .ok()
3349 .filter(|_| indexed)
3350 .map_or(Value::Unknown, |index| Value::Number(f64::from(index)));
3351 let args = [item.clone(), index, Value::Unknown];
3352 let (result, result_definite) = self.invoke(callable, Some(args.as_slice()), isolate, depth).split();
3353 match method {
3354 "map" => out.push(Value::doubtful(result, result_definite)),
3355 "flatMap" => match result {
3356 Value::Array(listed) => {
3357 out.extend(listed);
3358 definite &= result_definite;
3359 }
3360 Value::OneOf(_) => out.push(Value::Unknown),
3361 other => out.push(Value::doubtful(other, result_definite)),
3362 },
3363 _ => match truthiness(&result).filter(|_| result_definite) {
3364 Some(false) => {}
3365 Some(true) => out.push(item),
3366 None => {
3367 definite = false;
3368 out.push(item);
3369 }
3370 },
3371 }
3372 }
3373 Some(Value::doubtful(Value::Array(out), definite))
3374 }
3375
3376 fn object_from_entries(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3383 let Expression::StaticMemberExpression(member) = call.callee.get_inner_expression() else {
3384 return None;
3385 };
3386 let on_object = matches!(
3387 member.object.get_inner_expression(),
3388 Expression::Identifier(object) if object.name.as_str() == "Object"
3389 );
3390 if !on_object || member.property.name.as_str() != "fromEntries" || self.binds("Object") {
3391 return None;
3392 }
3393 let Some(argument) = single_argument(call) else {
3394 return Some(Value::Unknown);
3395 };
3396 let (Value::Array(pairs), definite) = self.read(argument, depth) else {
3397 return Some(Value::Unknown);
3398 };
3399 let mut entries: Vec<(String, Value)> = Vec::new();
3400 for pair in pairs {
3401 let Some((key, value)) = entry_pair(pair) else {
3402 let marker = self.spread_key(argument);
3403 if entries.last().is_none_or(|(last, _)| last != marker) {
3404 entries.push((marker.to_owned(), Value::Unknown));
3405 }
3406 continue;
3407 };
3408 let value = Value::doubtful(value, definite);
3409 let found = entries
3410 .iter()
3411 .rposition(|(name, _)| *name == key || ConfigValue::is_spread_key(name))
3412 .filter(|&at| entries[at].0 == key);
3413 match found {
3414 Some(at) => entries[at].1 = value,
3415 None => entries.push((key, value)),
3416 }
3417 }
3418 Some(Value::Object(entries))
3419 }
3420
3421 fn merge_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3426 let style = self.merge_style(call)?;
3427 if takes_options(&call.callee, call.arguments.len()) {
3428 self.arguments(call, depth);
3430 return Some(Value::Unknown);
3431 }
3432 let mut merged: Option<Vec<(String, Value)>> = None;
3433 let expressions = call.arguments.iter().map(Argument::as_expression);
3434 let mut values: Vec<_> = self.arguments(call, depth).into_iter().zip(expressions).collect();
3435 if style == MergeStyle::FirstWins {
3436 values.reverse();
3437 }
3438 for (value, expression) in values {
3439 let (value, definite) = value.split();
3440 match value {
3441 Value::Null | Value::Undefined | Value::Bool(_) => {}
3442 Value::Object(entries) => {
3443 let entries = entries
3445 .into_iter()
3446 .map(|(key, value)| (key, Value::doubtful(value, definite)))
3447 .collect();
3448 let base = merged.get_or_insert_with(Vec::new);
3449 merge_objects(base, entries, style != MergeStyle::Shallow);
3450 }
3451 _ => {
3452 let key = expression.map_or(UNKNOWN_SPREAD, |expression| self.spread_key(expression));
3453 merge_objects(
3454 merged.get_or_insert_with(Vec::new),
3455 vec![(key.to_owned(), Value::Unknown)],
3456 false,
3457 );
3458 }
3459 }
3460 }
3461 Some(merged.map_or(Value::Unknown, Value::Object))
3462 }
3463
3464 fn filter_call(&self, call: &'p CallExpression<'a>, depth: u32) -> Option<Value> {
3468 let Expression::StaticMemberExpression(member) = call.callee.get_inner_expression() else {
3469 return None;
3470 };
3471 let by_boolean = matches!(
3472 single_argument(call).map(Expression::get_inner_expression),
3473 Some(Expression::Identifier(id))
3474 if id.name.as_str() == "Boolean" && !self.binds("Boolean")
3475 );
3476 if member.property.name.as_str() != "filter" || !by_boolean {
3477 return None;
3478 }
3479 let (items, mut definite) = match member.object.get_inner_expression() {
3480 Expression::ArrayExpression(array) => self.filtered_elements(&array.elements, depth),
3481 object => match self.read(object, depth) {
3482 (Value::Array(items), definite) => (items, definite),
3483 _ => return None,
3484 },
3485 };
3486 let mut kept = Vec::new();
3487 for item in items {
3488 if let Some((value, may_drop)) = truthy_part(item) {
3489 definite &= !may_drop;
3490 kept.push(value);
3491 }
3492 }
3493 Some(Value::doubtful(Value::Array(kept), definite))
3494 }
3495
3496 fn binds(&self, name: &str) -> bool {
3498 self.is_local(name)
3499 || self.consts.contains_key(name)
3500 || self.functions.contains_key(name)
3501 || self.imports.contains_key(name)
3502 }
3503
3504 fn filtered_elements(&self, elements: &'p [ArrayExpressionElement<'a>], depth: u32) -> (Vec<Value>, bool) {
3508 let mut items = Vec::new();
3509 let mut definite = true;
3510 for element in elements {
3511 if let Some(Expression::LogicalExpression(and)) =
3512 element.as_expression().map(Expression::get_inner_expression)
3513 && and.operator == LogicalOperator::And
3514 && self.truthy(&and.left, depth).is_none()
3515 {
3516 items.push(self.expr(&and.right, depth));
3517 definite = false;
3518 continue;
3519 }
3520 let (part, part_definite) = self.array(std::slice::from_ref(element), depth).split();
3521 if let Value::Array(part) = part {
3522 items.extend(part);
3523 }
3524 definite &= part_definite;
3525 }
3526 (items, definite)
3527 }
3528
3529 fn capture_set(&self, call: &'p CallExpression<'a>, depth: u32) -> bool {
3531 let Some(setter) = self.config_setter.borrow().clone() else {
3532 return false;
3533 };
3534 let Expression::StaticMemberExpression(member) = call.callee.get_inner_expression() else {
3535 return false;
3536 };
3537 let on_setter =
3538 matches!(member.object.get_inner_expression(), Expression::Identifier(id) if id.name.as_str() == setter);
3539 let Some(argument) = single_argument(call).filter(|_| on_setter && member.property.name.as_str() == "set")
3540 else {
3541 return false;
3542 };
3543 let value = self.expr(argument, depth);
3544 *self.captured.borrow_mut() = Some(value);
3545 true
3546 }
3547
3548 fn call(&self, call: &'p CallExpression<'a>, depth: u32) -> Value {
3549 if self.capture_set(call, depth) {
3550 return Value::Unknown;
3551 }
3552 let special = self
3553 .required_value(call, depth)
3554 .or_else(|| required_package(call, depth))
3555 .or_else(|| self.package_directory(call, depth))
3556 .or_else(|| self.path_call(call, depth))
3557 .or_else(|| self.resolve_call(call, depth))
3558 .or_else(|| self.glob_call(call, depth))
3559 .or_else(|| self.flat_config_call(call, depth))
3560 .or_else(|| self.local_call(call, depth))
3561 .or_else(|| self.merge_call(call, depth))
3562 .or_else(|| self.filter_call(call, depth))
3563 .or_else(|| self.array_callback_call(call, depth))
3564 .or_else(|| self.object_from_entries(call, depth));
3565 if let Some(value) = special {
3566 return value;
3567 }
3568 self.defined(call)
3569 .map_or(Value::Unknown, |first| self.expr(first, depth))
3570 }
3571
3572 fn defined(&self, call: &'p CallExpression<'a>) -> Option<&'p Expression<'a>> {
3576 defined(call).filter(|_| self.unstable.calls_foreign(call))
3577 }
3578
3579 fn merge_style(&self, call: &CallExpression<'_>) -> Option<MergeStyle> {
3582 merge_style(&call.callee, self.bindings).filter(|_| self.unstable.calls_foreign(call))
3583 }
3584
3585 fn unknown_call_arguments(&self, expr: &'p Expression<'a>, value: &Value, depth: u32) -> Vec<Value> {
3592 if depth > MAX_DEPTH {
3593 return Vec::new();
3594 }
3595 let depth = depth + 1;
3596 match expr.get_inner_expression() {
3597 Expression::AwaitExpression(a) => self.unknown_call_arguments(&a.argument, value, depth),
3598 Expression::CallExpression(call) => self.unknown_arguments_of_call(call, value, depth),
3599 Expression::Identifier(id) => self.constant_call_arguments(id.name.as_str(), value, depth),
3600 Expression::ArrowFunctionExpression(f) => self.returned_call_arguments(Callable::Arrow(f), value, depth),
3601 Expression::FunctionExpression(f) => self.returned_call_arguments(Callable::Function(f), value, depth),
3602 Expression::ObjectExpression(object) => self.object_call_arguments(&object.properties, value, depth),
3603 Expression::ArrayExpression(array) => self.array_call_arguments(&array.elements, value, depth),
3604 _ => Vec::new(),
3605 }
3606 }
3607
3608 fn nested_call_arguments(&self, expr: &'p Expression<'a>, value: &Value, depth: u32) -> Vec<Value> {
3612 if depth > MAX_DEPTH {
3613 return Vec::new();
3614 }
3615 let depth = depth + 1;
3616 match expr.get_inner_expression() {
3617 Expression::AwaitExpression(a) => self.nested_call_arguments(&a.argument, value, depth),
3618 Expression::CallExpression(call) => self.unknown_arguments_of_call(call, value, depth),
3619 Expression::ObjectExpression(object) => self.object_call_arguments(&object.properties, value, depth),
3620 Expression::ArrayExpression(array) => self.array_call_arguments(&array.elements, value, depth),
3621 _ => Vec::new(),
3622 }
3623 }
3624
3625 fn object_call_arguments(&self, properties: &'p [ObjectPropertyKind<'a>], value: &Value, depth: u32) -> Vec<Value> {
3630 let object = value.definite();
3631 let spread = if object.get(UNKNOWN_SPREAD).is_some() || object.get(OWN_SPREAD).is_some() {
3632 Value::Unknown
3633 } else {
3634 Value::Null
3635 };
3636 let mut out = Vec::new();
3637 for property in properties {
3638 match property {
3639 ObjectPropertyKind::ObjectProperty(p) => {
3640 let Some(key) = p.key.static_name() else {
3641 continue;
3642 };
3643 let key = crate::js_string(key.as_js_str());
3644 let field = object.get(&key).cloned().unwrap_or(Value::Unknown);
3645 out.extend(
3646 self.nested_call_arguments(&p.value, &field, depth)
3647 .into_iter()
3648 .map(|argument| Value::Object(vec![(key.clone(), argument)])),
3649 );
3650 }
3651 ObjectPropertyKind::SpreadProperty(s) => {
3652 out.extend(self.nested_call_arguments(&s.argument, &spread, depth));
3653 }
3654 }
3655 }
3656 out
3657 }
3658
3659 fn array_call_arguments(
3662 &self,
3663 elements: &'p [ArrayExpressionElement<'a>],
3664 value: &Value,
3665 depth: u32,
3666 ) -> Vec<Value> {
3667 let unknown = Value::Unknown;
3668 let items: &[Value] = match value.definite() {
3669 Value::Array(items) => items,
3670 _ => &[],
3671 };
3672 elements
3673 .iter()
3674 .take_while(|element| !matches!(element, ArrayExpressionElement::SpreadElement(_)))
3675 .enumerate()
3676 .filter_map(|(index, element)| Some((element.as_expression()?, items.get(index).unwrap_or(&unknown))))
3677 .flat_map(|(element, item)| self.nested_call_arguments(element, item, depth))
3678 .collect()
3679 }
3680
3681 fn unknown_arguments_of_call(&self, call: &'p CallExpression<'a>, value: &Value, depth: u32) -> Vec<Value> {
3683 if let Some(first) = self.defined(call) {
3684 return self.unknown_call_arguments(first, value, depth);
3685 }
3686 let recorded = self.arguments_of.borrow().get(&call.span).cloned();
3687 if self.merge_style(call).is_some() {
3688 let values = recorded.unwrap_or_default();
3689 return call
3690 .arguments
3691 .iter()
3692 .zip(&values)
3693 .filter_map(|(argument, value)| Some((argument.as_expression()?, value)))
3694 .flat_map(|(argument, value)| self.unknown_call_arguments(argument, value, depth))
3695 .collect();
3696 }
3697 if !matches!(value.definite(), Value::Unknown | Value::OneOf(_)) {
3698 return Vec::new();
3699 }
3700 recorded.unwrap_or_else(|| self.arguments(call, depth))
3701 }
3702
3703 fn constant_call_arguments(&self, name: &str, value: &Value, depth: u32) -> Vec<Value> {
3706 if self.is_local(name) || self.functions.contains_key(name) {
3707 return Vec::new();
3708 }
3709 let Some(&init) = self.consts.get(name) else {
3710 return Vec::new();
3711 };
3712 let saved = std::mem::take(&mut *self.frames.borrow_mut());
3713 let arguments = self.unknown_call_arguments(init, value, depth);
3714 *self.frames.borrow_mut() = saved;
3715 arguments
3716 }
3717
3718 fn returned_call_arguments(&self, callable: Callable<'p, 'a>, value: &Value, depth: u32) -> Vec<Value> {
3722 let (params, body, statements) = match callable {
3723 Callable::Arrow(f) => (&f.params, f.get_expression(), f.get_function_body()),
3724 Callable::Function(f) => (&f.params, None, f.body.as_deref()),
3725 };
3726 let returned = body.or_else(|| match statements?.statements.as_slice() {
3727 [only] => single_return(only)?.argument.as_ref(),
3728 _ => None,
3729 });
3730 let Some(returned) = returned else {
3731 return Vec::new();
3732 };
3733 self.frames.borrow_mut().push(Frame::default());
3734 for param in ¶ms.items {
3735 self.bind_pattern(¶m.pattern, Value::Unknown, depth);
3736 }
3737 if let Some(param) = ¶ms.rest {
3738 self.bind_pattern(¶m.rest.argument, Value::Unknown, depth);
3739 }
3740 let arguments = self.unknown_call_arguments(returned, value, depth);
3741 self.frames.borrow_mut().pop();
3742 arguments
3743 }
3744
3745 fn invoke(&self, callable: Callable<'p, 'a>, args: Option<&[Value]>, isolate: bool, depth: u32) -> Value {
3750 if self.calls.get() >= MAX_CALLS {
3751 return Value::Unknown;
3752 }
3753 self.calls.set(self.calls.get() + 1);
3754 let params = match callable {
3755 Callable::Function(f) => &f.params,
3756 Callable::Arrow(f) => &f.params,
3757 };
3758 let saved = if isolate {
3759 std::mem::take(&mut *self.frames.borrow_mut())
3760 } else {
3761 Vec::new()
3762 };
3763 self.frames.borrow_mut().push(Frame::default());
3765 for (index, param) in params.items.iter().enumerate() {
3766 let value = match (args.map(|args| args.get(index)), ¶m.initializer) {
3767 (Some(Some(value)), Some(default)) => self.defaulted(value.clone(), default, depth),
3768 (Some(Some(value)), None) => value.clone(),
3769 (Some(None), Some(default)) => self.expr(default, depth),
3770 (None | Some(None), _) => Value::Unknown,
3771 };
3772 self.bind_pattern(¶m.pattern, value, depth);
3773 }
3774 if let Some(param) = ¶ms.rest {
3775 self.bind_pattern(¶m.rest.argument, Value::Unknown, depth);
3776 }
3777 let value = match callable {
3778 Callable::Arrow(f) => match f.get_expression() {
3779 Some(body) => self.expr(body, depth),
3780 None => f
3781 .get_function_body()
3782 .map_or(Value::Unknown, |b| self.returned(&b.statements, depth)),
3783 },
3784 Callable::Function(f) => f
3785 .body
3786 .as_ref()
3787 .map_or(Value::Unknown, |b| self.returned(&b.statements, depth)),
3788 };
3789 self.frames.borrow_mut().pop();
3790 if isolate {
3791 *self.frames.borrow_mut() = saved;
3792 }
3793 value
3794 }
3795
3796 fn call_with(&self, callable: Callable<'p, 'a>, argument: Value, depth: u32) -> Value {
3799 let statements = match callable {
3800 Callable::Arrow(f) => f.get_function_body().map(|body| &body.statements),
3801 Callable::Function(f) => f.body.as_ref().map(|body| &body.statements),
3802 };
3803 let (Some(name), Some(statements)) = (first_parameter(callable), statements) else {
3804 return self.invoke(callable, Some(std::slice::from_ref(&argument)), false, depth);
3805 };
3806 let params = match callable {
3807 Callable::Function(f) => &f.params,
3808 Callable::Arrow(f) => &f.params,
3809 };
3810 self.frames.borrow_mut().push(Frame::default());
3811 self.bind(&name, argument);
3812 for param in params.items.iter().skip(1) {
3813 self.bind_pattern(¶m.pattern, Value::Unknown, depth);
3814 }
3815 if let Some(param) = ¶ms.rest {
3816 self.bind_pattern(¶m.rest.argument, Value::Unknown, depth);
3817 }
3818 let returned = self.run(statements, depth);
3819 let frame = self.frames.borrow_mut().pop();
3820 returned.unwrap_or_else(|| match frame.and_then(|mut frame| frame.remove(&name)) {
3821 Some(Binding::Value(value)) => value,
3822 _ => Value::Unknown,
3823 })
3824 }
3825
3826 fn bind_pattern(&self, pattern: &'p BindingPattern<'a>, value: Value, depth: u32) {
3831 match pattern {
3832 BindingPattern::BindingIdentifier(id) => self.bind(id.name.as_str(), value),
3833 BindingPattern::AssignmentPattern(p) => {
3834 let value = self.defaulted(value, &p.right, depth);
3835 self.bind_pattern(&p.left, value, depth);
3836 }
3837 BindingPattern::ObjectPattern(object) => {
3838 for property in &object.properties {
3839 let field = property.key.static_name().map_or(Value::Unknown, |key| {
3840 property_of(&value, &crate::js_string(key.as_js_str()))
3841 });
3842 self.bind_pattern(&property.value, field, depth);
3843 }
3844 if let Some(rest) = &object.rest {
3845 self.bind_pattern(&rest.argument, Value::Unknown, depth);
3846 }
3847 }
3848 BindingPattern::ArrayPattern(_) => {
3849 for id in pattern.get_binding_identifiers() {
3850 self.bind(id.name.as_str(), Value::Unknown);
3851 }
3852 }
3853 }
3854 }
3855
3856 fn defaulted(&self, value: Value, default: &'p Expression<'a>, depth: u32) -> Value {
3860 match value {
3861 Value::Undefined => self.expr(default, depth),
3862 Value::Maybe(value) => {
3863 let default = self.read(default, depth).0;
3864 Value::doubtful(any_of(&[Some(*value), Some(default)]), false)
3865 }
3866 value => value,
3867 }
3868 }
3869
3870 fn bind(&self, name: &str, value: Value) {
3872 self.bind_to(name, Binding::Value(value));
3873 }
3874
3875 fn bind_to(&self, name: &str, binding: Binding<'p, 'a>) {
3877 if let Some(frame) = self.frames.borrow_mut().last_mut() {
3878 frame.insert(name.to_owned(), binding);
3879 }
3880 }
3881
3882 fn returned(&self, statements: &'p [Statement<'a>], depth: u32) -> Value {
3885 if let [Statement::ExpressionStatement(s)] = statements {
3886 return self.expr(&s.expression, depth);
3887 }
3888 self.run(statements, depth).unwrap_or(Value::Unknown)
3889 }
3890
3891 fn run(&self, statements: &'p [Statement<'a>], depth: u32) -> Option<Value> {
3898 for (index, statement) in statements.iter().enumerate() {
3899 match statement {
3900 Statement::ReturnStatement(r) => {
3901 if let Some(argument) = &r.argument {
3903 self.havoc(Writes::of_expression(argument, self.unstable));
3904 }
3905 return Some(r.argument.as_ref().map_or(Value::Unknown, |e| self.expr(e, depth)));
3906 }
3907 Statement::ExpressionStatement(s) => self.mutation(&s.expression, depth),
3908 Statement::BlockStatement(b) => {
3909 if let Some(value) = self.block(&b.body, depth) {
3910 return Some(value);
3911 }
3912 }
3913 Statement::IfStatement(i) => {
3914 self.havoc(Writes::of_expression(&i.test, self.unstable));
3915 let taken = match self.truthy(&i.test, depth) {
3916 Some(true) => Some(&i.consequent),
3917 Some(false) => i.alternate.as_ref(),
3918 None => {
3919 if !returns(statement) {
3920 self.either_branch(i, depth);
3921 continue;
3922 }
3923 let rest = &statements[index + 1..];
3924 return Some(self.either_return(i, rest, depth));
3925 }
3926 };
3927 if let Some(value) = taken.and_then(|branch| self.block(std::slice::from_ref(branch), depth)) {
3928 return Some(value);
3929 }
3930 }
3931 other if returns(other) => return Some(Value::Unknown),
3932 other => match other.as_declaration() {
3933 Some(Declaration::VariableDeclaration(v)) => {
3934 self.havoc(Writes::of_statement(other, self.unstable));
3935 self.declare(v, depth);
3936 }
3937 Some(Declaration::FunctionDeclaration(f)) => {
3938 if let Some(id) = &f.id {
3939 self.bind_to(id.name.as_str(), Binding::Callable(Callable::Function(f)));
3940 }
3941 }
3942 Some(_) => {}
3943 None => self.havoc(Writes::of_statement(other, self.unstable)),
3944 },
3945 }
3946 }
3947 None
3948 }
3949
3950 fn block(&self, statements: &'p [Statement<'a>], depth: u32) -> Option<Value> {
3953 self.frames.borrow_mut().push(Frame::default());
3954 let value = self.run(statements, depth);
3955 self.frames.borrow_mut().pop();
3956 let mut vars = Vars::default();
3957 for statement in statements {
3958 vars.visit_statement(statement);
3959 }
3960 for name in vars.0 {
3961 self.assign(&name, Binding::Value(Value::Unknown));
3962 }
3963 value
3964 }
3965
3966 fn either_branch(&self, statement: &'p IfStatement<'a>, depth: u32) {
3969 let before = self.frames.borrow().clone();
3970 let captured = self.captured.borrow().clone();
3971 self.block(std::slice::from_ref(&statement.consequent), depth);
3972 let consequent = std::mem::replace(&mut *self.frames.borrow_mut(), before);
3973 let consequent_captured = std::mem::replace(&mut *self.captured.borrow_mut(), captured);
3974 if let Some(alternate) = &statement.alternate {
3975 self.block(std::slice::from_ref(alternate), depth);
3976 }
3977 let mut frames = self.frames.borrow_mut();
3978 for (frame, other) in frames.iter_mut().zip(consequent) {
3979 let mut names: Vec<String> = frame.keys().chain(other.keys()).cloned().collect();
3981 names.sort_unstable();
3982 names.dedup();
3983 for name in names {
3984 let joined = match (frame.get(&name), other.get(&name)) {
3985 (Some(Binding::Value(a)), Some(Binding::Value(b))) => Binding::Value(join(a, b)),
3986 (Some(Binding::Alias(a)), Some(Binding::Alias(b))) if a.same_part(b) => Binding::Alias(Alias {
3987 value: join(&a.value, &b.value),
3988 ..a.clone()
3989 }),
3990 (Some(Binding::Callable(a)), Some(Binding::Callable(b))) if a.same(*b) => Binding::Callable(*a),
3991 _ => Binding::Value(Value::Unknown),
3992 };
3993 frame.insert(name, joined);
3994 }
3995 }
3996 let mut captured = self.captured.borrow_mut();
3997 if *captured != consequent_captured {
3998 *captured = Some(match (&*captured, &consequent_captured) {
3999 (Some(a), Some(b)) => either(a, b),
4000 _ => Value::Unknown,
4001 });
4002 }
4003 }
4004
4005 fn either_return(&self, statement: &'p IfStatement<'a>, rest: &'p [Statement<'a>], depth: u32) -> Value {
4009 let path = |branch: Option<&'p Statement<'a>>| -> Option<Value> {
4010 match branch.map(single_return) {
4011 Some(Some(r)) => Some(r.argument.as_ref().map_or(Value::Unknown, |e| self.expr(e, depth))),
4013 Some(None) if branch.is_some_and(returns) => Some(Value::Unknown),
4015 _ => None,
4017 }
4018 };
4019 let consequent = path(Some(&statement.consequent));
4021 let alternate = path(statement.alternate.as_ref());
4022 let after = || {
4023 for branch in [Some(&statement.consequent), statement.alternate.as_ref()]
4024 .into_iter()
4025 .flatten()
4026 {
4027 if !returns(branch) {
4028 self.havoc(Writes::of_statement(branch, self.unstable));
4029 }
4030 }
4031 self.run(rest, depth).unwrap_or(Value::Unknown)
4032 };
4033 let (a, b) = match (consequent, alternate) {
4034 (Some(a), Some(b)) => (a, b),
4035 (Some(a), None) => (a, after()),
4036 (None, Some(b)) => (after(), b),
4037 (None, None) => return Value::Unknown,
4038 };
4039 either(&a, &b)
4040 }
4041
4042 fn declare(&self, declaration: &'p VariableDeclaration<'a>, depth: u32) {
4046 for declarator in &declaration.declarations {
4047 if let BindingPattern::BindingIdentifier(id) = &declarator.id
4048 && let Some(callable) = declarator.init.as_ref().and_then(Callable::of_expression)
4049 {
4050 self.bind_to(id.name.as_str(), Binding::Callable(callable));
4051 continue;
4052 }
4053 let value = declarator
4054 .init
4055 .as_ref()
4056 .map_or(Value::Unknown, |init| self.expr(init, depth));
4057 let value = if node_count(&value) > MAX_CONST_NODES {
4058 Value::Unknown
4059 } else {
4060 value
4061 };
4062 let aliases = self.aliases(declaration.kind, declarator);
4063 self.bind_pattern(&declarator.id, value, depth);
4064 for (name, alias) in aliases {
4065 self.bind_alias(&name, alias);
4066 }
4067 }
4068 }
4069
4070 fn aliases(&self, kind: VariableDeclarationKind, declarator: &'p VariableDeclarator<'a>) -> Vec<(String, Alias)> {
4073 if kind != VariableDeclarationKind::Const {
4074 return Vec::new();
4075 }
4076 let Some((root, keys)) = declarator.init.as_ref().and_then(|init| alias_source(init)) else {
4077 return Vec::new();
4078 };
4079 let root = root.name.as_str();
4080 let Some(frame) = self.frames.borrow().iter().rposition(|frame| frame.contains_key(root)) else {
4081 return Vec::new();
4082 };
4083 alias_names(&declarator.id)
4084 .unwrap_or_default()
4085 .into_iter()
4086 .filter(|(id, _)| self.unstable.follows(id))
4087 .map(|(id, key)| {
4088 let mut keys = keys.clone();
4089 keys.extend(key);
4090 let alias = Alias {
4091 value: Value::Unknown,
4092 root: root.to_owned(),
4093 frame,
4094 keys,
4095 };
4096 (id.name.as_str().to_owned(), alias)
4097 })
4098 .collect()
4099 }
4100
4101 fn bind_alias(&self, name: &str, mut alias: Alias) {
4103 let mut frames = self.frames.borrow_mut();
4104 if let Some(binding) = frames.last_mut().and_then(|frame| frame.get_mut(name))
4105 && let Binding::Value(value) = &mut *binding
4106 {
4107 alias.value = std::mem::replace(value, Value::Unknown);
4108 *binding = Binding::Alias(alias);
4109 }
4110 }
4111
4112 fn havoc(&self, writes: Writes<'_>) {
4115 for name in writes.assigned {
4116 self.assign(&name, Binding::Value(Value::Unknown));
4117 }
4118 let mut changed = writes.changed;
4119 changed.sort_unstable();
4120 changed.dedup();
4121 for (name, write) in changed {
4122 self.edit(&name, &write.keys, &write.edit());
4123 }
4124 }
4125
4126 fn mutation(&self, expr: &'p Expression<'a>, depth: u32) {
4131 match expr.get_inner_expression() {
4132 Expression::AwaitExpression(a) => self.mutation(&a.argument, depth),
4133 Expression::AssignmentExpression(a) => {
4134 self.havoc(Writes::of_expression(&a.right, self.unstable));
4135 let assign = a.operator == AssignmentOperator::Assign;
4136 match &a.left {
4137 AssignmentTarget::AssignmentTargetIdentifier(id) => {
4138 let value = if assign {
4139 self.expr(&a.right, depth)
4140 } else {
4141 Value::Unknown
4142 };
4143 self.assign(id.name.as_str(), Binding::Value(value));
4144 }
4145 AssignmentTarget::StaticMemberExpression(m) => match reach(&m.object) {
4146 Some((root, keys, true)) => {
4147 let value = if assign {
4148 self.expr(&a.right, depth)
4149 } else {
4150 Value::Unknown
4151 };
4152 let edit = Edit::Set(m.property.name.as_str().to_owned(), value);
4153 self.edit(root.name.as_str(), &keys, &edit);
4154 }
4155 Some((root, keys, false)) => self.edit(root.name.as_str(), &keys, &Edit::Taint),
4156 None => {
4159 let mut writes = Writes::new(self.unstable);
4160 writes.visit_assignment_target(&a.left);
4161 self.havoc(writes.through_aliases());
4162 }
4163 },
4164 _ => self.havoc(Writes::of_expression(expr, self.unstable)),
4165 }
4166 }
4167 Expression::CallExpression(c) => {
4168 if !self.capture_set(c, depth) && !self.insert(c, depth) {
4169 self.havoc(Writes::of_effect(expr, self.unstable));
4170 }
4171 }
4172 _ => self.havoc(Writes::of_expression(expr, self.unstable)),
4174 }
4175 }
4176
4177 fn insert(&self, call: &'p CallExpression<'a>, depth: u32) -> bool {
4181 let Expression::StaticMemberExpression(member) = call.callee.get_inner_expression() else {
4182 return false;
4183 };
4184 let front = match member.property.name.as_str() {
4185 "push" => false,
4186 "unshift" => true,
4187 _ => return false,
4188 };
4189 let Some((root, keys, true)) = reach(&member.object) else {
4190 return false;
4191 };
4192 let Some(arguments) = call
4193 .arguments
4194 .iter()
4195 .map(Argument::as_expression)
4196 .collect::<Option<Vec<_>>>()
4197 else {
4198 return false;
4199 };
4200 if !self.is_list(root.name.as_str(), &keys) {
4201 return false;
4202 }
4203 for argument in &arguments {
4204 self.havoc(Writes::of_expression(argument, self.unstable));
4205 }
4206 let items = arguments
4207 .into_iter()
4208 .map(|argument| self.expr(argument, depth))
4209 .collect();
4210 self.edit(root.name.as_str(), &keys, &Edit::Insert { front, items });
4211 true
4212 }
4213
4214 fn is_list(&self, name: &str, keys: &[String]) -> bool {
4216 let frames = self.frames.borrow();
4217 let Some(binding) = frames.iter().rev().find_map(|frame| frame.get(name)) else {
4218 return false;
4219 };
4220 match binding {
4221 Binding::Value(value) | Binding::Alias(Alias { value, .. }) => {
4222 value_at(value.definite(), keys).is_some_and(holds_list)
4223 }
4224 Binding::Callable(_) => false,
4225 }
4226 }
4227
4228 fn edit(&self, name: &str, keys: &[String], edit: &Edit) {
4231 let mut frames = self.frames.borrow_mut();
4232 if let Some(index) = frames.iter().rposition(|frame| frame.contains_key(name)) {
4233 edit_binding(&mut frames, index, name, keys, edit);
4234 }
4235 }
4236
4237 fn array(&self, elements: &'p [ArrayExpressionElement<'a>], depth: u32) -> Value {
4240 let mut out = Vec::new();
4241 let mut definite = true;
4242 for element in elements {
4243 match element {
4244 ArrayExpressionElement::SpreadElement(s) => {
4245 let (items, spread_definite) = self.spread_elements(&s.argument, depth);
4246 out.extend(items);
4247 definite &= spread_definite;
4248 }
4249 other => out.push(other.as_expression().map_or(Value::Unknown, |e| self.expr(e, depth))),
4250 }
4251 }
4252 Value::doubtful(Value::Array(out), definite)
4253 }
4254
4255 fn spread_elements(&self, argument: &'p Expression<'a>, depth: u32) -> (Vec<Value>, bool) {
4260 let unknown = || (vec![Value::Unknown], true);
4261 let mut branches = self.possibilities(argument, depth);
4262 if branches.len() == 1 {
4263 return match branches.pop() {
4264 Some(Value::Array(items)) => (items, true),
4265 _ => unknown(),
4266 };
4267 }
4268 let mut items: Vec<Value> = Vec::new();
4269 for branch in branches {
4270 match branch {
4271 Value::Array(listed) => {
4272 for item in listed {
4273 if !items.contains(&item) {
4274 items.push(item);
4275 }
4276 }
4277 }
4278 Value::Null | Value::Undefined | Value::Bool(_) => {}
4279 _ => return unknown(),
4280 }
4281 }
4282 (items, false)
4283 }
4284
4285 fn object(&self, properties: &'p [ObjectPropertyKind<'a>], depth: u32) -> Value {
4286 let mut out: Vec<(String, Value)> = Vec::new();
4287 for property in properties {
4288 match property {
4289 ObjectPropertyKind::SpreadProperty(s) => self.spread(&s.argument, &mut out, depth),
4290 ObjectPropertyKind::ObjectProperty(p) => {
4291 let Some(key) = p.key.static_name() else {
4292 out.push((UNKNOWN_SPREAD.to_owned(), Value::Unknown));
4293 continue;
4294 };
4295 let value = if p.method || p.kind != oxc_ast::ast::PropertyKind::Init {
4296 Value::Unknown
4297 } else {
4298 self.expr(&p.value, depth)
4299 };
4300 out.push((crate::js_string(key.as_js_str()), value));
4301 }
4302 }
4303 }
4304 Value::Object(out)
4305 }
4306}
4307
4308fn edit_binding(frames: &mut [Frame<'_, '_>], index: usize, name: &str, keys: &[String], edit: &Edit) {
4312 let Some(binding) = frames.get_mut(index).and_then(|frame| frame.get_mut(name)) else {
4313 return;
4314 };
4315 let (root, frame, part, before) = match binding {
4316 Binding::Value(value) => return edit_at(value, keys, edit),
4317 Binding::Callable(_) => return,
4318 Binding::Alias(alias) => {
4319 let before = alias.value.clone();
4320 edit_at(&mut alias.value, keys, edit);
4321 (alias.root.clone(), alias.frame, alias.keys.clone(), before)
4322 }
4323 };
4324 let held = frames
4325 .get(frame)
4326 .and_then(|bindings| bindings.get(&root))
4327 .and_then(|binding| match binding {
4328 Binding::Value(value) | Binding::Alias(Alias { value, .. }) => value_at(value, &part),
4329 Binding::Callable(_) => None,
4330 });
4331 if held == Some(&before) {
4332 let mut path = part;
4333 path.extend_from_slice(keys);
4334 edit_binding(frames, frame, &root, &path, edit);
4335 } else {
4336 edit_binding(frames, frame, &root, &part, &Edit::Taint);
4337 }
4338}
4339
4340fn edit_at(value: &mut Value, keys: &[String], edit: &Edit) {
4345 let target = value.definite_mut();
4346 if let Value::OneOf(alternatives) = &mut *target {
4347 for alternative in alternatives {
4348 edit_at(alternative, keys, edit);
4349 }
4350 return;
4351 }
4352 let Some((key, rest)) = keys.split_first() else {
4353 return match edit {
4354 Edit::Set(key, field) => set_entry(target, key, field.clone()),
4355 Edit::Insert { front, items } => insert_items(target, *front, items),
4356 Edit::Taint => taint_value(target),
4357 };
4358 };
4359 match target {
4360 Value::Object(entries) => {
4361 let found = entries
4362 .iter()
4363 .rposition(|(name, _)| name == key || ConfigValue::is_spread_key(name));
4364 match found {
4365 Some(at) if entries[at].0 == *key => edit_at(&mut entries[at].1, rest, edit),
4366 Some(_) => open_entries(entries),
4368 None if inherits(entries, key) => open_entries(entries),
4369 None => {}
4370 }
4371 }
4372 other => taint_value(other),
4373 }
4374}
4375
4376fn set_entry(value: &mut Value, key: &str, field: Value) {
4378 match value.definite_mut() {
4379 Value::Object(entries) => match entries.iter_mut().find(|(existing, _)| existing == key) {
4380 Some((_, slot)) => *slot = field,
4381 None => entries.push((key.to_owned(), field)),
4382 },
4383 Value::Array(items) => items.push(Value::Unknown),
4384 _ => {}
4385 }
4386}
4387
4388fn holds_list(value: &Value) -> bool {
4390 match value.definite() {
4391 Value::Array(_) => true,
4392 Value::OneOf(alternatives) => alternatives.iter().all(holds_list),
4393 _ => false,
4394 }
4395}
4396
4397fn insert_items(value: &mut Value, front: bool, items: &[Value]) {
4399 match value {
4400 Value::Array(list) if front => {
4401 list.splice(0..0, items.iter().cloned());
4402 }
4403 Value::Array(list) => list.extend(items.iter().cloned()),
4404 other => taint_value(other),
4405 }
4406}
4407
4408fn taint_value(value: &mut Value) {
4412 match value.definite_mut() {
4413 Value::Object(entries) => open_entries(entries),
4414 Value::Array(items) => {
4415 items.iter_mut().for_each(open_below);
4416 if !matches!(items.last(), Some(Value::Unknown)) {
4417 items.push(Value::Unknown);
4418 }
4419 }
4420 _ => {}
4421 }
4422}
4423
4424fn open_below(value: &mut Value) {
4426 match value {
4427 Value::Object(entries) => {
4428 for (_, field) in entries.iter_mut() {
4429 open_below(field);
4430 }
4431 open_entries(entries);
4432 }
4433 Value::Array(items) => {
4434 items.iter_mut().for_each(open_below);
4435 if !matches!(items.last(), Some(Value::Unknown)) {
4436 items.push(Value::Unknown);
4437 }
4438 }
4439 Value::OneOf(alternatives) => alternatives.iter_mut().for_each(open_below),
4440 Value::Maybe(inner) => open_below(inner),
4441 _ => {}
4442 }
4443}
4444
4445fn open_entries(entries: &mut Vec<(String, Value)>) {
4447 if entries.last().is_none_or(|(key, _)| key != UNKNOWN_SPREAD) {
4448 entries.push((UNKNOWN_SPREAD.to_owned(), Value::Unknown));
4449 }
4450}
4451
4452fn value_at<'v>(value: &'v Value, keys: &[String]) -> Option<&'v Value> {
4455 let Some((key, rest)) = keys.split_first() else {
4456 return Some(value);
4457 };
4458 let Value::Object(entries) = value else {
4459 return None;
4460 };
4461 entries
4462 .iter()
4463 .rev()
4464 .find(|(name, _)| name == key || ConfigValue::is_spread_key(name))
4465 .filter(|(name, _)| name == key)
4466 .and_then(|(_, child)| value_at(child, rest))
4467}
4468
4469pub(crate) fn defined<'p, 'a>(call: &'p CallExpression<'a>) -> Option<&'p Expression<'a>> {
4473 let callee = match call.callee.get_inner_expression() {
4474 Expression::Identifier(id) => id.name.as_str(),
4475 Expression::StaticMemberExpression(m) if !on_reflection(&m.object) => m.property.name.as_str(),
4476 _ => return None,
4477 };
4478 let is_define = callee.starts_with("define") && callee.len() > "define".len();
4479 call.arguments
4480 .first()
4481 .and_then(Argument::as_expression)
4482 .filter(|_| is_define)
4483}
4484
4485fn on_reflection(object: &Expression<'_>) -> bool {
4487 matches!(
4488 object.get_inner_expression(),
4489 Expression::Identifier(id) if matches!(id.name.as_str(), "Object" | "Reflect")
4490 )
4491}
4492
4493#[derive(Debug, Clone, Copy, PartialEq, Eq)]
4495enum MergeStyle {
4496 Deep,
4498 Shallow,
4500 FirstWins,
4502}
4503
4504fn merge_style(callee: &Expression<'_>, bindings: &ModuleBindings) -> Option<MergeStyle> {
4507 match callee.get_inner_expression() {
4508 Expression::Identifier(id) => match id.name.as_str() {
4509 name if bindings.foreign_merges.contains(name) => None,
4510 "mergeConfig" | "merge" | "deepmerge" | "deepMerge" | "mergeDeep" | "mergeWith" => Some(MergeStyle::Deep),
4511 "defu" => Some(MergeStyle::FirstWins),
4512 _ => None,
4513 },
4514 Expression::StaticMemberExpression(member) => {
4515 let object_is = |name: &str| matches!(member.object.get_inner_expression(), Expression::Identifier(o) if o.name.as_str() == name);
4516 let merge_object = matches!(
4517 member.object.get_inner_expression(),
4518 Expression::Identifier(o)
4519 if matches!(o.name.as_str(), "_" | "lodash") || bindings.merge_names.contains(o.name.as_str())
4520 );
4521 match member.property.name.as_str() {
4522 "assign" if object_is("Object") => Some(MergeStyle::Shallow),
4523 "merge" | "mergeWith" if merge_object => Some(MergeStyle::Deep),
4524 "mergeConfig" | "deepmerge" | "mergeDeep" => Some(MergeStyle::Deep),
4525 _ => None,
4526 }
4527 }
4528 _ => None,
4529 }
4530}
4531
4532fn takes_options(callee: &Expression<'_>, count: usize) -> bool {
4535 let name = match callee.get_inner_expression() {
4536 Expression::Identifier(id) => id.name.as_str(),
4537 Expression::StaticMemberExpression(member) => member.property.name.as_str(),
4538 _ => return false,
4539 };
4540 match name {
4541 "deepmerge" | "deepMerge" => count > 2,
4542 "mergeWith" => true,
4543 _ => false,
4544 }
4545}
4546
4547fn merge_objects(base: &mut Vec<(String, Value)>, incoming: Vec<(String, Value)>, deep: bool) {
4550 for (key, value) in incoming {
4551 let Some(index) = base.iter().position(|(k, _)| *k == key) else {
4552 base.push((key, value));
4553 continue;
4554 };
4555 if ConfigValue::is_spread_key(&key) {
4556 continue;
4557 }
4558 let slot = &mut base[index].1;
4559 *slot = merged_value(std::mem::replace(slot, Value::Unknown), value, deep);
4560 }
4561}
4562
4563fn merged_value(current: Value, incoming: Value, deep: bool) -> Value {
4568 let (current, current_definite) = current.split();
4569 let (incoming, incoming_definite) = incoming.split();
4570 let merged = match (deep, current, incoming) {
4571 (true, Value::Object(mut left), Value::Object(right)) => {
4572 merge_objects(&mut left, right, true);
4573 Value::Object(left)
4574 }
4575 (true, Value::Array(mut left), Value::Array(right)) => {
4576 left.extend(right);
4577 Value::Array(left)
4578 }
4579 (true, Value::Object(mut known), Value::Unknown | Value::OneOf(_))
4581 | (true, Value::Unknown | Value::OneOf(_), Value::Object(mut known)) => {
4582 if !known.iter().any(|(k, _)| ConfigValue::is_spread_key(k)) {
4583 known.push((UNKNOWN_SPREAD.to_owned(), Value::Unknown));
4584 }
4585 Value::Object(known)
4586 }
4587 (true, Value::Array(mut known), Value::Unknown | Value::OneOf(_))
4588 | (true, Value::Unknown | Value::OneOf(_), Value::Array(mut known)) => {
4589 known.push(Value::Unknown);
4590 Value::Array(known)
4591 }
4592 (_, current, incoming) if !incoming_definite => {
4593 return Value::doubtful(any_of(&[Some(current), Some(incoming)]), false);
4594 }
4595 (_, _, incoming) => return incoming,
4596 };
4597 Value::doubtful(merged, current_definite && incoming_definite)
4598}
4599
4600fn single_return<'p, 'a>(statement: &'p Statement<'a>) -> Option<&'p ReturnStatement<'a>> {
4602 match statement {
4603 Statement::ReturnStatement(r) => Some(r),
4604 Statement::BlockStatement(b) => match b.body.as_slice() {
4605 [only] => single_return(only),
4606 _ => None,
4607 },
4608 _ => None,
4609 }
4610}
4611
4612fn returns(statement: &Statement<'_>) -> bool {
4614 #[derive(Default)]
4615 struct Finder(bool);
4616 impl<'a> Visit<'a> for Finder {
4617 fn visit_return_statement(&mut self, _: &ReturnStatement<'a>) {
4618 self.0 = true;
4619 }
4620 fn visit_function(&mut self, _: &Function<'a>, _: oxc_semantic::ScopeFlags) {}
4621 fn visit_arrow_function_expression(&mut self, _: &ArrowFunctionExpression<'a>) {}
4622 fn visit_class(&mut self, _: &oxc_ast::ast::Class<'a>) {}
4623 }
4624 let mut finder = Finder::default();
4625 finder.visit_statement(statement);
4626 finder.0
4627}
4628
4629struct Writes<'u> {
4634 unstable: &'u Unstable,
4636 assigned: Vec<String>,
4637 changed: Vec<(String, Write)>,
4638 aliases: Vec<(String, String)>,
4640}
4641
4642impl Write {
4643 fn edit(&self) -> Edit {
4645 self.key
4646 .clone()
4647 .map_or(Edit::Taint, |key| Edit::Set(key, Value::Unknown))
4648 }
4649}
4650
4651impl<'u> Writes<'u> {
4652 fn new(unstable: &'u Unstable) -> Self {
4653 Self {
4654 unstable,
4655 assigned: Vec::new(),
4656 changed: Vec::new(),
4657 aliases: Vec::new(),
4658 }
4659 }
4660
4661 fn of_statement(statement: &Statement<'_>, unstable: &'u Unstable) -> Self {
4662 let mut writes = Self::new(unstable);
4663 writes.visit_statement(statement);
4664 writes.through_aliases()
4665 }
4666
4667 fn of_expression(expression: &Expression<'_>, unstable: &'u Unstable) -> Self {
4668 let mut writes = Self::new(unstable);
4669 writes.visit_expression(expression);
4670 writes.through_aliases()
4671 }
4672
4673 fn through_aliases(mut self) -> Self {
4676 let mut index = 0;
4677 while let Some((name, _)) = self.changed.get(index).cloned() {
4678 for (alias, root) in &self.aliases {
4679 let whole = |(changed, write): &(String, Write)| changed == root && *write == Write::default();
4680 if *alias == name && !self.changed.iter().any(whole) {
4681 self.changed.push((root.clone(), Write::default()));
4682 }
4683 }
4684 index += 1;
4685 }
4686 self
4687 }
4688
4689 fn of_effect(expression: &Expression<'_>, unstable: &'u Unstable) -> Self {
4693 let mut writes = Self::new(unstable);
4694 match expression.get_inner_expression() {
4695 Expression::AssignmentExpression(a) => {
4696 match a.left.as_simple_assignment_target() {
4697 Some(target) => writes.visit_simple_assignment_target(target),
4698 None => writes.visit_assignment_target(&a.left),
4699 }
4700 writes.visit_expression(&a.right);
4701 }
4702 Expression::CallExpression(c) => {
4703 writes.visit_call_expression(c);
4704 writes.arguments(c.span, &c.arguments, true);
4705 }
4706 Expression::AwaitExpression(a) => return Self::of_effect(&a.argument, unstable),
4707 other => writes.visit_expression(other),
4708 }
4709 writes
4710 }
4711
4712 fn object(&mut self, expr: &Expression<'_>) {
4715 if let Some((root, keys, _)) = reach(expr) {
4716 return self.change(root.name.as_str(), Write { keys, key: None });
4717 }
4718 let unstable = self.unstable;
4719 for (name, keys, _) in unstable.parts_of(expr) {
4720 self.change(
4721 name,
4722 Write {
4723 keys: keys.clone(),
4724 key: None,
4725 },
4726 );
4727 }
4728 }
4729
4730 fn change(&mut self, name: &str, write: Write) {
4732 let change = (name.to_owned(), write);
4733 if !self.changed.contains(&change) {
4734 self.changed.push(change);
4735 }
4736 }
4737
4738 fn arguments(&mut self, span: Span, arguments: &[Argument<'_>], statement: bool) {
4742 if !self.unstable.changes_arguments(span, statement) {
4743 return;
4744 }
4745 for argument in arguments.iter().filter_map(given) {
4746 for (root, keys) in given_parts(argument) {
4747 if self.unstable.seen_after(root) {
4748 self.change(root.name.as_str(), Write { keys, key: None });
4749 }
4750 }
4751 }
4752 }
4753}
4754
4755impl<'a> Visit<'a> for Writes<'_> {
4756 fn visit_variable_declarator(&mut self, it: &VariableDeclarator<'a>) {
4757 if let Some((root, _)) = it.init.as_ref().and_then(|init| alias_source(init)) {
4758 for (id, _) in alias_names(&it.id).unwrap_or_default() {
4759 self.aliases
4760 .push((id.name.as_str().to_owned(), root.name.as_str().to_owned()));
4761 }
4762 }
4763 walk::walk_variable_declarator(self, it);
4764 }
4765
4766 fn visit_simple_assignment_target(&mut self, it: &SimpleAssignmentTarget<'a>) {
4767 match it {
4768 SimpleAssignmentTarget::AssignmentTargetIdentifier(id) => self.assigned.push(id.name.as_str().to_owned()),
4769 SimpleAssignmentTarget::StaticMemberExpression(m) => match reach(&m.object) {
4770 Some((root, keys, true)) => {
4772 let key = Some(m.property.name.as_str().to_owned());
4773 self.change(root.name.as_str(), Write { keys, key });
4774 }
4775 Some(_) => self.object(&m.object),
4776 None => {
4778 let unstable = self.unstable;
4779 for (name, keys, exact) in unstable.parts_of(&m.object) {
4780 let key = exact.then(|| m.property.name.as_str().to_owned());
4781 self.change(
4782 name,
4783 Write {
4784 keys: keys.clone(),
4785 key,
4786 },
4787 );
4788 }
4789 }
4790 },
4791 other => {
4792 if let Some(member) = other.as_member_expression() {
4793 self.object(member.object());
4794 }
4795 }
4796 }
4797 walk::walk_simple_assignment_target(self, it);
4798 }
4799
4800 fn visit_assignment_target_property_identifier(&mut self, it: &AssignmentTargetPropertyIdentifier<'a>) {
4801 self.assigned.push(it.binding.name.as_str().to_owned());
4802 walk::walk_assignment_target_property_identifier(self, it);
4803 }
4804
4805 fn visit_unary_expression(&mut self, it: &UnaryExpression<'a>) {
4806 if it.operator == UnaryOperator::Delete
4807 && let Some(member) = it.argument.get_inner_expression().as_member_expression()
4808 {
4809 self.object(member.object());
4810 }
4811 walk::walk_unary_expression(self, it);
4812 }
4813
4814 fn visit_call_expression(&mut self, it: &CallExpression<'a>) {
4815 if let Expression::Identifier(callee) = it.callee.get_inner_expression()
4816 && self.unstable.calls_value(it)
4817 {
4818 self.change(callee.name.as_str(), Write::default());
4819 }
4820 if let Some(target) = changed_by_call(it, self.unstable.is_pure(it), self.unstable.calls_back(it))
4821 && !self.unstable.is_namespace(target)
4822 {
4823 match (property_defined(it), reach(target)) {
4824 (Some(key), Some((root, keys, true))) => {
4825 self.change(root.name.as_str(), Write { keys, key: Some(key) });
4826 }
4827 _ => self.object(target),
4828 }
4829 }
4830 self.arguments(it.span, &it.arguments, false);
4831 walk::walk_call_expression(self, it);
4832 }
4833
4834 fn visit_new_expression(&mut self, it: &NewExpression<'a>) {
4835 self.arguments(it.span, &it.arguments, false);
4836 walk::walk_new_expression(self, it);
4837 }
4838
4839 fn visit_expression_statement(&mut self, it: &ExpressionStatement<'a>) {
4840 if let Some(call) = statement_call(&it.expression) {
4841 self.arguments(call.span, &call.arguments, true);
4842 }
4843 walk::walk_expression_statement(self, it);
4844 }
4845
4846 fn visit_with_statement(&mut self, it: &oxc_ast::ast::WithStatement<'a>) {
4847 self.object(&it.object);
4849 walk::walk_with_statement(self, it);
4850 }
4851
4852 fn visit_function(&mut self, _: &Function<'a>, _: oxc_semantic::ScopeFlags) {}
4853 fn visit_arrow_function_expression(&mut self, _: &ArrowFunctionExpression<'a>) {}
4854 fn visit_class(&mut self, _: &oxc_ast::ast::Class<'a>) {}
4855}
4856
4857#[derive(Default)]
4859struct Vars(Vec<String>);
4860
4861impl<'a> Visit<'a> for Vars {
4862 fn visit_variable_declaration(&mut self, it: &VariableDeclaration<'a>) {
4863 if it.kind == VariableDeclarationKind::Var {
4864 for declarator in &it.declarations {
4865 self.0.extend(
4866 declarator
4867 .id
4868 .get_binding_identifiers()
4869 .iter()
4870 .map(|id| id.name.as_str().to_owned()),
4871 );
4872 }
4873 }
4874 walk::walk_variable_declaration(self, it);
4875 }
4876
4877 fn visit_function(&mut self, _: &Function<'a>, _: oxc_semantic::ScopeFlags) {}
4878 fn visit_arrow_function_expression(&mut self, _: &ArrowFunctionExpression<'a>) {}
4879 fn visit_class(&mut self, _: &oxc_ast::ast::Class<'a>) {}
4880}
4881
4882fn changed(value: Value) -> Value {
4885 match value {
4886 Value::Object(mut entries) => {
4887 open_entries(&mut entries);
4888 Value::Object(entries)
4889 }
4890 Value::Array(mut items) => {
4891 items.push(Value::Unknown);
4892 Value::Array(items)
4893 }
4894 Value::Maybe(value) => Value::Maybe(Box::new(changed(*value))),
4895 _ => Value::Unknown,
4896 }
4897}
4898
4899fn unstable(value: Value, change: &Change) -> Value {
4905 let Change::Contents(writes) = change else {
4906 return changed(value);
4907 };
4908 let mut value = value;
4909 for write in writes {
4910 edit_at(&mut value, &write.keys, &write.edit());
4911 }
4912 value
4913}
4914
4915fn left_is_value(operator: LogicalOperator, left: &Value) -> Option<bool> {
4917 match operator {
4918 LogicalOperator::And => truthiness(left).map(|truthy| !truthy),
4919 LogicalOperator::Or => truthiness(left),
4920 LogicalOperator::Coalesce => match left {
4921 Value::Unknown | Value::OneOf(_) | Value::Maybe(_) => None,
4922 Value::Null | Value::Undefined => Some(false),
4923 _ => Some(true),
4924 },
4925 }
4926}
4927
4928fn is_primitive(expr: &Expression<'_>) -> bool {
4931 match expr.get_inner_expression() {
4932 Expression::BinaryExpression(binary) => {
4933 binary.operator.is_equality() || binary.operator.is_compare() || binary.operator.is_relational()
4934 }
4935 Expression::UnaryExpression(unary) => matches!(unary.operator, UnaryOperator::LogicalNot | UnaryOperator::Void),
4936 _ => false,
4937 }
4938}
4939
4940fn is_void(expr: &Expression<'_>) -> bool {
4942 matches!(expr.get_inner_expression(), Expression::UnaryExpression(unary) if unary.operator == UnaryOperator::Void)
4943}
4944
4945fn spread_entries(value: Value) -> Option<Vec<(String, Value)>> {
4948 match value {
4949 Value::Object(entries) => Some(entries),
4950 Value::Null | Value::Undefined | Value::Bool(_) | Value::Number(_) => Some(Vec::new()),
4951 Value::String(s) => s.is_empty().then(Vec::new),
4952 Value::Array(_) | Value::Regex { .. } | Value::Unknown | Value::OneOf(_) | Value::Maybe(_) => None,
4953 }
4954}
4955
4956fn entry_value(entries: &[(String, Value)], key: &str) -> Option<Value> {
4959 entries
4960 .iter()
4961 .rev()
4962 .find(|(name, _)| name == key || ConfigValue::is_spread_key(name))
4963 .map(|(name, value)| if name == key { value.clone() } else { Value::Unknown })
4964}
4965
4966fn length_of(value: &Value) -> Value {
4969 let length = match value {
4970 Value::String(text) => Some(text.encode_utf16().count()),
4971 Value::Array(items) if !items.contains(&Value::Unknown) => Some(items.len()),
4972 _ => None,
4973 };
4974 length
4975 .and_then(|length| u32::try_from(length).ok())
4976 .map_or(Value::Unknown, |length| Value::Number(f64::from(length)))
4977}
4978
4979fn property_of(value: &Value, key: &str) -> Value {
4984 let definite = value.is_definite();
4985 match (value.definite(), key) {
4986 (_, "length") if !definite => Value::Unknown,
4987 (known @ (Value::String(_) | Value::Array(_)), "length") => length_of(known),
4988 (Value::Object(entries), _) => match entry_value(entries, key) {
4989 Some(field) => Value::doubtful(field, definite),
4990 None if inherits(entries, key) => Value::Unknown,
4991 None => Value::Undefined,
4992 },
4993 (Value::Array(items), _) if definite => {
4994 array_index(key).map_or(Value::Unknown, |index| element_at(items, index))
4995 }
4996 _ => Value::Unknown,
4997 }
4998}
4999
5000fn array_index(key: &str) -> Option<usize> {
5002 let index: u32 = key.parse().ok()?;
5003 (index.to_string() == key)
5004 .then(|| usize::try_from(index).ok())
5005 .flatten()
5006}
5007
5008fn element_at(items: &[Value], index: usize) -> Value {
5011 if items
5012 .iter()
5013 .take(index.saturating_add(1))
5014 .any(|item| *item == Value::Unknown)
5015 {
5016 return Value::Unknown;
5017 }
5018 items.get(index).cloned().unwrap_or(Value::Undefined)
5019}
5020
5021fn entry_pair(pair: Value) -> Option<(String, Value)> {
5025 let Value::Array(parts) = pair else {
5026 return None;
5027 };
5028 let mut parts = parts.into_iter();
5029 let key = primitive_text(&parts.next()?)?;
5030 Some((key, parts.next().unwrap_or(Value::Undefined)))
5031}
5032
5033fn primitive_text(value: &Value) -> Option<String> {
5037 match value {
5038 Value::String(text) => Some(text.clone()),
5039 Value::Number(number) => number_text(*number),
5040 Value::Bool(b) => Some(b.to_string()),
5041 _ => None,
5042 }
5043}
5044
5045fn number_text(number: f64) -> Option<String> {
5049 if number == 0.0 {
5050 return Some("0".to_owned());
5052 }
5053 (1e-6..1e21).contains(&number.abs()).then(|| format!("{number}"))
5054}
5055
5056fn add(left: &Value, right: &Value) -> Value {
5059 match (left, right) {
5060 (Value::Number(a), Value::Number(b)) => Value::Number(a + b),
5061 (Value::String(_), _) | (_, Value::String(_)) => match (primitive_text(left), primitive_text(right)) {
5062 (Some(a), Some(b)) => Value::String(a + &b),
5063 _ => Value::Unknown,
5064 },
5065 _ => Value::Unknown,
5066 }
5067}
5068
5069fn strictly_equal(left: &Value, right: &Value) -> Option<bool> {
5072 let primitive = |value: &Value| {
5073 matches!(
5074 value,
5075 Value::Null | Value::Undefined | Value::Bool(_) | Value::Number(_) | Value::String(_)
5076 )
5077 };
5078 (primitive(left) && primitive(right)).then(|| left == right)
5079}
5080
5081fn inherits(entries: &[(String, Value)], key: &str) -> bool {
5084 OBJECT_PROTOTYPE_MEMBERS.contains(&key) || entries.iter().any(|(name, _)| name == "__proto__")
5085}
5086
5087fn spread_any(before: &[(String, Value)], branches: &[Vec<(String, Value)>]) -> Vec<(String, Value)> {
5089 let objects: Vec<Option<&[(String, Value)]>> = branches.iter().map(|branch| Some(branch.as_slice())).collect();
5090 any_entries(&objects, before)
5091}
5092
5093fn any_entries(objects: &[Option<&[(String, Value)]>], before: &[(String, Value)]) -> Vec<(String, Value)> {
5098 let entries = || objects.iter().flatten().copied().flatten();
5099 let mut keys: Vec<&str> = Vec::new();
5100 for (key, _) in entries() {
5101 if !ConfigValue::is_spread_key(key) && !keys.contains(&key.as_str()) {
5102 keys.push(key);
5103 }
5104 }
5105 let mut out: Vec<(String, Value)> = Vec::new();
5106 for key in keys {
5107 let fields: Vec<Option<(Value, bool)>> = objects
5108 .iter()
5109 .map(|object| {
5110 object
5111 .and_then(|object| entry_value(object, key))
5112 .or_else(|| entry_value(before, key))
5113 .map(Value::split)
5114 })
5115 .collect();
5116 let each_definite = fields
5117 .iter()
5118 .all(|field| field.as_ref().is_some_and(|(_, definite)| *definite));
5119 let values: Vec<Option<Value>> = fields.into_iter().map(|field| field.map(|(value, _)| value)).collect();
5120 let definite = each_definite && agree(&values);
5121 out.push((key.to_owned(), Value::doubtful(any_of(&values), definite)));
5122 }
5123 for marker in [UNKNOWN_SPREAD, OWN_SPREAD] {
5124 if entries().any(|(key, _)| key == marker) {
5125 out.push((marker.to_owned(), Value::Unknown));
5126 }
5127 }
5128 out
5129}
5130
5131fn any_of(values: &[Option<Value>]) -> Value {
5137 let present: Vec<&Value> = values.iter().flatten().collect();
5138 let Some(&first) = present.first() else {
5139 return Value::Unknown;
5140 };
5141 if agree(values) {
5142 return first.clone();
5143 }
5144 let lists: Option<Vec<&[Value]>> = present
5145 .iter()
5146 .map(|&value| match value {
5147 Value::Array(list) => Some(list.as_slice()),
5148 Value::String(_) => Some(std::slice::from_ref(value)),
5149 _ => None,
5150 })
5151 .collect();
5152 if let Some(lists) = lists
5153 && present.iter().any(|value| matches!(value, Value::Array(_)))
5154 {
5155 let mut items: Vec<Value> = Vec::new();
5156 for item in lists.into_iter().flatten() {
5157 if !items.contains(item) {
5158 items.push(item.clone());
5159 }
5160 }
5161 return Value::Array(items);
5162 }
5163 let objects: Option<Vec<Option<Entries<'_>>>> = values
5164 .iter()
5165 .map(|value| match value {
5166 None => Some(None),
5167 Some(Value::Object(entries)) => Some(Some(entries.as_slice())),
5168 Some(_) => None,
5169 })
5170 .collect();
5171 if let Some(objects) = objects {
5172 return Value::Object(any_entries(&objects, &[]));
5173 }
5174 if present.len() < values.len() {
5175 return Value::Unknown;
5176 }
5177 one_of(present.into_iter().cloned())
5178}
5179
5180const MAX_ALTERNATIVES: usize = 8;
5182
5183fn one_of(values: impl IntoIterator<Item = Value>) -> Value {
5188 let mut out: Vec<Value> = Vec::new();
5189 for value in values {
5190 let nested = match value {
5191 Value::Unknown | Value::Maybe(_) => return Value::Unknown,
5192 Value::OneOf(nested) => nested,
5193 other => vec![other],
5194 };
5195 for alternative in nested {
5196 if !out.contains(&alternative) {
5197 out.push(alternative);
5198 }
5199 }
5200 if out.len() > MAX_ALTERNATIVES {
5201 return Value::Unknown;
5202 }
5203 }
5204 match out.len() {
5205 0 => Value::Unknown,
5206 1 => out.pop().unwrap_or(Value::Unknown),
5207 _ => Value::OneOf(out),
5208 }
5209}
5210
5211fn each_combination(args: &[Value], apply: impl Fn(&[Value]) -> Value) -> Value {
5215 if !args.iter().any(|arg| matches!(arg, Value::OneOf(_))) {
5216 return apply(args);
5217 }
5218 let mut combinations: Vec<Vec<Value>> = vec![Vec::new()];
5219 for arg in args {
5220 let choices = alternatives(arg.clone());
5221 if combinations.len().saturating_mul(choices.len()) > MAX_ALTERNATIVES {
5222 return Value::Unknown;
5223 }
5224 let mut next = Vec::with_capacity(combinations.len() * choices.len());
5225 for combination in &combinations {
5226 for choice in &choices {
5227 let mut extended = combination.clone();
5228 extended.push(choice.clone());
5229 next.push(extended);
5230 }
5231 }
5232 combinations = next;
5233 }
5234 one_of(combinations.iter().map(|combination| apply(combination.as_slice())))
5235}
5236
5237fn alternatives(value: Value) -> Vec<Value> {
5239 match value {
5240 Value::OneOf(alternatives) => alternatives,
5241 other => vec![other],
5242 }
5243}
5244
5245fn truthy_part(value: Value) -> Option<(Value, bool)> {
5248 match truthiness(&value) {
5249 Some(false) => None,
5250 Some(true) => Some((value, false)),
5251 None => match value {
5252 Value::OneOf(alternatives) => {
5253 let count = alternatives.len();
5254 let kept: Vec<Value> = alternatives
5255 .into_iter()
5256 .filter(|alternative| truthiness(alternative) != Some(false))
5257 .collect();
5258 let may_drop = kept.len() < count || kept.iter().any(|alternative| truthiness(alternative).is_none());
5259 (!kept.is_empty()).then(|| (one_of(kept), may_drop))
5260 }
5261 other => Some((other, true)),
5262 },
5263 }
5264}
5265
5266fn agree(values: &[Option<Value>]) -> bool {
5268 values
5269 .first()
5270 .is_some_and(|first| first.is_some() && values.iter().all(|value| value == first))
5271}
5272
5273fn truthiness(value: &Value) -> Option<bool> {
5275 match value {
5276 Value::Null | Value::Undefined => Some(false),
5277 Value::Bool(b) => Some(*b),
5278 Value::Number(n) => Some(*n != 0.0 && !n.is_nan()),
5279 Value::String(s) => Some(!s.is_empty()),
5280 Value::Array(_) | Value::Object(_) | Value::Regex { .. } => Some(true),
5281 Value::Unknown | Value::OneOf(_) | Value::Maybe(_) => None,
5282 }
5283}
5284
5285fn either(a: &Value, b: &Value) -> Value {
5292 if a == b {
5293 return a.clone();
5294 }
5295 if !a.is_definite() || !b.is_definite() {
5296 return Value::doubtful(either(a.definite(), b.definite()), false);
5297 }
5298 let (Value::Object(left), Value::Object(right)) = (a, b) else {
5299 return one_of([a.clone(), b.clone()]);
5300 };
5301 let mut keys: Vec<&str> = Vec::new();
5302 for (key, _) in left.iter().chain(right) {
5303 if !ConfigValue::is_spread_key(key) && !keys.contains(&key.as_str()) {
5304 keys.push(key);
5305 }
5306 }
5307 let mut out: Vec<(String, Value)> = keys
5308 .into_iter()
5309 .map(|key| {
5310 let value = match (a.get(key), b.get(key)) {
5311 (Some(x), Some(y)) => either(x, y),
5312 (Some(only), None) | (None, Some(only)) => Value::doubtful(Value::Unknown, only.is_definite()),
5313 (None, None) => Value::Unknown,
5314 };
5315 (key.to_owned(), value)
5316 })
5317 .collect();
5318 for marker in [UNKNOWN_SPREAD, OWN_SPREAD] {
5319 if a.get(marker).is_some() || b.get(marker).is_some() {
5320 out.push((marker.to_owned(), Value::Unknown));
5321 }
5322 }
5323 Value::Object(out)
5324}
5325
5326fn join(a: &Value, b: &Value) -> Value {
5329 if !a.is_definite() || !b.is_definite() {
5330 return Value::doubtful(join(a.definite(), b.definite()), false);
5331 }
5332 match (a, b) {
5333 (Value::Array(x), Value::Array(y)) if x != y && (x.starts_with(y) || y.starts_with(x)) => {
5334 let mut common = if x.len() < y.len() { x.clone() } else { y.clone() };
5335 common.push(Value::Unknown);
5336 Value::Array(common)
5337 }
5338 _ => either(a, b),
5339 }
5340}
5341
5342fn flatten_into(value: Value, list: &mut Vec<Value>) {
5344 match value {
5345 Value::Array(items) => {
5346 for item in items {
5347 flatten_into(item, list);
5348 }
5349 }
5350 other => list.push(other),
5351 }
5352}
5353
5354fn single_argument<'p, 'a>(call: &'p CallExpression<'a>) -> Option<&'p Expression<'a>> {
5356 let mut arguments = call.arguments.iter();
5357 match (arguments.next(), arguments.next()) {
5358 (Some(only), None) => only.as_expression(),
5359 _ => None,
5360 }
5361}
5362
5363fn required_package(call: &CallExpression<'_>, depth: u32) -> Option<Value> {
5367 if depth < 2 {
5368 return None;
5369 }
5370 let call = match call.callee.get_inner_expression() {
5371 Expression::CallExpression(factory) => &**factory,
5372 _ => call,
5373 };
5374 let Expression::Identifier(callee) = call.callee.get_inner_expression() else {
5375 return None;
5376 };
5377 if callee.name.as_str() != "require" {
5378 return None;
5379 }
5380 let module = single_argument(call).and_then(static_string)?;
5381 (!module.starts_with(['.', '/'])).then_some(Value::String(module))
5382}
5383
5384fn manifest_directory(dir: &str) -> Option<Value> {
5389 if dir.starts_with('.') {
5390 return None;
5391 }
5392 let package = !dir.starts_with(['/', '#'])
5393 && !dir.contains([':', '\\'])
5394 && dir.split('/').all(|segment| !matches!(segment, "" | "." | ".."));
5395 Some(if package {
5396 Value::String(dir.to_owned())
5397 } else {
5398 Value::Unknown
5399 })
5400}
5401
5402fn is_module_resolve(call: &CallExpression<'_>) -> bool {
5404 is_resolve_of(call, "require")
5405 || matches!(
5406 call.callee.get_inner_expression(),
5407 Expression::StaticMemberExpression(m)
5408 if m.property.name.as_str() == "resolve"
5409 && matches!(m.object.get_inner_expression(), Expression::ImportMeta(_))
5410 )
5411}
5412
5413fn is_resolve_of(call: &CallExpression<'_>, object: &str) -> bool {
5415 matches!(
5416 call.callee.get_inner_expression(),
5417 Expression::StaticMemberExpression(m)
5418 if m.property.name.as_str() == "resolve"
5419 && matches!(m.object.get_inner_expression(), Expression::Identifier(o) if o.name.as_str() == object)
5420 )
5421}
5422
5423fn chain_object<'b, 'a>(chain: &'b ChainExpression<'a>) -> Option<&'b Expression<'a>> {
5425 match &chain.expression {
5426 ChainElement::CallExpression(call) => Some(&call.callee),
5427 ChainElement::TSNonNullExpression(e) => Some(&e.expression),
5428 element => element.as_member_expression().map(MemberExpression::object),
5429 }
5430}