1use std::{borrow::Cow, collections::BTreeMap, fmt::Display, ops::Deref};
2
3use anyhow::{Result, bail};
4use bincode::{Decode, Encode};
5use rustc_hash::FxHashMap;
6use serde::{Deserialize, Serialize};
7use serde_json::Value;
8use turbo_rcstr::{RcStr, rcstr};
9use turbo_tasks::FxIndexMap;
10
11use crate::resolve::{
12 alias_map::{AliasKey, AliasMap, AliasMapIter, AliasPattern, AliasTemplate},
13 options::ConditionValue,
14 pattern::Pattern,
15};
16
17#[derive(Copy, Clone)]
19pub(crate) enum ExportImport {
20 Export,
21 Import,
22}
23
24impl Display for ExportImport {
25 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
26 match self {
27 Self::Export => f.write_str("export"),
28 Self::Import => f.write_str("import"),
29 }
30 }
31}
32
33#[derive(Clone, PartialEq, Eq, Hash, Debug, Serialize, Deserialize)]
37pub enum SubpathValue {
38 Alternatives(Vec<SubpathValue>),
43
44 Conditional(Vec<(RcStr, SubpathValue)>),
53
54 Result(RcStr),
57
58 Excluded,
61
62 Empty,
66}
67
68#[derive(Clone, PartialEq, Eq, Hash, Debug)]
71pub enum ReplacedSubpathValue {
72 Alternatives(Vec<ReplacedSubpathValue>),
73 Conditional(Vec<(RcStr, ReplacedSubpathValue)>),
74 Result(Pattern),
75 Excluded,
76 Empty,
77}
78
79impl AliasTemplate for SubpathValue {
80 type Output<'a>
81 = ReplacedSubpathValue
82 where
83 Self: 'a;
84
85 fn convert(&self) -> ReplacedSubpathValue {
86 match self {
87 SubpathValue::Alternatives(list) => ReplacedSubpathValue::Alternatives(
88 list.iter()
89 .map(|value: &SubpathValue| value.convert())
90 .collect::<Vec<_>>(),
91 ),
92 SubpathValue::Conditional(list) => ReplacedSubpathValue::Conditional(
93 list.iter()
94 .map(|(condition, value)| (condition.clone(), value.convert()))
95 .collect::<Vec<_>>(),
96 ),
97 SubpathValue::Result(value) => ReplacedSubpathValue::Result(value.clone().into()),
98 SubpathValue::Excluded => ReplacedSubpathValue::Excluded,
99 SubpathValue::Empty => ReplacedSubpathValue::Empty,
100 }
101 }
102
103 fn replace(&self, capture: &Pattern) -> ReplacedSubpathValue {
104 match self {
105 SubpathValue::Alternatives(list) => ReplacedSubpathValue::Alternatives(
106 list.iter()
107 .map(|value: &SubpathValue| value.replace(capture))
108 .collect::<Vec<_>>(),
109 ),
110 SubpathValue::Conditional(list) => ReplacedSubpathValue::Conditional(
111 list.iter()
112 .map(|(condition, value)| (condition.clone(), value.replace(capture)))
113 .collect::<Vec<_>>(),
114 ),
115 SubpathValue::Result(value) => {
116 ReplacedSubpathValue::Result(capture.spread_into_star(value))
117 }
118 SubpathValue::Excluded => ReplacedSubpathValue::Excluded,
119 SubpathValue::Empty => ReplacedSubpathValue::Empty,
120 }
121 }
122}
123
124impl SubpathValue {
125 fn results_mut(&mut self) -> ResultsIterMut<'_> {
127 ResultsIterMut { stack: vec![self] }
128 }
129
130 fn try_new(value: &Value, ty: ExportImport) -> Result<Self> {
131 match value {
132 Value::Null => Ok(SubpathValue::Excluded),
133 Value::String(s) => Ok(SubpathValue::Result(s.as_str().into())),
134 Value::Number(_) => bail!("numeric values are invalid in {ty}s field entries"),
135 Value::Bool(false) => Ok(SubpathValue::Empty),
136 Value::Bool(true) => bail!("boolean true is invalid in {ty}s field entries"),
137 Value::Object(object) => Ok(SubpathValue::Conditional(
138 object
139 .iter()
140 .map(|(key, value)| {
141 if key.starts_with('.') {
142 bail!(
143 "invalid key \"{}\" in an {ty} field conditions object. Did you \
144 mean to place this request at a higher level?",
145 key
146 );
147 }
148
149 Ok((key.as_str().into(), SubpathValue::try_new(value, ty)?))
150 })
151 .collect::<Result<Vec<_>>>()?,
152 )),
153 Value::Array(array) => Ok(SubpathValue::Alternatives(
154 array
155 .iter()
156 .map(|value| SubpathValue::try_new(value, ty))
157 .collect::<Result<Vec<_>>>()?,
158 )),
159 }
160 }
161}
162
163#[derive(Clone, PartialEq, Eq, Hash, Debug)]
165pub enum ReplacedSubpathValueResultType {
166 Path(Pattern),
168 Empty,
170}
171
172pub struct ReplacedSubpathValueResult<'a, 'b> {
173 pub ty: ReplacedSubpathValueResultType,
174 pub conditions: Vec<(RcStr, bool)>,
175 pub map_prefix: Cow<'a, str>,
176 pub map_key: &'b AliasKey,
177}
178
179#[derive(Clone, Copy, PartialEq, Eq)]
183pub enum TerminalState {
184 Unset,
186 Result,
189 Stop,
193}
194
195impl ReplacedSubpathValue {
196 pub fn add_results<'a, 'b>(
214 self,
215 prefix: Cow<'a, str>,
216 key: &'b AliasKey,
217 conditions: &BTreeMap<RcStr, ConditionValue>,
218 unspecified_condition: &ConditionValue,
219 condition_overrides: &mut FxHashMap<RcStr, ConditionValue>,
220 target: &mut Vec<ReplacedSubpathValueResult<'a, 'b>>,
221 ) -> TerminalState {
222 match self {
223 ReplacedSubpathValue::Alternatives(list) => {
224 for value in list {
225 if value.add_results(
226 prefix.clone(),
227 key,
228 conditions,
229 unspecified_condition,
230 condition_overrides,
231 target,
232 ) == TerminalState::Stop
233 {
234 return TerminalState::Stop;
235 }
236 }
237 TerminalState::Result
240 }
241 ReplacedSubpathValue::Conditional(list) => {
242 for (condition, value) in list {
243 let condition_value = if condition == "default" {
244 &ConditionValue::Set
245 } else {
246 condition_overrides
247 .get(condition.as_str())
248 .or_else(|| conditions.get(&condition))
249 .unwrap_or(unspecified_condition)
250 };
251 match condition_value {
252 ConditionValue::Set => {
253 match value.add_results(
254 prefix.clone(),
255 key,
256 conditions,
257 unspecified_condition,
258 condition_overrides,
259 target,
260 ) {
261 TerminalState::Stop => return TerminalState::Stop,
262 TerminalState::Result => return TerminalState::Result,
263 TerminalState::Unset => {}
264 }
265 }
266 ConditionValue::Unset => {}
267 ConditionValue::Unknown => {
268 condition_overrides.insert(condition.clone(), ConditionValue::Set);
274 let inner = value.add_results(
275 prefix.clone(),
276 key,
277 conditions,
278 unspecified_condition,
279 condition_overrides,
280 target,
281 );
282 if inner != TerminalState::Unset {
283 condition_overrides.insert(condition, ConditionValue::Unset);
284 } else {
285 condition_overrides.remove(condition.as_str());
286 }
287 }
289 }
290 }
291 TerminalState::Unset
292 }
293 ReplacedSubpathValue::Result(r) => {
294 target.push(ReplacedSubpathValueResult {
295 ty: ReplacedSubpathValueResultType::Path(r),
296 conditions: collect_active_conditions(condition_overrides),
297 map_prefix: prefix,
298 map_key: key,
299 });
300 TerminalState::Result
301 }
302 ReplacedSubpathValue::Excluded => TerminalState::Stop,
304 ReplacedSubpathValue::Empty => {
305 target.push(ReplacedSubpathValueResult {
308 ty: ReplacedSubpathValueResultType::Empty,
309 conditions: collect_active_conditions(condition_overrides),
310 map_prefix: prefix,
311 map_key: key,
312 });
313 TerminalState::Stop
314 }
315 }
316 }
317}
318
319fn collect_active_conditions(
322 condition_overrides: &FxHashMap<RcStr, ConditionValue>,
323) -> Vec<(RcStr, bool)> {
324 condition_overrides
325 .iter()
326 .filter_map(|(k, v)| match v {
327 ConditionValue::Set => Some((k.clone(), true)),
328 ConditionValue::Unset => Some((k.clone(), false)),
329 ConditionValue::Unknown => None,
330 })
331 .collect()
332}
333
334struct ResultsIterMut<'a> {
335 stack: Vec<&'a mut SubpathValue>,
336}
337
338impl<'a> Iterator for ResultsIterMut<'a> {
339 type Item = &'a mut RcStr;
340
341 fn next(&mut self) -> Option<Self::Item> {
342 while let Some(value) = self.stack.pop() {
343 match value {
344 SubpathValue::Alternatives(list) => {
345 for value in list {
346 self.stack.push(value);
347 }
348 }
349 SubpathValue::Conditional(list) => {
350 for (_, value) in list {
351 self.stack.push(value);
352 }
353 }
354 SubpathValue::Result(r) => return Some(r),
355 SubpathValue::Excluded | SubpathValue::Empty => {}
356 }
357 }
358 None
359 }
360}
361
362#[derive(PartialEq, Eq, Encode, Decode)]
364pub struct ExportsField(AliasMap<SubpathValue>);
365
366impl TryFrom<&Value> for ExportsField {
367 type Error = anyhow::Error;
368
369 fn try_from(value: &Value) -> Result<Self> {
370 let map = match value {
373 Value::Object(object) => {
374 let mut map = AliasMap::new();
375 let mut conditions = vec![];
378
379 for (key, value) in object.iter() {
380 if key != "." && !key.starts_with("./") {
383 conditions.push((key, value));
384 continue;
385 }
386
387 let mut value = SubpathValue::try_new(value, ExportImport::Export)?;
388
389 let pattern = if is_folder_shorthand(key) {
390 expand_folder_shorthand(key, &mut value)?
391 } else {
392 AliasPattern::parse(key.as_str())
393 };
394
395 map.insert(pattern, value);
396 }
397
398 if !conditions.is_empty() {
399 map.insert(
400 AliasPattern::Exact(rcstr!(".")),
401 SubpathValue::Conditional(
402 conditions
403 .into_iter()
404 .map(|(key, value)| {
405 Ok((
406 key.as_str().into(),
407 SubpathValue::try_new(value, ExportImport::Export)?,
408 ))
409 })
410 .collect::<Result<Vec<_>>>()?,
411 ),
412 );
413 }
414
415 map
416 }
417 Value::String(string) => {
418 let mut map = AliasMap::new();
419 map.insert(
420 AliasPattern::Exact(rcstr!(".")),
421 SubpathValue::Result(string.as_str().into()),
422 );
423 map
424 }
425 Value::Array(array) => {
426 let mut map = AliasMap::new();
427 map.insert(
428 AliasPattern::Exact(rcstr!(".")),
429 SubpathValue::Alternatives(
433 array
434 .iter()
435 .map(|value| SubpathValue::try_new(value, ExportImport::Export))
436 .collect::<Result<Vec<_>>>()?,
437 ),
438 );
439 map
440 }
441 _ => {
442 bail!("\"exports\" field must be an object or a string");
443 }
444 };
445 Ok(Self(map))
446 }
447}
448
449impl Deref for ExportsField {
450 type Target = AliasMap<SubpathValue>;
451 fn deref(&self) -> &Self::Target {
452 &self.0
453 }
454}
455
456#[derive(PartialEq, Eq, Encode, Decode)]
458pub struct ImportsField(AliasMap<SubpathValue>);
459
460impl TryFrom<&Value> for ImportsField {
461 type Error = anyhow::Error;
462
463 fn try_from(value: &Value) -> Result<Self> {
464 let map = match value {
467 Value::Object(object) => {
468 let mut map = AliasMap::new();
469
470 for (key, value) in object.iter() {
471 if !key.starts_with('#') {
472 bail!("imports key \"{key}\" must begin with a '#'")
473 }
474 let value = SubpathValue::try_new(value, ExportImport::Import)?;
475 map.insert(AliasPattern::parse(key.as_str()), value);
476 }
477
478 map
479 }
480 _ => bail!("\"imports\" field must be an object"),
481 };
482 Ok(Self(map))
483 }
484}
485
486impl Deref for ImportsField {
487 type Target = AliasMap<SubpathValue>;
488 fn deref(&self) -> &Self::Target {
489 &self.0
490 }
491}
492
493fn is_folder_shorthand(key: &str) -> bool {
495 key.ends_with('/') && key.find('*').is_none()
496}
497
498fn expand_folder_shorthand(key: &str, value: &mut SubpathValue) -> Result<AliasPattern> {
506 let pattern = AliasPattern::wildcard(key, rcstr!(""));
508
509 for result in value.results_mut() {
511 if result.ends_with('/') {
512 if result.find('*').is_none() {
513 let mut buf = result.to_string();
514 buf.push('*');
515 *result = buf.into();
516 } else {
517 bail!(
518 "invalid exports field value \"{}\" for key \"{}\": \"*\" is not allowed in \
519 folder exports",
520 result,
521 key
522 );
523 }
524 } else {
525 bail!(
526 "invalid exports field value \"{}\" for key \"{}\": folder exports must end with \
527 \"/\"",
528 result,
529 key
530 );
531 }
532 }
533
534 Ok(pattern)
535}
536
537#[turbo_tasks::value(shared)]
539#[derive(Default)]
540pub struct ResolveAliasMap(#[turbo_tasks(unsafe_ignore)] AliasMap<SubpathValue>);
541
542impl TryFrom<&FxIndexMap<RcStr, Value>> for ResolveAliasMap {
543 type Error = anyhow::Error;
544
545 fn try_from(object: &FxIndexMap<RcStr, Value>) -> Result<Self> {
546 let mut map = AliasMap::new();
547
548 for (key, value) in object.iter() {
549 let mut value = SubpathValue::try_new(value, ExportImport::Export)?;
550
551 let pattern = if is_folder_shorthand(key) {
552 expand_folder_shorthand(key, &mut value)?
553 } else {
554 AliasPattern::parse(key.as_str())
555 };
556
557 map.insert(pattern, value);
558 }
559 Ok(Self(map))
560 }
561}
562
563impl<'a> IntoIterator for &'a ResolveAliasMap {
564 type Item = (AliasPattern, &'a SubpathValue);
565 type IntoIter = AliasMapIter<'a, SubpathValue>;
566
567 fn into_iter(self) -> Self::IntoIter {
568 (&self.0).into_iter()
569 }
570}