Skip to content

Commit ab355a0

Browse files
committed
Fix bug in format of sourceContents field, cleaner interface
1 parent ac75d97 commit ab355a0

File tree

7 files changed

+79
-117
lines changed

7 files changed

+79
-117
lines changed

compiler/bin-js_of_ocaml/cmd_arg.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -321,7 +321,7 @@ let options =
321321
; sources_contents =
322322
(if sourcemap_don't_inline_content
323323
then None
324-
else Some (Source_map.Sources_contents.encode []))
324+
else Some [])
325325
; names = []
326326
; mappings = Source_map.Mappings.empty
327327
} )
@@ -563,7 +563,7 @@ let options_runtime_only =
563563
; sources_contents =
564564
(if sourcemap_don't_inline_content
565565
then None
566-
else Some (Source_map.Sources_contents.encode []))
566+
else Some [])
567567
; names = []
568568
; mappings = Source_map.Mappings.empty
569569
} )

compiler/bin-js_of_ocaml/link.ml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ let options =
106106
; file
107107
; sourceroot = sourcemap_root
108108
; sources = []
109-
; sources_contents = Some (Source_map.Sources_contents.encode [])
109+
; sources_contents = Some []
110110
; names = []
111111
; mappings = Source_map.Mappings.empty
112112
} )

