Skip to main content

turbopack_core/source_map/
mod.rs

1use std::{
2    borrow::Cow,
3    io::Write,
4    ops::Deref,
5    sync::{Arc, LazyLock},
6};
7
8use anyhow::Result;
9use bincode::{
10    Decode, Encode,
11    de::Decoder,
12    enc::Encoder,
13    error::{DecodeError, EncodeError},
14};
15use bytes_str::BytesStr;
16use either::Either;
17use ref_cast::RefCast;
18use regex::Regex;
19use swc_sourcemap::{DecodedMap, SourceMap as RegularMap, SourceMapBuilder, SourceMapIndex};
20use turbo_rcstr::{RcStr, rcstr};
21use turbo_tasks::{ResolvedVc, TryJoinIterExt, ValueToStringRef, Vc};
22use turbo_tasks_fs::{
23    File, FileContent, FileSystem, FileSystemPath, VirtualFileSystem,
24    rope::{Rope, RopeBuilder},
25};
26
27use crate::{
28    SOURCE_URL_PROTOCOL, asset::AssetContent, source::Source,
29    source_map::utils::add_default_ignore_list, source_pos::SourcePos,
30    virtual_source::VirtualSource,
31};
32
33pub(crate) mod source_map_asset;
34pub mod utils;
35
36pub use source_map_asset::SourceMapAsset;
37
38/// Represents an empty value in a u32 variable in the sourcemap crate.
39static SOURCEMAP_CRATE_NONE_U32: u32 = !0;
40
41/// Allows callers to generate source maps.
42#[turbo_tasks::value_trait]
43pub trait GenerateSourceMap {
44    /// Generates a usable source map, capable of both tracing and stringifying.
45    #[turbo_tasks::function]
46    fn generate_source_map(self: Vc<Self>) -> Vc<FileContent>;
47
48    /// Returns an individual section of the larger source map, if found.
49    #[turbo_tasks::function]
50    fn by_section(self: Vc<Self>, _section: RcStr) -> Vc<FileContent> {
51        FileContent::NotFound.cell()
52    }
53}
54
55/// Implements the source map specification as a [decoded][`DecodedMap`] sourcemap. A "decoded" map
56/// represents a source map as if it was came out of a JSON decode.
57#[turbo_tasks::value(shared, cell = "new", eq = "manual")]
58#[derive(Debug)]
59pub struct SourceMap {
60    /// A decoded source map contains no Vcs.
61    #[turbo_tasks(trace_ignore)]
62    map: Arc<CrateMapWrapper>,
63}
64impl Eq for SourceMap {}
65impl PartialEq for SourceMap {
66    fn eq(&self, other: &Self) -> bool {
67        Arc::ptr_eq(&self.map, &other.map)
68    }
69}
70
71#[turbo_tasks::value(transparent)]
72pub struct OptionSourceMap(Option<SourceMap>);
73
74#[turbo_tasks::value_impl]
75impl OptionSourceMap {
76    #[turbo_tasks::function]
77    pub fn none() -> Vc<Self> {
78        Vc::cell(None)
79    }
80}
81
82impl OptionSourceMap {
83    pub fn none_resolved() -> ResolvedVc<Self> {
84        ResolvedVc::cell(None)
85    }
86}
87
88/// A token represents a mapping in a source map.
89///
90/// It may either be Synthetic, meaning it was generated by some build tool and doesn't
91/// represent a location in a user-authored source file, or it is Original, meaning it represents a
92/// real location in source file.
93#[turbo_tasks::value]
94#[derive(Clone, Debug)]
95pub enum Token {
96    Synthetic(SyntheticToken),
97    Original(OriginalToken),
98}
99
100#[turbo_tasks::value]
101#[derive(Clone, Debug)]
102pub struct TokenWithSource {
103    pub token: Token,
104    pub source_content: Option<ResolvedVc<Box<dyn Source>>>,
105}
106
107/// A SyntheticToken represents a region of the generated file that was created
108/// by some build tool.
109#[turbo_tasks::value]
110#[derive(Clone, Debug)]
111pub struct SyntheticToken {
112    pub generated_line: u32,
113    pub generated_column: u32,
114    pub guessed_original_file: Option<RcStr>,
115}
116
117/// An OriginalToken represents a region of the generated file that exists in
118/// user-authored source file.
119#[turbo_tasks::value]
120#[derive(Clone, Debug)]
121pub struct OriginalToken {
122    pub generated_line: u32,
123    pub generated_column: u32,
124    pub original_file: RcStr,
125    pub original_line: u32,
126    pub original_column: u32,
127    pub name: Option<RcStr>,
128    /// Whether this token's source is in the sourcemap's `ignoreList`.
129    pub is_ignored: bool,
130}
131
132impl Token {
133    pub fn generated_line(&self) -> u32 {
134        match self {
135            Self::Original(t) => t.generated_line,
136            Self::Synthetic(t) => t.generated_line,
137        }
138    }
139
140    pub fn generated_column(&self) -> u32 {
141        match self {
142            Self::Original(t) => t.generated_column,
143            Self::Synthetic(t) => t.generated_column,
144        }
145    }
146
147    pub fn with_offset(&self, line_offset: u32, column_offset: u32) -> Self {
148        match self {
149            Self::Original(t) => Self::Original(OriginalToken {
150                generated_line: t.generated_line + line_offset,
151                generated_column: if t.generated_line == 0 {
152                    t.generated_column + column_offset
153                } else {
154                    t.generated_column
155                },
156                original_file: t.original_file.clone(),
157                original_line: t.original_line,
158                original_column: t.original_column,
159                name: t.name.clone(),
160                is_ignored: t.is_ignored,
161            }),
162            Self::Synthetic(t) => Self::Synthetic(SyntheticToken {
163                generated_line: t.generated_line + line_offset,
164                generated_column: if t.generated_line == 0 {
165                    t.generated_column + column_offset
166                } else {
167                    t.generated_column
168                },
169                guessed_original_file: t.guessed_original_file.clone(),
170            }),
171        }
172    }
173}
174
175impl From<swc_sourcemap::Token<'_>> for Token {
176    fn from(t: swc_sourcemap::Token) -> Self {
177        if t.has_source() {
178            Token::Original(OriginalToken {
179                generated_line: t.get_dst_line(),
180                generated_column: t.get_dst_col(),
181                original_file: RcStr::from(
182                    t.get_source()
183                        .expect("already checked token has source")
184                        .clone(),
185                ),
186                original_line: t.get_src_line(),
187                original_column: t.get_src_col(),
188                name: t.get_name().cloned().map(RcStr::from),
189                // Set to false initially; will be updated by
190                // lookup_token_and_source_internal which has access to the full
191                // source map's ignoreList.
192                is_ignored: false,
193            })
194        } else {
195            Token::Synthetic(SyntheticToken {
196                generated_line: t.get_dst_line(),
197                generated_column: t.get_dst_col(),
198                guessed_original_file: None,
199            })
200        }
201    }
202}
203
204impl TryInto<swc_sourcemap::RawToken> for Token {
205    type Error = std::num::ParseIntError;
206
207    fn try_into(self) -> Result<swc_sourcemap::RawToken, Self::Error> {
208        Ok(match self {
209            Self::Original(t) => swc_sourcemap::RawToken {
210                dst_col: t.generated_column,
211                dst_line: t.generated_line,
212                name_id: match t.name {
213                    None => SOURCEMAP_CRATE_NONE_U32,
214                    Some(name) => name.parse()?,
215                },
216                src_col: t.original_column,
217                src_line: t.original_line,
218                src_id: t.original_file.parse()?,
219                is_range: false,
220            },
221            Self::Synthetic(t) => swc_sourcemap::RawToken {
222                dst_col: t.generated_column,
223                dst_line: t.generated_line,
224                name_id: SOURCEMAP_CRATE_NONE_U32,
225                src_col: SOURCEMAP_CRATE_NONE_U32,
226                src_line: SOURCEMAP_CRATE_NONE_U32,
227                src_id: SOURCEMAP_CRATE_NONE_U32,
228                is_range: false,
229            },
230        })
231    }
232}
233
234impl SourceMap {
235    fn new_regular(map: RegularMap) -> Self {
236        Self::new_decoded(DecodedMap::Regular(map))
237    }
238
239    fn new_decoded(map: DecodedMap) -> Self {
240        SourceMap {
241            map: Arc::new(CrateMapWrapper(map)),
242        }
243    }
244
245    pub fn new_from_rope(content: &Rope) -> Result<Option<Self>> {
246        let Ok(map) = DecodedMap::from_reader(content.read()) else {
247            return Ok(None);
248        };
249        Ok(Some(SourceMap::new_decoded(map)))
250    }
251}
252
253#[turbo_tasks::value_impl]
254impl SourceMap {
255    /// This function should be used sparingly to reduce memory usage, only in cold code paths
256    /// (issue resolving, etc).
257    #[turbo_tasks::function]
258    pub async fn new_from_rope_cached(content: Vc<FileContent>) -> Result<Vc<OptionSourceMap>> {
259        let content = content.await?;
260        let Some(content) = content.as_content() else {
261            return Ok(OptionSourceMap::none());
262        };
263        Ok(Vc::cell(SourceMap::new_from_rope(content.content())?))
264    }
265}
266
267impl SourceMap {
268    pub fn to_source_map(&self) -> Arc<CrateMapWrapper> {
269        self.map.clone()
270    }
271}
272
273static EMPTY_SOURCE_MAP_ROPE: LazyLock<Rope> =
274    LazyLock::new(|| Rope::from(r#"{"version":3,"sources":[],"names":[],"mappings":"A"}"#));
275
276impl SourceMap {
277    /// A source map that contains no actual source location information (no
278    /// `sources`, no mappings that point into a source). This is used to tell
279    /// Chrome that the generated code starting at a particular offset is no
280    /// longer part of the previous section's mappings.
281    pub fn empty() -> Self {
282        let mut builder = SourceMapBuilder::new(None);
283        builder.add(0, 0, 0, 0, None, None, false);
284        SourceMap::new_regular(builder.into_sourcemap())
285    }
286
287    /// A source map that contains no actual source location information (no
288    /// `sources`, no mappings that point into a source). This is used to tell
289    /// Chrome that the generated code starting at a particular offset is no
290    /// longer part of the previous section's mappings.
291    pub fn empty_rope() -> Rope {
292        EMPTY_SOURCE_MAP_ROPE.clone()
293    }
294
295    pub fn sections_to_rope(
296        sections: impl IntoIterator<Item = (SourcePos, Rope)>,
297        debug_id: Option<RcStr>,
298    ) -> Rope {
299        let mut sections = sections.into_iter().peekable();
300
301        let mut first = sections.next();
302        if let Some((offset, map)) = &mut first
303            && sections.peek().is_none()
304            && *offset == (0, 0)
305            && debug_id.is_none()
306        {
307            // There is just a single sourcemap that starts at the beginning of the file.
308            return std::mem::take(map);
309        }
310
311        // My kingdom for a decent dedent macro with interpolation!
312        // NOTE: The empty `sources` array is technically incorrect, but there is a bug
313        // in Node.js that requires sectioned source maps to have a `sources` array.
314        let mut rope = RopeBuilder::from(
315            r#"{
316  "version": 3,
317  "sources": [],
318"#,
319        );
320        if let Some(debug_id) = debug_id {
321            writeln!(rope, r#"  "debugId": "{debug_id}","#).unwrap();
322        }
323        rope += "  \"sections\": [";
324
325        let mut first_section = true;
326        for (offset, section_map) in first.into_iter().chain(sections) {
327            if !first_section {
328                rope += ",";
329            }
330            first_section = false;
331
332            write!(
333                rope,
334                r#"
335    {{"offset": {{"line": {}, "column": {}}}, "map": "#,
336                offset.line, offset.column,
337            )
338            .unwrap();
339
340            rope += &section_map;
341
342            rope += "}";
343        }
344
345        rope += "]";
346
347        rope += "\n}";
348
349        rope.build()
350    }
351
352    /// Stringifies the source map into JSON bytes.
353    pub fn to_rope(&self) -> Result<Rope> {
354        let mut bytes = vec![];
355        self.map.0.to_writer(&mut bytes)?;
356        Ok(Rope::from(bytes))
357    }
358
359    /// Traces a generated line/column into an mapping token representing either
360    /// synthetic code or user-authored original code.
361    pub fn lookup_token(&self, line: u32, column: u32) -> Token {
362        let (token, _) = self.lookup_token_and_source_internal(line, column, true);
363        token
364    }
365
366    /// Traces a generated line/column into an mapping token representing either
367    /// synthetic code or user-authored original code.
368    pub async fn lookup_token_and_source(&self, line: u32, column: u32) -> Result<TokenWithSource> {
369        let (token, content) = self.lookup_token_and_source_internal(line, column, true);
370        Ok(TokenWithSource {
371            token,
372            source_content: match content {
373                Some(v) => Some(v.to_resolved().await?),
374                None => None,
375            },
376        })
377    }
378
379    pub async fn with_resolved_sources(&self, origin: FileSystemPath) -> Result<Self> {
380        async fn resolve_source(
381            source_request: BytesStr,
382            source_content: Option<BytesStr>,
383            origin: FileSystemPath,
384        ) -> Result<(BytesStr, BytesStr)> {
385            Ok(
386                if let Some(path) = origin.parent().try_join(&source_request) {
387                    let path_str = path.to_string_ref().await?;
388                    let source = format!("{SOURCE_URL_PROTOCOL}///{path_str}");
389                    let source_content = if let Some(source_content) = source_content {
390                        source_content
391                    } else if let FileContent::Content(file) = &*path.read().await? {
392                        let text = file.content().to_str()?;
393                        text.to_string().into()
394                    } else {
395                        format!("unable to read source {path_str}").into()
396                    };
397                    (source.into(), source_content)
398                } else {
399                    let origin_str = origin.to_string_ref().await?;
400                    static INVALID_REGEX: LazyLock<Regex> =
401                        LazyLock::new(|| Regex::new(r#"(?:^|/)(?:\.\.?(?:/|$))+"#).unwrap());
402                    let source = INVALID_REGEX
403                        .replace_all(&source_request, |s: &regex::Captures<'_>| {
404                            s[0].replace('.', "_")
405                        });
406                    let source = format!("{SOURCE_URL_PROTOCOL}///{origin_str}/{source}");
407                    let source_content = source_content.unwrap_or_else(|| {
408                        format!(
409                            "unable to access {source_request} in {origin_str} (it's leaving the \
410                             filesystem root)"
411                        )
412                        .into()
413                    });
414                    (source.into(), source_content)
415                },
416            )
417        }
418        async fn regular_map_with_resolved_sources(
419            map: &RegularMapWrapper,
420            origin: FileSystemPath,
421        ) -> Result<RegularMap> {
422            let map = &map.0;
423            let file = map.get_file().cloned();
424            let tokens = map.tokens().map(|t| t.get_raw_token()).collect();
425            let names = map.names().cloned().collect();
426            let count = map.get_source_count() as usize;
427            let sources = map.sources().cloned().collect::<Vec<_>>();
428            let source_contents = map
429                .source_contents()
430                .map(|s| s.cloned())
431                .collect::<Vec<_>>();
432            let mut new_sources = Vec::with_capacity(count);
433            let mut new_source_contents = Vec::with_capacity(count);
434            for (source, source_content) in sources.into_iter().zip(source_contents) {
435                let (source, source_content) =
436                    resolve_source(source, source_content, origin.clone()).await?;
437                new_sources.push(source);
438                new_source_contents.push(Some(source_content));
439            }
440            let mut map =
441                RegularMap::new(file, tokens, names, new_sources, Some(new_source_contents));
442
443            add_default_ignore_list(&mut map);
444
445            Ok(map)
446        }
447        async fn decoded_map_with_resolved_sources(
448            map: &CrateMapWrapper,
449            origin: FileSystemPath,
450        ) -> Result<CrateMapWrapper> {
451            Ok(CrateMapWrapper(match &map.0 {
452                DecodedMap::Regular(map) => {
453                    let map = RegularMapWrapper::ref_cast(map);
454                    DecodedMap::Regular(regular_map_with_resolved_sources(map, origin).await?)
455                }
456                DecodedMap::Index(map) => {
457                    let count = map.get_section_count() as usize;
458                    let file = map.get_file().cloned();
459                    let sections = map
460                        .sections()
461                        .filter_map(|section| {
462                            section
463                                .get_sourcemap()
464                                .map(|s| (section.get_offset(), CrateMapWrapper::ref_cast(s)))
465                        })
466                        .collect::<Vec<_>>();
467                    let sections = sections
468                        .into_iter()
469                        .map(|(offset, map)| {
470                            let origin = origin.clone();
471                            async move {
472                                Ok((
473                                    offset,
474                                    Box::pin(decoded_map_with_resolved_sources(
475                                        map,
476                                        origin.clone(),
477                                    ))
478                                    .await?,
479                                ))
480                            }
481                        })
482                        .try_join()
483                        .await?;
484                    let mut new_sections = Vec::with_capacity(count);
485                    for (offset, map) in sections {
486                        new_sections.push(swc_sourcemap::SourceMapSection::new(
487                            offset,
488                            // Urls are deprecated and we don't accept them
489                            None,
490                            Some(map.0),
491                        ));
492                    }
493                    DecodedMap::Index(SourceMapIndex::new(file, new_sections))
494                }
495                DecodedMap::Hermes(_) => {
496                    todo!("hermes source maps are not implemented");
497                }
498            }))
499        }
500
501        let map = Box::pin(decoded_map_with_resolved_sources(&self.map, origin)).await?;
502        Ok(Self::new_decoded(map.0))
503    }
504}
505
506#[turbo_tasks::function]
507fn sourcemap_content_fs_root() -> Vc<FileSystemPath> {
508    VirtualFileSystem::new_with_name(rcstr!("sourcemap-content")).root()
509}
510
511#[turbo_tasks::function]
512async fn sourcemap_content_source(path: RcStr, content: RcStr) -> Result<Vc<Box<dyn Source>>> {
513    let path = sourcemap_content_fs_root().await?.join(&path)?;
514    let content = AssetContent::file(FileContent::new(File::from(content)).cell());
515    Ok(Vc::upcast(VirtualSource::new(path, content)))
516}
517
518impl SourceMap {
519    fn lookup_token_and_source_internal(
520        &self,
521        line: u32,
522        column: u32,
523        need_source_content: bool,
524    ) -> (Token, Option<Vc<Box<dyn Source>>>) {
525        let mut content: Option<Vc<Box<dyn Source>>> = None;
526
527        let token: Token = {
528            let map = &self.map;
529
530            let tok = map.lookup_token(line, column);
531            let mut token = tok.map(Token::from).unwrap_or_else(|| {
532                Token::Synthetic(SyntheticToken {
533                    generated_line: line,
534                    generated_column: column,
535                    guessed_original_file: None,
536                })
537            });
538
539            if let Token::Synthetic(SyntheticToken {
540                guessed_original_file,
541                ..
542            }) = &mut token
543                && let DecodedMap::Regular(map) = &map.0
544                && map.get_source_count() == 1
545            {
546                let source = map.sources().next().unwrap().clone();
547                *guessed_original_file = Some(RcStr::from(source));
548            }
549
550            // Check ignoreList and resolve source content using the regular
551            // (possibly flattened) source map. For Index maps,
552            // SourceMapIndex::flatten() correctly transfers ignoreList entries
553            // from each section into the flattened map.
554            if let Some(flat_map) = map.as_regular_source_map() {
555                if let Token::Original(ref mut orig) = token
556                    && let Some(source_name) = tok.and_then(|t| t.get_source().cloned())
557                    && let Some(idx) = flat_map.sources().position(|s| *s == *source_name)
558                {
559                    orig.is_ignored = flat_map.ignore_list().any(|id| *id == idx as u32);
560                }
561
562                if need_source_content && content.is_none() {
563                    content = tok.and_then(|tok| {
564                        let src_id = tok.get_src_id();
565
566                        let name = flat_map.get_source(src_id);
567                        let content = flat_map.get_source_contents(src_id);
568
569                        let (name, content) = name.zip(content)?;
570                        Some(sourcemap_content_source(
571                            name.clone().into(),
572                            content.clone().into(),
573                        ))
574                    });
575                }
576            }
577
578            token
579        };
580
581        (token, content)
582    }
583}
584
585impl SourceMap {
586    pub fn tokens(&self) -> impl Iterator<Item = Token> + '_ {
587        let map = &self.map;
588
589        fn regular_map_to_tokens(
590            map: &RegularMap,
591            offset_line: u32,
592            offset_column: u32,
593        ) -> impl Iterator<Item = Token> + '_ {
594            map.tokens()
595                .map(move |t| Token::from(t).with_offset(offset_line, offset_column))
596        }
597
598        fn index_map_to_tokens(
599            map: &SourceMapIndex,
600            offset_line: u32,
601            offset_column: u32,
602        ) -> impl Iterator<Item = Token> + '_ {
603            map.sections().flat_map(move |section| {
604                let (line, col) = section.get_offset();
605                let offset_line = offset_line + line;
606                let offset_column = if line == 0 { offset_column + col } else { col };
607                if let Some(source_map) = section.get_sourcemap() {
608                    Either::Left(Box::new(decoded_map_to_tokens(
609                        source_map,
610                        offset_line,
611                        offset_column,
612                    )) as Box<dyn Iterator<Item = Token>>)
613                } else {
614                    Either::Right(std::iter::empty())
615                }
616            })
617        }
618
619        fn decoded_map_to_tokens(
620            map: &DecodedMap,
621            offset_line: u32,
622            offset_column: u32,
623        ) -> impl Iterator<Item = Token> + '_ {
624            match map {
625                DecodedMap::Regular(map) => {
626                    Either::Left(regular_map_to_tokens(map, offset_line, offset_column))
627                }
628                DecodedMap::Index(map) => {
629                    Either::Right(index_map_to_tokens(map, offset_line, offset_column))
630                }
631                DecodedMap::Hermes(_) => {
632                    todo!("hermes source maps are not implemented");
633                }
634            }
635        }
636
637        decoded_map_to_tokens(&map.0, 0, 0)
638    }
639}
640
641#[turbo_tasks::value_impl]
642impl GenerateSourceMap for SourceMap {
643    #[turbo_tasks::function]
644    fn generate_source_map(&self) -> Result<Vc<FileContent>> {
645        Ok(FileContent::Content(File::from(self.to_rope()?)).cell())
646    }
647}
648
649/// Wraps the DecodedMap struct so that it is Sync and Send.
650///
651/// # Safety
652///
653/// Must not use per line access to the SourceMap, as it is not thread safe.
654#[derive(Debug, RefCast)]
655#[repr(transparent)]
656pub struct CrateMapWrapper(DecodedMap);
657
658// Safety: DecodedMap contains a raw pointer, which isn't Send, which is
659// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
660unsafe impl Send for CrateMapWrapper {}
661unsafe impl Sync for CrateMapWrapper {}
662
663/// Wraps the RegularMap struct so that it is Sync and Send.
664///
665/// # Safety
666///
667/// Must not use per line access to the SourceMap, as it is not thread safe.
668#[derive(Debug, RefCast)]
669#[repr(transparent)]
670pub struct RegularMapWrapper(RegularMap);
671
672// Safety: RegularMap contains a raw pointer, which isn't Send, which is
673// required to cache in a Vc. So, we have wrap it in 4 layers of cruft to do it.
674unsafe impl Send for RegularMapWrapper {}
675unsafe impl Sync for RegularMapWrapper {}
676
677impl CrateMapWrapper {
678    pub fn as_regular_source_map(&self) -> Option<Cow<'_, RegularMap>> {
679        match &self.0 {
680            DecodedMap::Regular(m) => Some(Cow::Borrowed(m)),
681            DecodedMap::Index(m) => m.flatten().map(Cow::Owned).ok(),
682            _ => None,
683        }
684    }
685}
686
687impl Deref for CrateMapWrapper {
688    type Target = DecodedMap;
689
690    fn deref(&self) -> &Self::Target {
691        &self.0
692    }
693}
694
695impl Encode for CrateMapWrapper {
696    fn encode<E: Encoder>(&self, encoder: &mut E) -> Result<(), EncodeError> {
697        let mut bytes = Vec::new();
698        self.0
699            .to_writer(&mut bytes)
700            .map_err(|e| EncodeError::OtherString(e.to_string()))?;
701        bytes.encode(encoder)
702    }
703}
704
705impl<Context> Decode<Context> for CrateMapWrapper {
706    fn decode<D: Decoder<Context = Context>>(decoder: &mut D) -> Result<Self, DecodeError> {
707        let bytes = Vec::<u8>::decode(decoder)?;
708        let map = DecodedMap::from_reader(&*bytes)
709            .map_err(|e| DecodeError::OtherString(e.to_string()))?;
710        Ok(CrateMapWrapper(map))
711    }
712}
713
714bincode::impl_borrow_decode!(CrateMapWrapper);