Skip to content

Commit 043b147

Browse files
authored
fix: SourceMapper correctly uses ModuleLoader to get source lines (#827)
Fixes `SourceMapper` API that was changed in #823. Now the source lines are correctly mapped even if deprecated `SourceMapGetter` API is not used.
1 parent 8c8dd2a commit 043b147

File tree

2 files changed

+142
-43
lines changed

2 files changed

+142
-43
lines changed

core/error.rs

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -475,26 +475,15 @@ impl JsError {
475475
{
476476
let state = JsRuntime::state_from(scope);
477477
let mut source_mapper = state.source_mapper.borrow_mut();
478-
if source_mapper.has_user_sources() {
479-
for (i, frame) in frames.iter().enumerate() {
480-
if let (Some(file_name), Some(line_number)) =
481-
(&frame.file_name, frame.line_number)
482-
{
483-
if !file_name.trim_start_matches('[').starts_with("ext:") {
484-
source_line =
485-
source_mapper.get_source_line(file_name, line_number);
486-
source_line_frame_index = Some(i);
487-
break;
488-
}
489-
}
490-
}
491-
} else if let Some(frame) = frames.first() {
492-
if let Some(file_name) = &frame.file_name {
478+
for (i, frame) in frames.iter().enumerate() {
479+
if let (Some(file_name), Some(line_number)) =
480+
(&frame.file_name, frame.line_number)
481+
{
493482
if !file_name.trim_start_matches('[').starts_with("ext:") {
494-
source_line = msg
495-
.get_source_line(scope)
496-
.map(|v| v.to_rust_string_lossy(scope));
497-
source_line_frame_index = Some(0);
483+
source_line =
484+
source_mapper.get_source_line(file_name, line_number);
485+
source_line_frame_index = Some(i);
486+
break;
498487
}
499488
}
500489
}

core/source_map.rs

Lines changed: 134 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ pub trait SourceMapGetter {
2424
) -> Option<String>;
2525
}
2626

27+
#[derive(Debug, PartialEq)]
2728
pub enum SourceMapApplication {
2829
/// No mapping was applied, the location is unchanged.
2930
Unchanged,
@@ -69,10 +70,6 @@ impl SourceMapper {
6970
}
7071
}
7172

72-
pub(crate) fn has_user_sources(&self) -> bool {
73-
self.getter.is_some()
74-
}
75-
7673
/// Apply a source map to the passed location. If there is no source map for
7774
/// this location, or if the location remains unchanged after mapping, the
7875
/// changed values are returned.
@@ -154,27 +151,140 @@ impl SourceMapper {
154151
file_name: &str,
155152
line_number: i64,
156153
) -> Option<String> {
154+
if let Some(maybe_source_line) =
155+
self.source_lines.get(&(file_name.to_string(), line_number))
156+
{
157+
return maybe_source_line.clone();
158+
}
159+
160+
// TODO(bartlomieju): shouldn't we cache `None` values to avoid computing it each time, instead of early return?
161+
if let Some(source_line) = self
162+
.loader
163+
.get_source_mapped_source_line(file_name, (line_number - 1) as usize)
164+
.filter(|s| s.len() <= Self::MAX_SOURCE_LINE_LENGTH)
165+
{
166+
// Cache and return
167+
self.source_lines.insert(
168+
(file_name.to_string(), line_number),
169+
Some(source_line.to_string()),
170+
);
171+
return Some(source_line);
172+
}
173+
174+
// TODO(bartlomieju): remove in deno_core 0.230.0
175+
// Fallback to a deprecated API
157176
let getter = self.getter.as_ref()?;
177+
let s = getter.get_source_line(file_name, (line_number - 1) as usize);
178+
let maybe_source_line =
179+
s.filter(|s| s.len() <= Self::MAX_SOURCE_LINE_LENGTH);
180+
// Cache and return
181+
self.source_lines.insert(
182+
(file_name.to_string(), line_number),
183+
maybe_source_line.clone(),
184+
);
185+
maybe_source_line
186+
}
187+
}
158188

159-
self
160-
.source_lines
161-
.entry((file_name.to_string(), line_number))
162-
.or_insert_with(|| {
163-
// Source lookup expects a 0-based line number, ours are 1-based.
164-
if let Some(source_line) = self
165-
.loader
166-
.get_source_mapped_source_line(file_name, (line_number - 1) as usize)
167-
{
168-
if source_line.len() <= Self::MAX_SOURCE_LINE_LENGTH {
169-
Some(source_line)
170-
} else {
171-
None
172-
}
173-
} else {
174-
let s = getter.get_source_line(file_name, (line_number - 1) as usize);
175-
s.filter(|s| s.len() <= Self::MAX_SOURCE_LINE_LENGTH)
176-
}
177-
})
178-
.clone()
189+
#[cfg(test)]
190+
mod tests {
191+
use anyhow::Error;
192+
use url::Url;
193+
194+
use super::*;
195+
use crate::ascii_str;
196+
use crate::ModuleCodeString;
197+
use crate::ModuleLoadResponse;
198+
use crate::ModuleSpecifier;
199+
use crate::RequestedModuleType;
200+
use crate::ResolutionKind;
201+
202+
struct SourceMapLoaderContent {
203+
source_map: Option<ModuleCodeString>,
204+
}
205+
206+
#[derive(Default)]
207+
pub struct SourceMapLoader {
208+
map: HashMap<ModuleSpecifier, SourceMapLoaderContent>,
209+
}
210+
211+
impl ModuleLoader for SourceMapLoader {
212+
fn resolve(
213+
&self,
214+
_specifier: &str,
215+
_referrer: &str,
216+
_kind: ResolutionKind,
217+
) -> Result<ModuleSpecifier, Error> {
218+
unreachable!()
219+
}
220+
221+
fn load(
222+
&self,
223+
_module_specifier: &ModuleSpecifier,
224+
_maybe_referrer: Option<&ModuleSpecifier>,
225+
_is_dyn_import: bool,
226+
_requested_module_type: RequestedModuleType,
227+
) -> ModuleLoadResponse {
228+
unreachable!()
229+
}
230+
231+
fn get_source_map(&self, file_name: &str) -> Option<Vec<u8>> {
232+
let url = Url::parse(file_name).unwrap();
233+
let content = self.map.get(&url)?;
234+
content
235+
.source_map
236+
.as_ref()
237+
.map(|s| s.to_string().into_bytes())
238+
}
239+
240+
fn get_source_mapped_source_line(
241+
&self,
242+
_file_name: &str,
243+
_line_number: usize,
244+
) -> Option<String> {
245+
Some("fake source line".to_string())
246+
}
247+
}
248+
249+
#[test]
250+
fn test_source_mapper() {
251+
let mut loader = SourceMapLoader::default();
252+
loader.map.insert(
253+
Url::parse("file:///b.js").unwrap(),
254+
SourceMapLoaderContent { source_map: None },
255+
);
256+
loader.map.insert(
257+
Url::parse("file:///a.ts").unwrap(),
258+
SourceMapLoaderContent {
259+
source_map: Some(ascii_str!(r#"{"version":3,"sources":["file:///a.ts"],"sourcesContent":["export function a(): string {\n return \"a\";\n}\n"],"names":[],"mappings":"AAAA,OAAO,SAAS;EACd,OAAO;AACT"}"#).into()),
260+
},
261+
);
262+
263+
let mut source_mapper = SourceMapper::new(Rc::new(loader), None);
264+
265+
// Non-existent file
266+
let application =
267+
source_mapper.apply_source_map("file:///doesnt_exist.js", 1, 1);
268+
assert_eq!(application, SourceMapApplication::Unchanged);
269+
270+
// File with no source map
271+
let application = source_mapper.apply_source_map("file:///b.js", 1, 1);
272+
assert_eq!(application, SourceMapApplication::Unchanged);
273+
274+
// File with a source map
275+
let application = source_mapper.apply_source_map("file:///a.ts", 1, 21);
276+
assert_eq!(
277+
application,
278+
SourceMapApplication::LineAndColumn {
279+
line_number: 1,
280+
column_number: 17
281+
}
282+
);
283+
284+
let line = source_mapper.get_source_line("file:///a.ts", 1).unwrap();
285+
assert_eq!(line, "fake source line");
286+
// Get again to hit a cache
287+
let line = source_mapper.get_source_line("file:///a.ts", 1).unwrap();
288+
assert_eq!(line, "fake source line");
179289
}
180290
}

0 commit comments

Comments
 (0)