Skip to content

Commit 9fea74c

Browse files
committed
Support the parsing of composite sourcemaps
1 parent fe7ab83 commit 9fea74c

File tree

7 files changed

+191
-31
lines changed

7 files changed

+191
-31
lines changed

compiler/lib/link_js.ml

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,14 +170,19 @@ let prefix_kind line =
170170
| true -> `Json_base64 (String.length sourceMappingURL_base64)
171171
| false -> `Url (String.length sourceMappingURL))
172172

173+
let rule_out_index_map = function
174+
| `Standard sm -> sm
175+
| `Index _ -> failwith "unexpected index map at this stage"
176+
173177
let action ~resolve_sourcemap_url ~drop_source_map file line =
174178
match prefix_kind line, drop_source_map with
175179
| `Other, (true | false) -> Keep
176180
| `Unit, (true | false) -> Unit
177181
| `Build_info bi, _ -> Build_info bi
178182
| (`Json_base64 _ | `Url _), true -> Drop
179183
| `Json_base64 offset, false ->
180-
Source_map (Source_map.of_string (Base64.decode_exn ~off:offset line))
184+
Source_map
185+
(rule_out_index_map (Source_map.of_string (Base64.decode_exn ~off:offset line)))
181186
| `Url _, false when not resolve_sourcemap_url -> Drop
182187
| `Url offset, false ->
183188
let url = String.sub line ~pos:offset ~len:(String.length line - offset) in
@@ -186,7 +191,7 @@ let action ~resolve_sourcemap_url ~drop_source_map file line =
186191
let l = in_channel_length ic in
187192
let content = really_input_string ic l in
188193
close_in ic;
189-
Source_map (Source_map.of_string content)
194+
Source_map (rule_out_index_map (Source_map.of_string content))
190195

191196
module Units : sig
192197
val read : Line_reader.t -> Unit_info.t -> Unit_info.t

compiler/lib/source_map.ml

Lines changed: 119 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,6 @@ let gen_line = function
5757
let gen_col = function
5858
| Gen { gen_col; _ } | Gen_Ori { gen_col; _ } | Gen_Ori_Name { gen_col; _ } -> gen_col
5959

60-
6160
module Line_edits = struct
6261
type action =
6362
| Keep
@@ -76,7 +75,6 @@ module Line_edits = struct
7675
let pp fmt = Format.(pp_print_list pp_action fmt)
7776
end
7877

79-
8078
module Mappings = struct
8179
type t = Uninterpreted of string [@@unboxed]
8280

@@ -579,7 +577,6 @@ let empty ~filename =
579577
; mappings = Mappings.empty
580578
}
581579

582-
583580
let concat ~file ~sourceroot s1 s2 =
584581
if not (Int.equal s1.version s2.version)
585582
then invalid_arg "Source_map.concat: different versions";
@@ -695,15 +692,15 @@ let merge = function
695692

696693
(* IO *)
697694

695+
let rewrite_path path =
696+
if Filename.is_relative path
697+
then path
698+
else
699+
match Build_path_prefix_map.get_build_path_prefix_map () with
700+
| Some map -> Build_path_prefix_map.rewrite map path
701+
| None -> path
702+
698703
let json t =
699-
let rewrite_path path =
700-
if Filename.is_relative path
701-
then path
702-
else
703-
match Build_path_prefix_map.get_build_path_prefix_map () with
704-
| Some map -> Build_path_prefix_map.rewrite map path
705-
| None -> path
706-
in
707704
let stringlit s = `Stringlit (Yojson.Safe.to_string (`String s)) in
708705
`Assoc
709706
[ "version", `Intlit (string_of_int t.version)
@@ -764,7 +761,7 @@ let list_stringlit_opt name rest =
764761
| _ -> invalid ()
765762
with Not_found -> None
766763

