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