enhanced_magic_string/
magic_string.rs

1use std::{
2  collections::{HashMap, HashSet},
3  sync::Arc,
4};
5
6use crate::{error::Result, utils::common::get_relative_path};
7use parking_lot::Mutex;
8use sourcemap::{SourceMap, SourceMapBuilder};
9
10use crate::{
11  chunk::Chunk,
12  mappings::Mappings,
13  types::SourceMapOptions,
14  utils::{char_string::CharString, get_locator::get_locator},
15};
16
17pub type ExclusionRange = (usize, usize);
18
19#[derive(Default)]
20pub struct MagicStringOptions {
21  pub filename: Option<String>,
22  pub indent_exclusion_ranges: Vec<ExclusionRange>,
23  pub ignore_list: Vec<CharString>,
24  pub source_map_chain: Vec<Arc<String>>,
25}
26
27pub struct MagicString {
28  pub original: CharString,
29  pub outro: CharString,
30  pub intro: CharString,
31
32  pub first_chunk: Arc<Mutex<Chunk>>,
33  pub last_chunk: Arc<Mutex<Chunk>>,
34  pub last_searched_chunk: Arc<Mutex<Chunk>>,
35  pub chunk_by_start: HashMap<usize, Arc<Mutex<Chunk>>>,
36  pub chunk_by_end: HashMap<usize, Arc<Mutex<Chunk>>>,
37
38  pub filename: Option<String>,
39  pub indent_exclusion_ranges: Vec<ExclusionRange>,
40  pub sourcemap_locations: HashSet<usize>,
41  pub stored_names: HashMap<CharString, bool>,
42  pub indent_str: Option<CharString>,
43  pub ignore_list: Vec<CharString>,
44  source_map_chain: Vec<Arc<String>>,
45
46  pub separator: char,
47}
48
49impl MagicString {
50  pub fn new(original: &str, options: Option<MagicStringOptions>) -> Self {
51    let options = options.unwrap_or_default();
52    let original = CharString::new(original);
53    let chunk = Arc::new(Mutex::new(Chunk::new(0, original.len(), original.clone())));
54
55    let mut magic_string = Self {
56      original: original.clone(),
57      outro: CharString::new(""),
58      intro: CharString::new(""),
59      first_chunk: chunk.clone(),
60      last_chunk: chunk.clone(),
61      last_searched_chunk: chunk,
62      chunk_by_start: HashMap::new(),
63      chunk_by_end: HashMap::new(),
64      filename: options.filename,
65      indent_exclusion_ranges: options.indent_exclusion_ranges,
66      sourcemap_locations: HashSet::new(),
67      stored_names: HashMap::new(),
68      indent_str: None,
69      ignore_list: options.ignore_list,
70      source_map_chain: options.source_map_chain,
71      separator: '\n',
72    };
73
74    magic_string
75      .chunk_by_start
76      .insert(0, magic_string.first_chunk.clone());
77    magic_string
78      .chunk_by_end
79      .insert(0, magic_string.last_chunk.clone());
80
81    magic_string
82  }
83
84  pub fn get_source_map_chain(&self) -> Vec<SourceMap> {
85    let mut chain = self
86      .source_map_chain
87      .iter()
88      .map(|source| SourceMap::from_slice(source.as_bytes()).unwrap())
89      .filter(|source| {
90        // if the source map is empty, we should ignore it
91        source.get_token_count() > 0
92      })
93      .collect::<Vec<_>>();
94    chain.reverse();
95
96    chain
97  }
98
99  pub fn generate_map(&self, opts: SourceMapOptions) -> Result<SourceMap> {
100    let source_index = 0;
101    // let names: Vec<&CharString> = self.stored_names.keys().collect();
102
103    let locate = get_locator(&self.original);
104    let mut mappings = Mappings::new(opts.hires.unwrap_or_default());
105
106    if !self.intro.is_empty() {
107      mappings.advance(&self.intro);
108    }
109
110    self.first_chunk.lock().each_next(|chunk| {
111      let loc = locate(chunk.start);
112
113      if !chunk.intro.is_empty() {
114        mappings.advance(&chunk.intro);
115      }
116
117      if !chunk.edited {
118        mappings.add_unedited_chunk(
119          source_index,
120          &chunk,
121          &self.original,
122          loc,
123          &self.sourcemap_locations,
124        )
125      } else {
126        unimplemented!("chunk.edited")
127      }
128
129      if !chunk.outro.is_empty() {
130        mappings.advance(&chunk.outro)
131      }
132    });
133
134    let source = if let Some(src) = &opts.source {
135      get_relative_path(opts.file.clone().unwrap_or_default().as_str(), src).unwrap()
136    } else {
137      opts.file.clone().unwrap_or_default()
138    };
139
140    let mut sourcemap_builder = SourceMapBuilder::new(opts.file.as_ref().map(|f| f.as_str()));
141    let src_id = sourcemap_builder.add_source(&source);
142
143    let inline_content = opts.include_content.unwrap_or(false);
144
145    let contet = if inline_content {
146      Some(self.original.to_string())
147    } else {
148      None
149    };
150    sourcemap_builder.set_source_contents(src_id, contet.as_deref());
151    mappings.into_sourcemap_mappings(&mut sourcemap_builder);
152    Ok(sourcemap_builder.into_sourcemap())
153  }
154
155  pub fn prepend(&mut self, str: &str) {
156    let mut new_intro = CharString::new(str);
157    new_intro.append(&self.intro);
158    self.intro = new_intro;
159  }
160
161  pub fn append(&mut self, str: &str) {
162    let mut new_outro = self.outro.clone();
163    new_outro.append_str(str);
164    self.outro = new_outro;
165  }
166}
167
168impl ToString for MagicString {
169  fn to_string(&self) -> String {
170    let mut str = self.intro.to_string();
171    let guard = self.first_chunk.lock();
172    let mut chunk = Some(&*guard);
173
174    while let Some(c) = chunk {
175      str += &c.to_string();
176      chunk = c.next();
177    }
178
179    str += &self.outro.to_string();
180    str
181  }
182}