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