-
Notifications
You must be signed in to change notification settings - Fork 107
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[WIP] feat: Add native SourceMap support #819
base: main
Are you sure you want to change the base?
Changes from 20 commits
3d8c9c8
2905e0d
23cdef9
ec8e4f1
333c579
72ffd9d
588dc8a
a78caa2
a46cd55
0a2821c
6fc5d1d
0166243
6bfb77b
224bcf2
01bf21c
7ff9928
40aff1a
84d6c3f
7a380af
85a9634
b0bfb5c
a6c6f76
126eb6b
4e4036d
b253371
a503d80
cc573b4
f01fbe8
2d4fdb9
1ccf6c6
0795341
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"use strict"; | ||
|
||
throw new Error("Hello world!"); | ||
//# sourceMappingURL=bar.js.map | ||
|
||
asd; |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,9 +5,15 @@ | |
// TODO(bartlomieju): remove once `SourceMapGetter` is removed. | ||
#![allow(deprecated)] | ||
|
||
use crate::module_specifier::ModuleResolutionError; | ||
use crate::resolve_import; | ||
use crate::resolve_url; | ||
use crate::runtime::JsRealm; | ||
use crate::ModuleLoader; | ||
use crate::ModuleName; | ||
use crate::RequestedModuleType; | ||
use base64::prelude::BASE64_STANDARD; | ||
use base64::Engine; | ||
pub use sourcemap::SourceMap; | ||
use std::borrow::Cow; | ||
use std::collections::HashMap; | ||
|
@@ -89,13 +95,93 @@ impl SourceMapper { | |
std::mem::take(&mut self.ext_source_maps) | ||
} | ||
|
||
pub fn apply_source_map_from_module_map( | ||
&mut self, | ||
scope: &mut v8::HandleScope, | ||
file_name: &str, | ||
line_number: u32, | ||
column_number: u32, | ||
) -> Option<SourceMapApplication> { | ||
let module_map_rc = JsRealm::module_map_from(scope); | ||
let id = module_map_rc.get_id(file_name, RequestedModuleType::None)?; | ||
|
||
let module_handle = module_map_rc.get_handle(id).unwrap(); | ||
let module = v8::Local::new(scope, module_handle); | ||
let unbound_module_script = module.get_unbound_module_script(scope); | ||
let maybe_source_mapping_url = | ||
unbound_module_script.get_source_mapping_url(scope); | ||
|
||
// TODO(bartlomieju): This should be the last fallback and it's only useful | ||
// for eval - probably for `Deno.core.evalContext()`. | ||
// let maybe_source_url = unbound_module_script | ||
// .get_source_url(scope) | ||
// .to_rust_string_lossy(scope); | ||
// eprintln!("maybe source url {}", maybe_source_url); | ||
|
||
if !maybe_source_mapping_url.is_string() { | ||
return None; | ||
} | ||
bartlomieju marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let source_map_string = | ||
maybe_source_mapping_url.to_rust_string_lossy(scope); | ||
// eprintln!("maybe source mapping url {}", source_map_string); | ||
|
||
// TODO(bartlomieju): this is a fast path - if it fails, we should try to parse | ||
// the URL (or resolve it from the current file being mapped) and fallback to | ||
// acquiring a source map from that URL. In Deno we might want to apply permissions | ||
// checks for fetching the map. | ||
let source_map = if let Some(b64) = | ||
source_map_string.strip_prefix("data:application/json;base64,") | ||
{ | ||
let decoded_b64 = BASE64_STANDARD.decode(b64).ok()?; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This code is duplicated in CLI, maybe we can have a common helper shared between the two? |
||
// eprintln!( | ||
// "source map {:?}", | ||
// String::from_utf8(decoded_b64.clone()).unwrap() | ||
// ); | ||
SourceMap::from_slice(&decoded_b64).ok()? | ||
} else { | ||
let url = match resolve_import(&source_map_string, file_name) { | ||
Ok(url) => Some(url), | ||
Err(err) => match err { | ||
ModuleResolutionError::ImportPrefixMissing(_, _) => { | ||
resolve_import(&format!("./{}", source_map_string), file_name).ok() | ||
} | ||
_ => None, | ||
}, | ||
}; | ||
// eprintln!( | ||
// "source map url sourceMappingURL={} file_name={} url={}", | ||
// source_map_string, | ||
// file_name, | ||
// url.as_ref().map(|s| s.as_str()).unwrap_or_default() | ||
// ); | ||
let url = url?; | ||
if url.scheme() != "file" { | ||
return None; | ||
} | ||
let contents = module_map_rc | ||
.loader | ||
.borrow() | ||
.get_source_map(url.to_file_path().ok()?.to_str()?)?; | ||
bartlomieju marked this conversation as resolved.
Show resolved
Hide resolved
|
||
SourceMap::from_slice(&contents).ok()? | ||
}; | ||
|
||
Some(Self::compute_application( | ||
&source_map, | ||
file_name, | ||
line_number, | ||
column_number, | ||
)) | ||
} | ||
|
||
/// Apply a source map to the passed location. If there is no source map for | ||
/// this location, or if the location remains unchanged after mapping, the | ||
/// changed values are returned. | ||
/// | ||
/// Line and column numbers are 1-based. | ||
pub fn apply_source_map( | ||
&mut self, | ||
scope: &mut v8::HandleScope, | ||
file_name: &str, | ||
line_number: u32, | ||
column_number: u32, | ||
|
@@ -104,6 +190,17 @@ impl SourceMapper { | |
let line_number = line_number - 1; | ||
let column_number = column_number - 1; | ||
|
||
// TODO(bartlomieju): requires scope and should only be called in a fallback op, | ||
// that will access scope if the fast op doesn't return anything. | ||
if let Some(app) = self.apply_source_map_from_module_map( | ||
scope, | ||
file_name, | ||
line_number, | ||
column_number, | ||
) { | ||
return app; | ||
} | ||
bartlomieju marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
let getter = self.getter.as_ref(); | ||
let maybe_source_map = | ||
self.maps.entry(file_name.to_owned()).or_insert_with(|| { | ||
|
@@ -123,6 +220,15 @@ impl SourceMapper { | |
return SourceMapApplication::Unchanged; | ||
}; | ||
|
||
Self::compute_application(source_map, file_name, line_number, column_number) | ||
} | ||
|
||
fn compute_application( | ||
source_map: &SourceMap, | ||
file_name: &str, | ||
line_number: u32, | ||
column_number: u32, | ||
) -> SourceMapApplication { | ||
let Some(token) = source_map.lookup_token(line_number, column_number) | ||
else { | ||
return SourceMapApplication::Unchanged; | ||
|
@@ -212,11 +318,13 @@ mod tests { | |
|
||
use super::*; | ||
use crate::ascii_str; | ||
use crate::JsRuntime; | ||
use crate::ModuleCodeString; | ||
use crate::ModuleLoadResponse; | ||
use crate::ModuleSpecifier; | ||
use crate::RequestedModuleType; | ||
use crate::ResolutionKind; | ||
use crate::RuntimeOptions; | ||
|
||
struct SourceMapLoaderContent { | ||
source_map: Option<ModuleCodeString>, | ||
|
@@ -279,19 +387,29 @@ mod tests { | |
}, | ||
); | ||
|
||
let mut source_mapper = SourceMapper::new(Rc::new(loader), None); | ||
let loader = Rc::new(loader); | ||
|
||
let mut js_runtime = JsRuntime::new(RuntimeOptions { | ||
module_loader: Some(loader.clone()), | ||
..Default::default() | ||
}); | ||
let state = JsRuntime::state_from(js_runtime.v8_isolate()); | ||
let scope = &mut js_runtime.handle_scope(); | ||
let mut source_mapper = state.source_mapper.borrow_mut(); | ||
|
||
// Non-existent file | ||
let application = | ||
source_mapper.apply_source_map("file:///doesnt_exist.js", 1, 1); | ||
source_mapper.apply_source_map(scope, "file:///doesnt_exist.js", 1, 1); | ||
assert_eq!(application, SourceMapApplication::Unchanged); | ||
|
||
// File with no source map | ||
let application = source_mapper.apply_source_map("file:///b.js", 1, 1); | ||
let application = | ||
source_mapper.apply_source_map(scope, "file:///b.js", 1, 1); | ||
assert_eq!(application, SourceMapApplication::Unchanged); | ||
|
||
// File with a source map | ||
let application = source_mapper.apply_source_map("file:///a.ts", 1, 21); | ||
let application = | ||
source_mapper.apply_source_map(scope, "file:///a.ts", 1, 21); | ||
assert_eq!( | ||
application, | ||
SourceMapApplication::LineAndColumn { | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
"use strict"; | ||
|
||
throw new Error("Hello world!"); | ||
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiIiwic291cmNlUm9vdCI6IiIsInNvdXJjZXMiOlsiaHR0cDovL2xvY2FsaG9zdDo0NTQ1L3J1bi9pbmxpbmVfanNfc291cmNlX21hcF8yLnRzIl0sInNvdXJjZXNDb250ZW50IjpbIjErMTtcbmludGVyZmFjZSBUZXN0IHtcbiAgaGVsbG86IHN0cmluZztcbn1cblxudGhyb3cgbmV3IEVycm9yKFwiSGVsbG8gd29ybGQhXCIgYXMgdW5rbm93biBhcyBzdHJpbmcpO1xuIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiI7QUFBQSxDQUFDLEdBQUMsQ0FBQyxDQUFDO0FBS0osTUFBTSxJQUFJLEtBQUssQ0FBQyxjQUErQixDQUFDLENBQUMifQ== | ||
|
||
asd; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Verify this in a separate PR. We support source maps for
ext:
modules so most likely this is not needed