compiler/lib/js_output.ml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1926,7 +1926,7 @@ let program ?(accept_unnamed_var = false) f ?source_map p =
19261926
loop xs ys
19271927
in
19281928
let sources_contents =
1929-
Option.map ~f:Source_map.Sources_contents.decode sm.sources_contents
1929+
Option.map ~f:(List.map ~f:Source_map.Source_text.decode) sm.sources_contents
19301930
in
19311931
loop sm.sources (Option.value ~default:[] sources_contents);
19321932
List.iter sm.Source_map.names ~f:(fun f ->
@@ -1985,7 +1985,7 @@ let program ?(accept_unnamed_var = false) f ?source_map p =
19851985
let sources_contents =
19861986
let open Option.Syntax in
19871987
let* r = contents in
1988-
Option.return (Source_map.Sources_contents.encode (List.rev !r))
1988+
Option.return (List.map ~f:Source_map.Source_text.encode (List.rev !r))
19891989
in
19901990
let sources =
19911991
List.map sources ~f:(fun filename ->

compiler/lib/link_js.ml

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -470,10 +470,7 @@ let link ~output ~linkall ~mklib ~toplevel ~files ~resolve_sourcemap_url ~source
470470
in
471471
let merged_sourcemap =
472472
let open Source_map in
473-
assert (
474-
match init_sm.mappings with
475-
| Uninterpreted "" -> true
476-
| _ -> false);
473+
assert (String.equal (Mappings.to_string init_sm.mappings) "");
477474
{ version = init_sm.version
478475
; file = init_sm.file
479476
; Index.sections =

compiler/lib/source_map.ml

Lines changed: 30 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -58,27 +58,14 @@ module Line_edits = struct
5858
let pp fmt = Format.(pp_print_list pp_action fmt)
5959
end
6060

61-
module Mappings : sig
62-
type t = private Uninterpreted of string [@@unboxed]
63-
64-
external uninterpreted : string -> t = "%identity"
65-
66-
val empty : t
67-
68-
val decode : t -> map list
69-
70-
val encode : map list -> t
71-
72-
val edit : strict:bool -> t -> Line_edits.t -> t
73-
74-
(* Not for export *)
75-
val concat : source_count1:int -> name_count1:int -> t -> t -> t
76-
end = struct
61+
module Mappings = struct
7762
type t = Uninterpreted of string [@@unboxed]
7863

7964
let empty = Uninterpreted ""
8065

81-
external uninterpreted : string -> t = "%identity"
66+
external of_string : string -> t = "%identity"
67+
68+
external to_string : t -> string = "%identity"
8269

8370
let update_carries_from_segment
8471
~carry_source
@@ -573,71 +560,43 @@ end = struct
573560
readline 1 0 []
574561
end
575562

576-
module Sources_contents : sig
577-
type t = private Uninterpreted of string [@@unboxed]
563+
module Source_text = struct
564+
type t = Uninterpreted of string [@@unboxed]
578565

579-
external uninterpreted : string -> t = "%identity"
566+
external of_json_string : string -> t = "%identity"
580567

581-
val decode : t -> string option list
568+
external to_json_string : t -> string = "%identity"
569+
570+
let to_json =
571+
function
572+
| None -> `Null
573+
| Some text -> `String text
582574

583-
val encode : string option list -> t
584-
end = struct
585-
type t = Uninterpreted of string [@@unboxed]
575+
let encode t =
576+
let json = Yojson.Basic.to_string (to_json t) in
577+
Uninterpreted json
586578

587-
external uninterpreted : string -> t = "%identity"
588-
589-
let to_json (cs : string option list) =
590-
`List
591-
(List.map
592-
~f:(function
593-
| None -> `Null
594-
| Some s -> `String s)
595-
cs)
596-
597-
let encode cs =
598-
(* There are two stages to the encoding. First encoding the list as a JSON
599-
array of strings... *)
600-
let array = Yojson.Basic.to_string (to_json cs) in
601-
(* ... and then reifying that array itself as a string, under the form of a
602-
JSON string literal. *)
603-
let reified = Yojson.Basic.to_string (`String array) in
604-
Uninterpreted reified
605-
606-
let of_json json =
607-
match json with
608-
| `List l ->
609-
List.map
610-
~f:(function
611-
| `String s -> Some s
612-
| `Null -> None
613-
| _ -> invalid_arg "Source_map.Sources_contents.of_json")
614-
l
615-
| _ -> invalid_arg "Source_map.Sources_contents.of_json"
616-
617-
let decode (Uninterpreted s) : string option list =
579+
let of_json =
580+
function
581+
| `String s -> Some s
582+
| `Null -> None
583+
| _ -> invalid_arg "Source_map.Sources_contents.of_json: expected string or null"
584+
585+
let decode (Uninterpreted s) : string option =
618586
(* The two stages of the encoding, in reverse. *)
619-
match Yojson.Basic.from_string s with
620-
| `String array -> (
621-
try of_json (Yojson.Basic.from_string array)
622-
with Yojson.Json_error s ->
623-
invalid_arg
624-
("Source_map.Sources_contents.decode: This is a valid JSON literal, but it \
625-
does not encode a JSON array: "
626-
^ s))
627-
| _ ->
628-
invalid_arg
629-
"Source_map.Sources_contents.decode: This is a valid JSON object but not a \
630-
string literal"
631-
| exception Yojson.Json_error s ->
632-
invalid_arg ("Source_map.Sources_contents.decode: not a JSON string literal: " ^ s)
587+
try of_json (Yojson.Basic.from_string s) with
588+
| Yojson.Json_error s ->
589+
invalid_arg
590+
("Source_map.Sources_contents.decode: This is not a valid JSON object: "
591+
^ s)
633592
end
634593

635594
type t =
636595
{ version : int
637596
; file : string
638597
; sourceroot : string option
639598
; sources : string list
640-
; sources_contents : Sources_contents.t option
599+
; sources_contents : Source_text.t list option
641600
; names : string list
642601
; mappings : Mappings.t
643602
}
@@ -662,10 +621,7 @@ let concat ~file ~sourceroot s1 s2 =
662621
; sources_contents =
663622
(match s1.sources_contents, s2.sources_contents with
664623
| None, contents | contents, None -> contents
665-
| Some c1, Some c2 ->
666-
let c1 = Sources_contents.decode c1 in
667-
let c2 = Sources_contents.decode c2 in
668-
Some (Sources_contents.encode (c1 @ c2)))
624+
| Some c1, Some c2 -> Some (c1 @ c2))
669625
; names = s1.names @ s2.names
670626
; mappings =
671627
Mappings.concat

compiler/lib/source_map.mli

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -52,52 +52,53 @@ module Line_edits : sig
5252
end
5353

5454
module Mappings : sig
55-
(** Left uninterpreted, since many operations can be performed efficiently directly
56-
on the encoded form. Instances of [t] produced by {!val:encode} are
57-
guaranteed to be valid JSON string literals (surrounding double quotes
58-
included). *)
59-
type t = private Uninterpreted of string [@@unboxed]
55+
type t
6056

6157
val empty : t
6258
(** Represents the empty mapping. *)
6359

64-
external uninterpreted : string -> t = "%identity"
65-
(** Create a value of type {!type:t} from a string, without attempting to
66-
decode it. *)
60+
val of_string : string -> t
61+
(** By default, mappings are left uninterpreted, since many operations can be
62+
performed efficiently directly on the encoded form. It is guaranteed that
63+
{!val:of_string} and {!val:to_string} are inverse functions. *)
6764

6865
val decode : t -> map list
6966

7067
val encode : map list -> t
7168

69+
val to_string : t -> string
70+
(** Returns the mappings as a string in the Source map v3 format. *)
71+
7272
val edit : strict:bool -> t -> Line_edits.t -> t
7373
(** Apply line edits in order. If the number of {!const:Line_edits.Keep} and
7474
{!const:Line_edits.Drop} actions does not match the number of lines in
7575
the domain of the input mapping, only the lines affected by an edit are
7676
included in the result. *)
7777
end
7878

79-
module Sources_contents : sig
80-
(** Left uninterpreted by default as decoding this field can be costly if the
81-
amount of code is large, and is seldom required. Instances of [t]
82-
produced by {!val:encode} are guaranteed to be valid JSON string
83-
literals (surrounding double quotes included). *)
84-
type t = private Uninterpreted of string [@@unboxed]
79+
module Source_text : sig
80+
type t
81+
82+
val of_json_string : string -> t
83+
(** By default, sources contents are left uninterpreted as decoding this field can be
84+
costly if the amount of code is large, and is seldom required. It is guaranteed that
85+
{!val:of_json_string} and {!val:to_json_string} are inverse functions. *)
8586

86-
external uninterpreted : string -> t = "%identity"
87-
(** Create a value of type {!type:t} from a string, without attempting to
88-
decode it. *)
87+
val decode : t -> string option
8988

90-
val decode : t -> string option list
89+
val encode : string option -> t
9190

92-
val encode : string option list -> t
91+
val to_json_string : t -> string
92+
(** Returns a valid JSON object (in this instance, a string literal, double quotes
93+
included) representing the source text. *)
9394
end
9495

9596
type t =
9697
{ version : int
9798
; file : string
9899
; sourceroot : string option
99100
; sources : string list
100-
; sources_contents : Sources_contents.t option
101+
; sources_contents : Source_text.t list option
101102
(** Left uninterpreted by default, since decoding it requires to handle special
102103
characters, which can be costly for huge codebases. *)
103104
; names : string list
@@ -113,10 +114,7 @@ val concat : file:string -> sourceroot:string option -> t -> t -> t
113114
(** If [s1] encodes a mapping for a generated file [f1], and [s2] for a
114115
generated file [f2], then [concat ~file ~sourceroot s1 s2] encodes the
115116
union of these mappings for the concatenation of [f1] and [f2], with name
116-
[file] and source root [sourceroot). Note that at the moment, this function
117-
can be slow when the [sources_contents] field contains very large
118-
codebases, as it decodes the whole source text. This may be fixed in the
119-
future. *)
117+
[file] and source root [sourceroot). *)
120118

121119
module Index : sig
122120
type offset =

compiler/lib/source_map_io.yojson.ml

Lines changed: 21 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,9 @@ let rewrite_path path =
3131
let stringlit_of_string s = `Stringlit (Yojson.Basic.to_string (`String s))
3232