767-
let of_json (json : Yojson.Raw.t) =
764+
let standard_map_of_json (json : Yojson.Raw.t) =
768765
match json with
769766
| `Assoc (("version", `Intlit version) :: rest) when int_of_string version = 3 ->
770767
let string name json = Option.map ~f:string_of_stringlit (stringlit name json) in
@@ -808,8 +805,116 @@ let of_json (json : Yojson.Raw.t) =
808805
}
809806
| _ -> invalid ()
810807

811-
let of_string s = of_json (Yojson.Raw.from_string s)
812-
813808
let to_string m = Yojson.Raw.to_string (json m)
814809

815810
let to_file m file = Yojson.Raw.to_file file (json m)
811+
812+
module Index = struct
813+
type offset =
814+
{ gen_line : int
815+
; gen_column : int
816+
}
817+
818+
(* Type synonym to avoid confusion between toplevel [t] and this submodule's [t]. *)
819+
type map = t
820+
821+
type t =
822+
{ version : int
823+
; file : string
824+
; sections : (offset * [ `Map of map ]) list
825+
}
826+
827+
let json t =
828+
let stringlit s = `Stringlit (Yojson.Safe.to_string (`String s)) in
829+
`Assoc
830+
[ "version", `Intlit (string_of_int t.version)
831+
; "file", stringlit (rewrite_path t.file)
832+
; ( "sections"
833+
, `List
834+
(List.map
835+
~f:(fun ({ gen_line; gen_column }, `Map sm) ->
836+
`Assoc
837+
[ ( "offset"
838+
, `Assoc
839+
[ "line", `Intlit (string_of_int gen_line)
840+
; "column", `Intlit (string_of_int gen_column)
841+
] )
842+
; "map", json sm
843+
])
844+
t.sections) )
845+
]
846+
847+
let intlit ~errmsg name json =
848+
match List.assoc name json with
849+
| `Intlit i -> int_of_string i
850+
| _ -> invalid_arg errmsg
851+
| exception Not_found -> invalid_arg errmsg
852+
853+
let section_of_json : Yojson.Raw.t -> offset * [ `Map of map ] = function
854+
| `Assoc json ->
855+
let offset =
856+
match List.assoc "offset" json with
857+
| `Assoc fields ->
858+
let gen_line =
859+
intlit
860+
"line"
861+
fields
862+
~errmsg:
863+
"Source_map_io.Index.of_json: field 'line' absent or invalid from \
864+
section"
865+
in
866+
let gen_column =
867+
intlit
868+
"column"
869+
fields
870+
~errmsg:
871+
"Source_map_io.Index.of_json: field 'column' absent or invalid from \
872+
section"
873+
in
874+
{ gen_line; gen_column }
875+
| _ ->
876+
invalid_arg "Source_map_io.Index.of_json: 'offset' field of unexpected type"
877+
in
878+
(match List.assoc "url" json with
879+
| _ ->
880+
invalid_arg
881+
"Source_map_io.Index.of_json: URLs in index maps are not currently \
882+
supported"
883+
| exception Not_found -> ());
884+
let map =
885+
try standard_map_of_json (List.assoc "map" json) with
886+
| Not_found -> invalid_arg "Source_map_io.Index.of_json: field 'map' absent"
887+
| Invalid_argument _ ->
888+
invalid_arg "Source_map_io.Index.of_json: invalid sub-map object"
889+
in
890+
offset, `Map map
891+
| _ -> invalid_arg "Source_map_io.Index.of_json: section of unexpected type"
892+
893+
let of_json = function
894+
| `Assoc fields -> (
895+
let string name json = Option.map ~f:string_of_stringlit (stringlit name json) in
896+
let file = string "file" fields in
897+
match List.assoc "sections" fields with
898+
| `List sections ->
899+
let sections = List.map ~f:section_of_json sections in
900+
{ version = 3; file = Option.value file ~default:""; sections }
901+
| _ -> invalid_arg "Source_map_io.Index.of_json: `sections` is not an array"
902+
| exception Not_found ->
903+
invalid_arg "Source_map_io.Index.of_json: no `sections` field")
904+
| _ -> invalid_arg "Source_map_io.of_json: map is not an object"
905+
906+
let to_string m = Yojson.Raw.to_string (json m)
907+
908+
let to_file m file = Yojson.Raw.to_file file (json m)
909+
end
910+
911+
let of_json = function
912+
| `Assoc fields as json -> (
913+
match List.assoc "sections" fields with
914+
| _ -> `Index (Index.of_json json)
915+
| exception Not_found -> `Standard (standard_map_of_json json))
916+
| _ -> invalid_arg "Source_map_io.of_json: map is not an object"
917+
918+
let of_string s = of_json (Yojson.Raw.from_string s)
919+
920+
let of_file f = of_json (Yojson.Raw.from_file f)

compiler/lib/source_map.mli

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,8 +108,16 @@ type t =
108108
}
109109

110110
val filter_map : t -> f:(int -> int option) -> t
111+
(** If [f l] returns [Some l'], map line [l] to [l'] (in the generated file) in
112+
the returned debug mappings. If [f l] returns [None], remove debug mappings
113+
which concern line [l] of the generated file. The time cost of this
114+
function is more than linear in the size of the input mappings. When
115+
possible, prefer using {!val:Mappings.edit}. *)
111116

112117
val merge : t list -> t option
118+
(** Merge two lists of debug mappings. The time cost of the merge is more than
119+
linear in function of the size of the input mappings. When possible, prefer
120+
using {!val:concat}. *)
113121

114122
val empty : filename:string -> t
115123

@@ -119,8 +127,31 @@ val concat : file:string -> sourceroot:string option -> t -> t -> t
119127
union of these mappings for the concatenation of [f1] and [f2], with name
120128
[file] and source root [sourceroot). *)
121129

130+
module Index : sig
131+
type offset =
132+
{ gen_line : int
133+
; gen_column : int
134+
}
135+
136+
type nonrec t =
137+
{ version : int
138+
; file : string
139+
; sections : (offset * [ `Map of t ]) list
140+
(** List of [(offset, map)] pairs. The sourcemap spec allows for [map] to be
141+
either a sourcemap object or a URL, but we don't need to generate
142+
composite sourcemaps with URLs for now, and it is therefore not
143+
implemented. *)
144+
}
145+
146+
val to_string : t -> string
147+
148+
val to_file : t -> string -> unit
149+
end
150+
122151
val to_string : t -> string
123152

124153
val to_file : t -> string -> unit
125154

126-
val of_string : string -> t
155+
val of_string : string -> [ `Standard of t | `Index of Index.t ]
156+
157+
val of_file : string -> [ `Standard of t | `Index of Index.t ]

compiler/tests-compiler/build_path_prefix_map.ml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ let%expect_test _ =
2929
|> compile_cmo_to_javascript ~sourcemap:true ~pretty:false
3030
|> extract_sourcemap
3131
|> function
32-
| Some (sm : Js_of_ocaml_compiler.Source_map.t) ->
32+
| Some (`Standard (sm : Js_of_ocaml_compiler.Source_map.t)) ->
3333
Printf.printf "file: %s\n" sm.file;
3434
Printf.printf "sourceRoot: %s\n" (Option.value ~default:"<none>" sm.sourceroot);
3535
Printf.printf "sources:\n";
3636
List.iter sm.sources ~f:(fun source ->
3737
Printf.printf "- %s\n" (normalize_path source))
38+
| Some (`Index _) -> failwith "unexpected index map"
3839
| None -> failwith "no sourcemap generated!");
3940
[%expect
4041
{|

compiler/tests-compiler/sourcemap.ml

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,8 @@ let%expect_test _ =
5555
print_file (Filetype.path_of_js_file js_file);
5656
match extract_sourcemap js_file with
5757
| None -> Printf.printf "No sourcemap found\n"
58-
| Some sm -> print_mapping sm);
58+
| Some (`Standard sm) -> print_mapping sm
59+
| Some (`Index _) -> failwith "unexpected index map");
5960
[%expect
6061
{|
6162
$ cat "test.ml"
@@ -140,16 +141,19 @@ let%expect_test _ =
140141
; mappings = Source_map.Mappings.encode [ gen (3, 3) (5, 5) 0 ]
141142
}
142143
in
143-
let m = Source_map.merge [ s1; Source_map.filter_map s2 ~f:(fun x -> Some (x + 20)) ] in
144-
(match m with
145-
| None -> ()
146-
| Some sm ->
147-
let encoded_mappings = sm.Source_map.mappings in
148-
print_endline (Source_map.Mappings.to_string encoded_mappings);
149-
print_mapping sm);
144+
let edits =
145+
Source_map.Line_edits.([ Add { count = 17 } ] @ List.init ~len:3 ~f:(Fun.const Keep))
146+
in
147+
let s2 =
148+
{ s2 with mappings = Source_map.Mappings.edit ~strict:true s2.mappings edits }
149+
in
150+
let m = Source_map.concat ~file:"" ~sourceroot:None s1 s2 in
151+
let encoded_mappings = m.Source_map.mappings in
152+
print_endline (Source_map.Mappings.to_string encoded_mappings);
153+
print_mapping m;
150154
[%expect
151155
{|
152-
CASU;;GCUU;;;;;;;;;;;;;;;;;;;;GCff
156+
CASU;;GCUU;;;;;;;;;;;;;;;;;;;;GCff;
153157
sa:10:10 -> 1:1
154158
sb:20:20 -> 3:3
155159
sa2:5:5 -> 23:3 |}]

compiler/tests-compiler/util/util.mli

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,12 @@ val compile_bc_to_javascript :
5353
val jsoo_minify :
5454
?flags:string list -> pretty:bool -> Filetype.js_file -> Filetype.js_file
5555

56-
val extract_sourcemap : Filetype.js_file -> Js_of_ocaml_compiler.Source_map.t option
56+
val extract_sourcemap :
57+
Filetype.js_file
58+
-> [ `Standard of Js_of_ocaml_compiler.Source_map.t
59+
| `Index of Js_of_ocaml_compiler.Source_map.Index.t
60+
]
61+
option
5762

5863
val run_javascript : Filetype.js_file -> string
5964

compiler/tests-sourcemap/dump_sourcemap.ml

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ let extract_sourcemap lines =
3636
Some (Source_map.of_string content)
3737
| _ -> None
3838

39-
let print_mapping lines (sm : Source_map.t) =
39+
let print_mapping lines ?(line_offset = 0) (sm : Source_map.t) =
4040
let lines = Array.of_list lines in
4141
let sources = Array.of_list sm.sources in
4242
let _names = Array.of_list sm.names in
@@ -68,9 +68,18 @@ let print_mapping lines (sm : Source_map.t) =
6868
ori_line
6969
ori_col
7070
gen_col
71-
(mark gen_col lines.(gen_line - 1))
71+
(mark gen_col lines.(gen_line - 1 + line_offset))
7272
| _ -> ()))
7373

74+
let print_sourcemap lines = function
75+
| `Standard sm -> print_mapping lines sm
76+
| `Index l ->
77+
List.iter
78+
l.Source_map.Index.sections
79+
~f:(fun (Source_map.Index.{ gen_line; gen_column }, `Map sm) ->
80+
assert (gen_column = 0);
81+
print_mapping lines ~line_offset:gen_line sm)
82+
7483
let files = Sys.argv |> Array.to_list |> List.tl
7584

7685
let () =
@@ -80,4 +89,4 @@ let () =
8089
| None -> Printf.printf "not sourcemap for %s\n" f
8190
| Some sm ->
8291
Printf.printf "sourcemap for %s\n" f;
83-
print_mapping lines sm)
92+
print_sourcemap lines sm)

0 commit comments

Comments
 (0)