enhanced_magic_string/
magic_string.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
use std::{
  collections::{HashMap, HashSet},
  sync::Arc,
};

use crate::{error::Result, utils::common::get_relative_path};
use parking_lot::Mutex;
use sourcemap::{SourceMap, SourceMapBuilder};

use crate::{
  chunk::Chunk,
  mappings::Mappings,
  types::SourceMapOptions,
  utils::{char_string::CharString, get_locator::get_locator},
};

pub type ExclusionRange = (usize, usize);

#[derive(Default)]
pub struct MagicStringOptions {
  pub filename: Option<String>,
  pub indent_exclusion_ranges: Vec<ExclusionRange>,
  pub ignore_list: Vec<CharString>,
  pub source_map_chain: Vec<Arc<String>>,
}

pub struct MagicString {
  pub original: CharString,
  pub outro: CharString,
  pub intro: CharString,

  pub first_chunk: Arc<Mutex<Chunk>>,
  pub last_chunk: Arc<Mutex<Chunk>>,
  pub last_searched_chunk: Arc<Mutex<Chunk>>,
  pub chunk_by_start: HashMap<usize, Arc<Mutex<Chunk>>>,
  pub chunk_by_end: HashMap<usize, Arc<Mutex<Chunk>>>,

  pub filename: Option<String>,
  pub indent_exclusion_ranges: Vec<ExclusionRange>,
  pub sourcemap_locations: HashSet<usize>,
  pub stored_names: HashMap<CharString, bool>,
  pub indent_str: Option<CharString>,
  pub ignore_list: Vec<CharString>,
  source_map_chain: Vec<Arc<String>>,

  pub separator: char,
}

impl MagicString {
  pub fn new(original: &str, options: Option<MagicStringOptions>) -> Self {
    let options = options.unwrap_or_default();
    let original = CharString::new(original);
    let chunk = Arc::new(Mutex::new(Chunk::new(0, original.len(), original.clone())));

    let mut magic_string = Self {
      original: original.clone(),
      outro: CharString::new(""),
      intro: CharString::new(""),
      first_chunk: chunk.clone(),
      last_chunk: chunk.clone(),
      last_searched_chunk: chunk,
      chunk_by_start: HashMap::new(),
      chunk_by_end: HashMap::new(),
      filename: options.filename,
      indent_exclusion_ranges: options.indent_exclusion_ranges,
      sourcemap_locations: HashSet::new(),
      stored_names: HashMap::new(),
      indent_str: None,
      ignore_list: options.ignore_list,
      source_map_chain: options.source_map_chain,
      separator: '\n',
    };

    magic_string
      .chunk_by_start
      .insert(0, magic_string.first_chunk.clone());
    magic_string
      .chunk_by_end
      .insert(0, magic_string.last_chunk.clone());

    magic_string
  }

  pub fn get_source_map_chain(&self) -> Vec<SourceMap> {
    let mut chain = self
      .source_map_chain
      .iter()
      .map(|source| SourceMap::from_slice(source.as_bytes()).unwrap())
      .filter(|source| {
        // if the source map is empty, we should ignore it
        source.get_token_count() > 0
      })
      .collect::<Vec<_>>();
    chain.reverse();

    chain
  }

  pub fn generate_map(&self, opts: SourceMapOptions) -> Result<SourceMap> {
    let source_index = 0;
    // let names: Vec<&CharString> = self.stored_names.keys().collect();

    let locate = get_locator(&self.original);
    let mut mappings = Mappings::new(opts.hires.unwrap_or_default());

    if !self.intro.is_empty() {
      mappings.advance(&self.intro);
    }

    self.first_chunk.lock().each_next(|chunk| {
      let loc = locate(chunk.start);

      if !chunk.intro.is_empty() {
        mappings.advance(&chunk.intro);
      }

      if !chunk.edited {
        mappings.add_unedited_chunk(
          source_index,
          &chunk,
          &self.original,
          loc,
          &self.sourcemap_locations,
        )
      } else {
        unimplemented!("chunk.edited")
      }

      if !chunk.outro.is_empty() {
        mappings.advance(&chunk.outro)
      }
    });

    let source = if let Some(src) = &opts.source {
      get_relative_path(opts.file.clone().unwrap_or_default().as_str(), src).unwrap()
    } else {
      opts.file.clone().unwrap_or_default()
    };

    let mut sourcemap_builder = SourceMapBuilder::new(opts.file.as_ref().map(|f| f.as_str()));
    let src_id = sourcemap_builder.add_source(&source);

    let inline_content = opts.include_content.unwrap_or(false);

    let contet = if inline_content {
      Some(self.original.to_string())
    } else {
      None
    };
    sourcemap_builder.set_source_contents(src_id, contet.as_deref());
    mappings.into_sourcemap_mappings(&mut sourcemap_builder);
    Ok(sourcemap_builder.into_sourcemap())
  }

  pub fn prepend(&mut self, str: &str) {
    let mut new_intro = CharString::new(str);
    new_intro.append(&self.intro);
    self.intro = new_intro;
  }

  pub fn append(&mut self, str: &str) {
    let mut new_outro = self.outro.clone();
    new_outro.append_str(str);
    self.outro = new_outro;
  }
}

impl ToString for MagicString {
  fn to_string(&self) -> String {
    let mut str = self.intro.to_string();
    let guard = self.first_chunk.lock();
    let mut chunk = Some(&*guard);

    while let Some(c) = chunk {
      str += &c.to_string();
      chunk = c.next();
    }

    str += &self.outro.to_string();
    str
  }
}