3333
let json t =
34-
let (Source_map.Mappings.Uninterpreted mappings) = t.mappings in
34+
let mappings =
35+
`Stringlit ("\"" ^ Mappings.to_string t.mappings ^ "\"") (* Nothing to escape *)
36+
in
3537
let fields =
3638
[ "version", `Intlit (Int.to_string t.version)
3739
; "file", stringlit_of_string (rewrite_path t.file)
@@ -43,18 +45,17 @@ let json t =
4345
; "names", `List (List.map (fun s -> stringlit_of_string s) t.names)
4446
; ( "sources"
4547
, `List (List.map (fun s -> stringlit_of_string (rewrite_path s)) t.sources) )
46-
; "mappings", `Stringlit ("\"" ^ mappings ^ "\"") (* Nothing to escape *)
48+
; "mappings", mappings
4749
]
4850
in
4951
match t.sources_contents with
5052
| None -> `Assoc fields
51-
| Some (Source_map.Sources_contents.Uninterpreted cs) ->
53+
| Some cs ->
5254
`Assoc
5355
(fields
5456
@ [ ( "sourcesContent"
55-
(* It is the job of {!mod:Sources_contents} to enforce that [cs] is
56-
already a valid JSON string literal *)
57-
, `Stringlit cs )
57+
, `List (List.map (fun t -> `Stringlit (Source_text.to_json_string t))
58+
cs) )
5859
])
5960

6061
let invalid () = invalid_arg "Source_map.of_json"
@@ -89,6 +90,17 @@ let stringlit_opt name assoc =
8990
| `Stringlit s -> Some s
9091
| _ | (exception Not_found) -> None
9192

93+
let stringlit_list_opt name assoc =
94+
match List.assoc name assoc with
95+
| `List l ->
96+
Some (List.map
97+
(function
98+
| `Stringlit lit -> lit
99+
| _ -> invalid ())
100+
l)
101+
| _ -> invalid ()
102+
| exception Not_found -> None
103+
92104
let of_json json =
93105
match json with
94106
| `Assoc (("version", version) :: rest) ->
@@ -105,7 +117,7 @@ let of_json json =
105117
let sourceroot = string "sourceRoot" rest in
106118
let names = list_string "names" rest in
107119
let sources = list_string "sources" rest in
108-
let sources_contents = stringlit_opt "sourcesContent" rest in
120+
let sources_contents = stringlit_list_opt "sourcesContent" rest in
109121
let mappings = stringlit_opt "mappings" rest in
110122
let mappings =
111123
Option.map
@@ -114,15 +126,14 @@ let of_json json =
114126
String.length mappings >= 2
115127
&& Char.equal mappings.[0] '"'
116128
&& Char.equal mappings.[String.length mappings - 1] '"');
117-
let mappings = String.sub mappings 1 (String.length mappings - 2) in
118-
Mappings.uninterpreted mappings)
129+
Mappings.of_string (String.sub mappings 1 (String.length mappings - 2)))
119130
mappings
120131
in
121132
{ version = 3
122133
; file = Option.value file ~default:""
123134
; sourceroot
124135
; names = Option.value names ~default:[]
125-
; sources_contents = Option.map Sources_contents.uninterpreted sources_contents
136+
; sources_contents = Option.map (List.map Source_text.of_json_string) sources_contents
126137
; sources = Option.value sources ~default:[]
127138
; mappings = Option.value mappings ~default:Mappings.empty
128139
}

0 commit comments

Comments
 (0)