diff --git a/.ocamlformat-ignore b/.ocamlformat-ignore new file mode 100644 index 00000000000..9c4bafda2d5 --- /dev/null +++ b/.ocamlformat-ignore @@ -0,0 +1 @@ +compiler/flow_parser/** diff --git a/CHANGELOG.md b/CHANGELOG.md index 5a96fe68590..8de7cc3c5e3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -57,6 +57,7 @@ #### :house: Internal +- Vendor the Flow parser 0.267.0 sources used by the compiler, removing the external `flow_parser` dependency and establishing a maintained baseline for future OCaml upgrades. https://github.com/rescript-lang/rescript/pull/8587 - Store processed external declarations as structured data instead of serialized values in `pval_prim`, and lower external calls during Lambda translation. This removes `Pccall`, `external_spec`, and the unsupported `%absfloat` primitive. The AST, CMI, and CMT magic numbers are bumped (`ResImpl01301`/`ResIntf01301`, `Caml1999I025`, `Caml1999T026`). https://github.com/rescript-lang/rescript/pull/8581 - Resolve dynamic-import targets during Lambda translation and store the module and export path directly in `Pimport`. This removes the `dynamic_import` flags from `Pjs_call` and `Lglobal_module`, along with backend expression-shape detection. https://github.com/rescript-lang/rescript/pull/8582 - Give nominal variants one canonical runtime layout: compute their JavaScript representation once after typing each declaration, replace positional constructor tags with semantic runtime descriptors, and make construction, matching, coercion, printing, diagnostics, and GenType consume the stored representation instead of reinterpreting annotations. Pattern matching keeps occurrence-specific plans local without adding another Lambda or Lam expression form. https://github.com/rescript-lang/rescript/pull/8579 diff --git a/compiler/flow_parser/LICENSE b/compiler/flow_parser/LICENSE new file mode 100644 index 00000000000..b93be90515c --- /dev/null +++ b/compiler/flow_parser/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) Meta Platforms, Inc. and affiliates. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/compiler/flow_parser/README.md b/compiler/flow_parser/README.md new file mode 100644 index 00000000000..fd45180bba8 --- /dev/null +++ b/compiler/flow_parser/README.md @@ -0,0 +1,21 @@ +# Vendored Flow parser + +This directory contains the OCaml Flow parser used by the ReScript compiler. + +- Upstream fork: https://github.com/rescript-lang/flow +- Flow parser version: `0.267.0` +- Source commit: `9ea4062c0b7e037415c4413a7634c459ebd5c31b` +- Original source directories: `src/parser`, `src/third-party/sedlex`, + `src/third-party/sedlex-ppx`, and `src/hack_forked/utils/collections` + +The Dune files were adapted to build these sources as private libraries inside +the ReScript repository. Sources used only by the upstream JavaScript and C API +targets are not included. One ambiguous Sedlex documentation comment was +converted to a regular comment so the vendored sources build with ReScript's +warning settings. + +Vendored sources are excluded from the repository-wide OCamlformat check so +that they remain comparable with their upstream versions. + +The Flow sources are licensed under the MIT licence in `LICENSE`. Vendored +Sedlex and collections sources retain their own licence files. diff --git a/compiler/flow_parser/collections/dune b/compiler/flow_parser/collections/dune new file mode 100644 index 00000000000..d4000098831 --- /dev/null +++ b/compiler/flow_parser/collections/dune @@ -0,0 +1,6 @@ +(include_subdirs unqualified) + +(library + (name collections) + (wrapped false) + (libraries base)) diff --git a/compiler/flow_parser/collections/iMap.ml b/compiler/flow_parser/collections/iMap.ml new file mode 100644 index 00000000000..93c66823ebc --- /dev/null +++ b/compiler/flow_parser/collections/iMap.ml @@ -0,0 +1,13 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +include WrappedMap.Make (IntKey) + +let pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit = + (fun pp_data -> make_pp IntKey.pp pp_data) + +let show pp_data x = Format.asprintf "%a" (pp pp_data) x diff --git a/compiler/flow_parser/collections/iSet.ml b/compiler/flow_parser/collections/iSet.ml new file mode 100644 index 00000000000..471f51dda4a --- /dev/null +++ b/compiler/flow_parser/collections/iSet.ml @@ -0,0 +1,14 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +include Flow_set.Make (IntKey) + +let pp = make_pp IntKey.pp + +let show iset = Format.asprintf "%a" pp iset + +let to_string = show diff --git a/compiler/flow_parser/collections/immQueue.ml b/compiler/flow_parser/collections/immQueue.ml new file mode 100644 index 00000000000..3f326a45855 --- /dev/null +++ b/compiler/flow_parser/collections/immQueue.ml @@ -0,0 +1,54 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type 'a t = { + incoming: 'a list; + outgoing: 'a list; + length: int; +} + +let empty = { incoming = []; outgoing = []; length = 0 } + +let length t = t.length + +let is_empty t = length t = 0 + +let push t x = { t with incoming = x :: t.incoming; length = t.length + 1 } + +let prepare_for_read t = + match t.outgoing with + | [] -> { t with incoming = []; outgoing = List.rev t.incoming } + | _ -> t + +let pop t = + let t = prepare_for_read t in + match t.outgoing with + | [] -> (None, t) + | hd :: tl -> (Some hd, { t with outgoing = tl; length = t.length - 1 }) + +let peek t = + let t = prepare_for_read t in + match t.outgoing with + | [] -> (None, t) + | hd :: _ -> (Some hd, t) + +let exists t ~f = List.exists f t.outgoing || List.exists f t.incoming + +let iter t ~f = + List.iter f t.outgoing; + List.iter f (List.rev t.incoming) + +let from_list x = { incoming = []; outgoing = x; length = List.length x } + +let to_list x = x.outgoing @ List.rev x.incoming + +let concat t = + { + incoming = []; + outgoing = Base.List.concat_map ~f:to_list t; + length = List.map (fun u -> u.length) t |> List.fold_left ( + ) 0; + } diff --git a/compiler/flow_parser/collections/immQueue.mli b/compiler/flow_parser/collections/immQueue.mli new file mode 100644 index 00000000000..67743a82b5e --- /dev/null +++ b/compiler/flow_parser/collections/immQueue.mli @@ -0,0 +1,37 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* + * Immutable queue implementation. Modeled loosely after the mutable stdlib + * Queue. push, pop, etc. are amortized O(1). + *) + +type 'a t + +val empty : 'a t + +val push : 'a t -> 'a -> 'a t + +val pop : 'a t -> 'a option * 'a t + +val peek : 'a t -> 'a option * 'a t + +val is_empty : 'a t -> bool + +val length : 'a t -> int + +val exists : 'a t -> f:('a -> bool) -> bool + +val iter : 'a t -> f:('a -> unit) -> unit + +(* from_list: the head of the list is the first one to be popped *) +val from_list : 'a list -> 'a t + +(* to_list: the head of the list is the first one to be popped *) +val to_list : 'a t -> 'a list + +val concat : 'a t list -> 'a t diff --git a/compiler/flow_parser/collections/intKey.ml b/compiler/flow_parser/collections/intKey.ml new file mode 100644 index 00000000000..4701376db44 --- /dev/null +++ b/compiler/flow_parser/collections/intKey.ml @@ -0,0 +1,12 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type t = int + +let compare = ( - ) + +let pp = Format.pp_print_int diff --git a/compiler/flow_parser/collections/priorityQueue.ml b/compiler/flow_parser/collections/priorityQueue.ml new file mode 100644 index 00000000000..cc2b52866f8 --- /dev/null +++ b/compiler/flow_parser/collections/priorityQueue.ml @@ -0,0 +1,101 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Make (Ord : Set.OrderedType) = struct + type elt = Ord.t + + type t = { + mutable __queue: elt option array; + mutable size: int; + } + + let rec make_empty n = { __queue = Array.make n None; size = 0 } + + and is_empty t = t.size = 0 + + and pop t = + if t.size = 0 then failwith "Popping from an empty priority queue"; + let v = t.__queue.(0) in + t.size <- t.size - 1; + + if t.size <> 0 then ( + let last = t.__queue.(t.size) in + t.__queue.(t.size) <- None; + __bubble_down t.__queue t.size last 0 + ); + + match v with + | None -> failwith "Attempting to return a null value" + | Some v -> v + + and push t element = + if Array.length t.__queue = t.size then ( + let new_queue = Array.make ((Array.length t.__queue * 2) + 1) None in + Array.blit t.__queue 0 new_queue 0 (Array.length t.__queue); + t.__queue <- new_queue + ); + + t.__queue.(t.size) <- Some element; + __bubble_up t.__queue t.size; + t.size <- t.size + 1; + () + + and __swap arr i j = + let tmp = arr.(i) in + arr.(i) <- arr.(j); + arr.(j) <- tmp + + and __bubble_up arr index = + if index = 0 then (); + let pindex = (index - 1) / 2 in + match (arr.(index), arr.(pindex)) with + | (None, _) + | (_, None) -> + failwith "Unexpected null index found when calling __bubble_up" + | (Some e, Some p) -> + if Ord.compare e p < 0 then ( + __swap arr index pindex; + __bubble_up arr pindex + ) + + and __bubble_down arr size value index = + let right_child_index = (index * 2) + 2 in + let left_child_index = right_child_index - 1 in + if right_child_index < size then + match (arr.(right_child_index), arr.(left_child_index), value) with + | (None, _, _) + | (_, None, _) + | (_, _, None) -> + failwith "Unexpected null index found when calling __bubble_down" + | (Some r, Some l, Some v) -> + let (smaller_child, smaller_child_index) = + if Ord.compare r l < 0 then + (r, right_child_index) + else + (l, left_child_index) + in + if Ord.compare v smaller_child <= 0 then + arr.(index) <- value + else ( + arr.(index) <- arr.(smaller_child_index); + __bubble_down arr size value smaller_child_index + ) + else if left_child_index < size then + match (arr.(left_child_index), value) with + | (None, _) + | (_, None) -> + failwith "Unexpected null index found when calling __bubble_down" + | (Some l, Some v) -> + if Ord.compare v l <= 0 then + arr.(index) <- value + else ( + arr.(index) <- arr.(left_child_index); + arr.(left_child_index) <- value + ) + else + arr.(index) <- value +end diff --git a/compiler/flow_parser/collections/reordered_argument_collections.ml b/compiler/flow_parser/collections/reordered_argument_collections.ml new file mode 100644 index 00000000000..052743beb93 --- /dev/null +++ b/compiler/flow_parser/collections/reordered_argument_collections.ml @@ -0,0 +1,94 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Reordered_argument_map (S : WrappedMap.S) = struct + include S + + let add m ~key ~data = add key data m + + let filter m ~f = filter f m + + let fold m ~init ~f = fold f m init + + let find_opt m k = find_opt k m + + let find m k = find k m + + let iter m ~f = iter f m + + let map m ~f = map f m + + let mapi m ~f = mapi f m + + let mem m v = mem v m + + let remove m v = remove v m + + let exists m ~f = exists f m + + let merge m1 m2 ~f = merge f m1 m2 + + let filter m ~f = filter m ~f + + let partition m ~f = partition f m +end + +module Reordered_argument_set (S : Flow_set.S) = struct + include S + + let add s v = add v s + + let filter s ~f = filter f s + + let fold s ~init ~f = fold f s init + + let iter s ~f = iter f s + + let mem s v = mem v s + + let remove s v = remove v s + + let exists s ~f = exists f s + + let of_list l = List.fold_left add S.empty l + + let make_pp pp fmt x = + Format.fprintf fmt "@[{"; + let elts = elements x in + (match elts with + | [] -> () + | _ -> Format.fprintf fmt " "); + ignore + (List.fold_left + (fun sep elt -> + if sep then Format.fprintf fmt ";@ "; + let () = pp fmt elt in + true) + false + elts + ); + (match elts with + | [] -> () + | _ -> Format.fprintf fmt " "); + Format.fprintf fmt "}@]" +end + +module SSet = struct + include Reordered_argument_set (SSet) + + let pp = SSet.pp + + let show = SSet.show +end + +module SMap = struct + include Reordered_argument_map (SMap) + + let pp = SMap.pp + + let show = SMap.show +end diff --git a/compiler/flow_parser/collections/sMap.ml b/compiler/flow_parser/collections/sMap.ml new file mode 100644 index 00000000000..e179a06c9c1 --- /dev/null +++ b/compiler/flow_parser/collections/sMap.ml @@ -0,0 +1,13 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +include WrappedMap.Make (StringKey) + +let pp : (Format.formatter -> 'a -> unit) -> Format.formatter -> 'a t -> unit = + (fun pp_data -> make_pp StringKey.pp pp_data) + +let show pp_data x = Format.asprintf "%a" (pp pp_data) x diff --git a/compiler/flow_parser/collections/sSet.ml b/compiler/flow_parser/collections/sSet.ml new file mode 100644 index 00000000000..b197a0f2bd7 --- /dev/null +++ b/compiler/flow_parser/collections/sSet.ml @@ -0,0 +1,14 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +include Flow_set.Make (StringKey) + +let pp = make_pp StringKey.pp + +let show sset = Format.asprintf "%a" pp sset + +let to_string = show diff --git a/compiler/flow_parser/collections/stringKey.ml b/compiler/flow_parser/collections/stringKey.ml new file mode 100644 index 00000000000..777cbda1ba6 --- /dev/null +++ b/compiler/flow_parser/collections/stringKey.ml @@ -0,0 +1,14 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type t = string + +let compare (x : t) (y : t) = String.compare x y + +let to_string x = x + +let pp fmt x = Format.fprintf fmt "%S" x diff --git a/compiler/flow_parser/collections/third-party/LICENSE b/compiler/flow_parser/collections/third-party/LICENSE new file mode 100644 index 00000000000..3666ebe1556 --- /dev/null +++ b/compiler/flow_parser/collections/third-party/LICENSE @@ -0,0 +1,203 @@ +In the following, "the OCaml Core System" refers to all files marked +"Copyright INRIA" in this distribution. + +The OCaml Core System is distributed under the terms of the +GNU Lesser General Public License (LGPL) version 2.1 (included below). + +As a special exception to the GNU Lesser General Public License, you +may link, statically or dynamically, a "work that uses the OCaml Core +System" with a publicly distributed version of the OCaml Core System +to produce an executable file containing portions of the OCaml Core +System, and distribute that executable file under terms of your +choice, without any of the additional requirements listed in clause 6 +of the GNU Lesser General Public License. By "a publicly distributed +version of the OCaml Core System", we mean either the unmodified OCaml +Core System as distributed by INRIA, or a modified version of the +OCaml Core System that is distributed under the conditions defined in +clause 2 of the GNU Lesser General Public License. This exception +does not however invalidate any other reasons why the executable file +might be covered by the GNU Lesser General Public License. + +---------------------------------------------------------------------- + +GNU LESSER GENERAL PUBLIC LICENSE + +Version 2.1, February 1999 + +Copyright (C) 1991, 1999 Free Software Foundation, Inc. +51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +Everyone is permitted to copy and distribute verbatim copies +of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + +Preamble + +The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public Licenses are intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. + +This license, the Lesser General Public License, applies to some specially designated software packages--typically libraries--of the Free Software Foundation and other authors who decide to use it. You can use it too, but we suggest you first think carefully about whether this license or the ordinary General Public License is the better strategy to use in any particular case, based on the explanations below. + +When we speak of free software, we are referring to freedom of use, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish); that you receive source code or can get it if you want it; that you can change the software and use pieces of it in new free programs; and that you are informed that you can do these things. + +To protect your rights, we need to make restrictions that forbid distributors to deny you these rights or to ask you to surrender these rights. These restrictions translate to certain responsibilities for you if you distribute copies of the library or if you modify it. + +For example, if you distribute copies of the library, whether gratis or for a fee, you must give the recipients all the rights that we gave you. You must make sure that they, too, receive or can get the source code. If you link other code with the library, you must provide complete object files to the recipients, so that they can relink them with the library after making changes to the library and recompiling it. And you must show them these terms so they know their rights. + +We protect your rights with a two-step method: (1) we copyright the library, and (2) we offer you this license, which gives you legal permission to copy, distribute and/or modify the library. + +To protect each distributor, we want to make it very clear that there is no warranty for the free library. Also, if the library is modified by someone else and passed on, the recipients should know that what they have is not the original version, so that the original author's reputation will not be affected by problems that might be introduced by others. + +Finally, software patents pose a constant threat to the existence of any free program. We wish to make sure that a company cannot effectively restrict the users of a free program by obtaining a restrictive license from a patent holder. Therefore, we insist that any patent license obtained for a version of the library must be consistent with the full freedom of use specified in this license. + +Most GNU software, including some libraries, is covered by the ordinary GNU General Public License. This license, the GNU Lesser General Public License, applies to certain designated libraries, and is quite different from the ordinary General Public License. We use this license for certain libraries in order to permit linking those libraries into non-free programs. + +When a program is linked with a library, whether statically or using a shared library, the combination of the two is legally speaking a combined work, a derivative of the original library. The ordinary General Public License therefore permits such linking only if the entire combination fits its criteria of freedom. The Lesser General Public License permits more lax criteria for linking other code with the library. + +We call this license the "Lesser" General Public License because it does Less to protect the user's freedom than the ordinary General Public License. It also provides other free software developers Less of an advantage over competing non-free programs. These disadvantages are the reason we use the ordinary General Public License for many libraries. However, the Lesser license provides advantages in certain special circumstances. + +For example, on rare occasions, there may be a special need to encourage the widest possible use of a certain library, so that it becomes a de-facto standard. To achieve this, non-free programs must be allowed to use the library. A more frequent case is that a free library does the same job as widely used non-free libraries. In this case, there is little to gain by limiting the free library to free software only, so we use the Lesser General Public License. + +In other cases, permission to use a particular library in non-free programs enables a greater number of people to use a large body of free software. For example, permission to use the GNU C Library in non-free programs enables many more people to use the whole GNU operating system, as well as its variant, the GNU/Linux operating system. + +Although the Lesser General Public License is Less protective of the users' freedom, it does ensure that the user of a program that is linked with the Library has the freedom and the wherewithal to run that program using a modified version of the Library. + +The precise terms and conditions for copying, distribution and modification follow. Pay close attention to the difference between a "work based on the library" and a "work that uses the library". The former contains code derived from the library, whereas the latter must be combined with the library in order to run. + +TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + +0. This License Agreement applies to any software library or other program which contains a notice placed by the copyright holder or other authorized party saying it may be distributed under the terms of this Lesser General Public License (also called "this License"). Each licensee is addressed as "you". + +A "library" means a collection of software functions and/or data prepared so as to be conveniently linked with application programs (which use some of those functions and data) to form executables. + +The "Library", below, refers to any such software library or work which has been distributed under these terms. A "work based on the Library" means either the Library or any derivative work under copyright law: that is to say, a work containing the Library or a portion of it, either verbatim or with modifications and/or translated straightforwardly into another language. (Hereinafter, translation is included without limitation in the term "modification".) + +"Source code" for a work means the preferred form of the work for making modifications to it. For a library, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the library. + +Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running a program using the Library is not restricted, and output from such a program is covered only if its contents constitute a work based on the Library (independent of the use of the Library in a tool for writing it). Whether that is true depends on what the Library does and what the program that uses the Library does. + +1. You may copy and distribute verbatim copies of the Library's complete source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and distribute a copy of this License along with the Library. + +You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. + +2. You may modify your copy or copies of the Library or any portion of it, thus forming a work based on the Library, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + b) You must cause the files modified to carry prominent notices stating that you changed the files and the date of any change. + c) You must cause the whole of the work to be licensed at no charge to all third parties under the terms of this License. + d) If a facility in the modified Library refers to a function or a table of data to be supplied by an application program that uses the facility, other than as an argument passed when the facility is invoked, then you must make a good faith effort to ensure that, in the event an application does not supply such function or table, the facility still operates, and performs whatever part of its purpose remains meaningful. + + (For example, a function in a library to compute square roots has a purpose that is entirely well-defined independent of the application. Therefore, Subsection 2d requires that any application-supplied function or table used by this function must be optional: if the application does not supply it, the square root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Library, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Library, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library with the Library (or with a work based on the Library) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. + +3. You may opt to apply the terms of the ordinary GNU General Public License instead of this License to a given copy of the Library. To do this, you must alter all the notices that refer to this License, so that they refer to the ordinary GNU General Public License, version 2, instead of to this License. (If a newer version than version 2 of the ordinary GNU General Public License has appeared, then you can specify that version instead if you wish.) Do not make any other change in these notices. + +Once this change is made in a given copy, it is irreversible for that copy, so the ordinary GNU General Public License applies to all subsequent copies and derivative works made from that copy. + +This option is useful when you wish to copy part of the code of the Library into a program that is not a library. + +4. You may copy and distribute the Library (or a portion or derivative of it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange. + +If distribution of object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place satisfies the requirement to distribute the source code, even though third parties are not compelled to copy the source along with the object code. + +5. A program that contains no derivative of any portion of the Library, but is designed to work with the Library by being compiled or linked with it, is called a "work that uses the Library". Such a work, in isolation, is not a derivative work of the Library, and therefore falls outside the scope of this License. + +However, linking a "work that uses the Library" with the Library creates an executable that is a derivative of the Library (because it contains portions of the Library), rather than a "work that uses the library". The executable is therefore covered by this License. Section 6 states terms for distribution of such executables. + +When a "work that uses the Library" uses material from a header file that is part of the Library, the object code for the work may be a derivative work of the Library even though the source code is not. Whether this is true is especially significant if the work can be linked without the Library, or if the work is itself a library. The threshold for this to be true is not precisely defined by law. + +If such an object file uses only numerical parameters, data structure layouts and accessors, and small macros and small inline functions (ten lines or less in length), then the use of the object file is unrestricted, regardless of whether it is legally a derivative work. (Executables containing this object code plus portions of the Library will still fall under Section 6.) + +Otherwise, if the work is a derivative of the Library, you may distribute the object code for the work under the terms of Section 6. Any executables containing that work also fall under Section 6, whether or not they are linked directly with the Library itself. + +6. As an exception to the Sections above, you may also combine or link a "work that uses the Library" with the Library to produce a work containing portions of the Library, and distribute that work under terms of your choice, provided that the terms permit modification of the work for the customer's own use and reverse engineering for debugging such modifications. + +You must give prominent notice with each copy of the work that the Library is used in it and that the Library and its use are covered by this License. You must supply a copy of this License. If the work during execution displays copyright notices, you must include the copyright notice for the Library among them, as well as a reference directing the user to the copy of this License. Also, you must do one of these things: + + a) Accompany the work with the complete corresponding machine-readable source code for the Library including whatever changes were used in the work (which must be distributed under Sections 1 and 2 above); and, if the work is an executable linked with the Library, with the complete machine-readable "work that uses the Library", as object code and/or source code, so that the user can modify the Library and then relink to produce a modified executable containing the modified Library. (It is understood that the user who changes the contents of definitions files in the Library will not necessarily be able to recompile the application to use the modified definitions.) + b) Use a suitable shared library mechanism for linking with the Library. A suitable mechanism is one that (1) uses at run time a copy of the library already present on the user's computer system, rather than copying library functions into the executable, and (2) will operate properly with a modified version of the library, if the user installs one, as long as the modified version is interface-compatible with the version that the work was made with. + c) Accompany the work with a written offer, valid for at least three years, to give the same user the materials specified in Subsection 6a, above, for a charge no more than the cost of performing this distribution. + d) If distribution of the work is made by offering access to copy from a designated place, offer equivalent access to copy the above specified materials from the same place. + e) Verify that the user has already received a copy of these materials or that you have already sent this user a copy. + +For an executable, the required form of the "work that uses the Library" must include any data and utility programs needed for reproducing the executable from it. However, as a special exception, the materials to be distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. + +It may happen that this requirement contradicts the license restrictions of other proprietary libraries that do not normally accompany the operating system. Such a contradiction means you cannot use both them and the Library together in an executable that you distribute. + +7. You may place library facilities that are a work based on the Library side-by-side in a single library together with other library facilities not covered by this License, and distribute such a combined library, provided that the separate distribution of the work based on the Library and of the other library facilities is otherwise permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work based on the Library, uncombined with any other library facilities. This must be distributed under the terms of the Sections above. + b) Give prominent notice with the combined library of the fact that part of it is a work based on the Library, and explaining where to find the accompanying uncombined form of the same work. + +8. You may not copy, modify, sublicense, link with, or distribute the Library except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense, link with, or distribute the Library is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. + +9. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Library or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Library (or any work based on the Library), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Library or works based on it. + +10. Each time you redistribute the Library (or any work based on the Library), the recipient automatically receives a license from the original licensor to copy, distribute, link with or modify the Library subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties with this License. + +11. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Library at all. For example, if a patent license would not permit royalty-free redistribution of the Library by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply, and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. + +This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. + +12. If the distribution and/or use of the Library is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Library under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. + +13. The Free Software Foundation may publish revised and/or new versions of the Lesser General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Library does not specify a license version number, you may choose any version ever published by the Free Software Foundation. + +14. If you wish to incorporate parts of the Library into other free programs whose distribution conditions are incompatible with these, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. + +NO WARRANTY + +15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. +END OF TERMS AND CONDITIONS + +How to Apply These Terms to Your New Libraries + +If you develop a new library, and you want it to be of the greatest possible use to the public, we recommend making it free software that everyone can redistribute and change. You can do so by permitting redistribution under these terms (or, alternatively, under the terms of the ordinary General Public License). + +To apply these terms, attach the following notices to the library. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. + +one line to give the library's name and an idea of what it does. +Copyright (C) year name of author + +This library is free software; you can redistribute it and/or +modify it under the terms of the GNU Lesser General Public +License as published by the Free Software Foundation; either +version 2.1 of the License, or (at your option) any later version. + +This library is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +Lesser General Public License for more details. + +You should have received a copy of the GNU Lesser General Public +License along with this library; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + +Also add information on how to contact you by electronic and paper mail. + +You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the library, if necessary. Here is a sample; alter the names: + +Yoyodyne, Inc., hereby disclaims all copyright interest in +the library `Frob' (a library for tweaking knobs) written +by James Random Hacker. + +signature of Ty Coon, 1 April 1990 +Ty Coon, President of Vice + +That's all there is to it! + +-------------------------------------------------- diff --git a/compiler/flow_parser/collections/third-party/flow_map.ml b/compiler/flow_parser/collections/third-party/flow_map.ml new file mode 100644 index 00000000000..1646207e016 --- /dev/null +++ b/compiler/flow_parser/collections/third-party/flow_map.ml @@ -0,0 +1,885 @@ +(* Portions Copyright (c) Meta Platforms, Inc. and affiliates. *) + +(*********************************************************************** + * * + * Objective Caml * + * * + * Xavier Leroy, projet Cristal, INRIA Rocquencourt * + * * + * Copyright 1996 Institut National de Recherche en Informatique et * + * en Automatique. All rights reserved. This file is distributed * + * under the terms of the GNU Library General Public License, with * + * the special exception on linking described in file LICENSE. * + * * + ***********************************************************************) + +(* This module has been inspired from the OCaml standard library. + * There are some modifications to make it run fast. + * - It adds a Leaf node to avoid excessive allocation for singleton map + * - In the hot [bal] function when we know it has to be [Node], we do + * an unsafe cast to avoid some unneeded tests + * - Functions not needing comparison functions are lifted outside functors + * - Leaf node is cast as a tuple to save some allocations + * - We add some utilities e.g, [adjust] and can add more relying on the + * internals in the future + *) + +type ('k, 'v) t0 = + | Empty + | Leaf of { + v: 'k; + d: 'v; + } + | Node of { + h: int; + v: 'k; + d: 'v; + l: ('k, 'v) t0; + r: ('k, 'v) t0; + } + +type ('k, 'v) partial_node = { + h: int; + v: 'k; + d: 'v; + l: ('k, 'v) t0; + r: ('k, 'v) t0; +} + +type ('k, 'v) leaf_tuple = 'k * 'v + +external ( ~!! ) : ('k, 'v) t0 -> ('k, 'v) leaf_tuple = "%identity" + +external ( ~! ) : ('k, 'v) t0 -> ('k, 'v) partial_node = "%identity" + +let[@inline] height = function + | Empty -> 0 + | Leaf _ -> 1 + | Node { h; _ } -> h + +let singleton x d = Leaf { v = x; d } + +let sorted_two_nodes_larger node v d = Node { l = node; v; d; r = Empty; h = 2 } + +let sorted_two_nodes_smaller v d node = Node { l = Empty; v; d; r = node; h = 2 } + +let create l x d r = + let hl = height l in + let hr = height r in + let h = + if hl >= hr then + hl + 1 + else + hr + 1 + in + if h = 1 then + singleton x d + else + Node { l; v = x; d; r; h } + +let rec of_increasing_iterator_unchecked f = function + | 0 -> Empty + | 1 -> + let (v, d) = f () in + Leaf { v; d } + | n -> + let lenl = n lsr 1 in + let lenr = n - lenl - 1 in + let l = of_increasing_iterator_unchecked f lenl in + let (v, d) = f () in + let r = of_increasing_iterator_unchecked f lenr in + Node { l; v; d; r; h = height l + 1 } + +let of_sorted_array_unchecked xs = + let len = Array.length xs in + let i = ref 0 in + let f () = + let x = xs.(!i) in + incr i; + x + in + of_increasing_iterator_unchecked f len + +(* The result can not be leaf *) +let node l x d r = + let hl = height l in + let hr = height r in + let h = + if hl >= hr then + hl + 1 + else + hr + 1 + in + Node { l; v = x; d; r; h } + +let bal l x d r = + let hl = height l in + let hr = height r in + if hl > hr + 2 then + let { l = ll; v = lv; d = ld; r = lr; _ } = ~!l in + if height ll >= height lr then + node ll lv ld (create lr x d r) + else + let { l = lrl; v = lrv; d = lrd; r = lrr; _ } = ~!lr in + node (create ll lv ld lrl) lrv lrd (create lrr x d r) + else if hr > hl + 2 then + let { l = rl; v = rv; d = rd; r = rr; _ } = ~!r in + if height rr >= height rl then + node (create l x d rl) rv rd rr + else + let { l = rll; v = rlv; d = rld; r = rlr; _ } = ~!rl in + node (create l x d rll) rlv rld (create rlr rv rd rr) + else + create l x d r + +let empty = Empty + +let[@inline] is_empty = function + | Empty -> true + | _ -> false + +type ('key, 'a) enumeration = + | End + | More of 'key * 'a * ('key, 'a) t0 * ('key, 'a) enumeration + +let rec cons_enum m e = + match m with + | Empty -> e + | Leaf { v; d } -> More (v, d, empty, e) + | Node { l; v; d; r; _ } -> cons_enum l (More (v, d, r, e)) + +let rec min_binding tree = + match tree with + | Empty -> raise Not_found + | Leaf _ -> ~!!tree + | Node { l = Empty; v; d; _ } -> (v, d) + | Node { l; _ } -> min_binding l + +let rec min_binding_from_node_unsafe tree = + let { l; v; d; _ } = ~!tree in + match l with + | Empty -> (v, d) + | Leaf _ -> ~!!l + | Node _ -> min_binding_from_node_unsafe l + +let rec min_binding_opt tree = + match tree with + | Empty -> None + | Leaf { v; d } -> Some (v, d) + | Node { l = Empty; v; d; _ } -> Some (v, d) + | Node { l; _ } -> min_binding_opt l + +let rec max_binding tree = + match tree with + | Empty -> raise Not_found + | Leaf _ -> ~!!tree + | Node { v; d; r = Empty; _ } -> (v, d) + | Node { r; _ } -> max_binding r + +let rec max_binding_opt tree = + match tree with + | Empty -> None + | Leaf { v; d } -> Some (v, d) + | Node { v; d; r = Empty; _ } -> Some (v, d) + | Node { r; _ } -> max_binding_opt r + +let rec remove_min_binding_from_node_unsafe tree = + let { l; v; d; r; _ } = ~!tree in + match l with + | Empty -> r + | Leaf _ -> bal Empty v d r + | Node _ -> bal (remove_min_binding_from_node_unsafe l) v d r + +(* Beware: those two functions assume that the added k is *strictly* + smaller (or bigger) than all the present keys in the tree; it + does not test for equality with the current min (or max) key. + + Indeed, they are only used during the "join" operation which + respects this precondition. +*) + +let rec add_min_node node tree = + match tree with + | Empty -> node + | Leaf { v; d } -> sorted_two_nodes_larger node v d + | Node { l; v; d; r; _ } -> bal (add_min_node node l) v d r + +let rec add_min_binding k x tree = + match tree with + | Empty -> singleton k x + | Leaf _ -> sorted_two_nodes_smaller k x tree + | Node { l; v; d; r; _ } -> bal (add_min_binding k x l) v d r + +let rec add_max_node node tree = + match tree with + | Empty -> node + | Leaf { v; d; _ } -> sorted_two_nodes_smaller v d node + | Node { l; v; d; r; _ } -> bal l v d (add_max_node node r) + +let rec add_max_binding k x tree = + match tree with + | Empty -> singleton k x + | Leaf _ -> sorted_two_nodes_larger tree k x + | Node { l; v; d; r; _ } -> bal l v d (add_max_binding k x r) + +let internal_merge t1 t2 = + match (t1, t2) with + | (Empty, t) -> t + | (t, Empty) -> t + | (Leaf _, t) -> add_min_node t1 t + | (t, Leaf _) -> add_max_node t2 t + | (Node _, Node _) -> + let (x, d) = min_binding_from_node_unsafe t2 in + bal t1 x d (remove_min_binding_from_node_unsafe t2) + +(* Same as create and bal, but no assumptions are made on the + relative heights of l and r. *) + +let rec join l v d r = + match (l, r) with + | (Empty, _) -> add_min_binding v d r + | (_, Empty) -> add_max_binding v d l + | (Leaf _, Leaf _) -> Node { l; v; d; r; h = 2 } + | (Leaf _, Node { l = rl; v = rv; d = rd; r = rr; h = rh }) -> + if rh > 3 then + bal (join l v d rl) rv rd rr + else + create l v d r + | (Node { l = ll; v = lv; d = ld; r = lr; h = lh }, Leaf _) -> + if lh > 3 then + bal ll lv ld (join lr v d r) + else + create l v d r + | ( Node { l = ll; v = lv; d = ld; r = lr; h = lh }, + Node { l = rl; v = rv; d = rd; r = rr; h = rh } + ) -> + if lh > rh + 2 then + bal ll lv ld (join lr v d r) + else if rh > lh + 2 then + bal (join l v d rl) rv rd rr + else + create l v d r + +(* Merge two trees l and r into one. + All elements of l must precede the elements of r. + No assumption on the heights of l and r. *) + +let concat t1 t2 = + match (t1, t2) with + | (Empty, t) -> t + | (t, Empty) -> t + | (Leaf _, t) -> add_min_node t1 t + | (t, Leaf _) -> add_max_node t2 t + | (Node _, Node _) -> + let (x, d) = min_binding_from_node_unsafe t2 in + join t1 x d (remove_min_binding_from_node_unsafe t2) + +let concat_or_join t1 v d t2 = + match d with + | Some d -> join t1 v d t2 + | None -> concat t1 t2 + +let rec iter f = function + | Empty -> () + | Leaf { v; d } -> f v d + | Node { l; v; d; r; _ } -> + iter f l; + f v d; + iter f r + +let rec map f = function + | Empty -> Empty + | Leaf { v; d } -> + let d' = f d in + Leaf { v; d = d' } + | Node { l; v; d; r; h } -> + let l' = map f l in + let d' = f d in + let r' = map f r in + Node { l = l'; v; d = d'; r = r'; h } + +let rec mapi f = function + | Empty -> Empty + | Leaf { v; d } -> + let d' = f v d in + Leaf { v; d = d' } + | Node { l; v; d; r; h } -> + let l' = mapi f l in + let d' = f v d in + let r' = mapi f r in + Node { l = l'; v; d = d'; r = r'; h } + +let rec fold f m accu = + match m with + | Empty -> accu + | Leaf { v; d } -> f v d accu + | Node { l; v; d; r; _ } -> fold f r (f v d (fold f l accu)) + +let rec keys_aux accu tree = + match tree with + | Empty -> accu + | Leaf { v; _ } -> v :: accu + | Node { l; v; r; _ } -> keys_aux (v :: keys_aux accu r) l + +let keys s = keys_aux [] s + +let ordered_keys = keys + +let rec for_all p = function + | Empty -> true + | Leaf { v; d } -> p v d + | Node { l; v; d; r; _ } -> p v d && for_all p l && for_all p r + +let rec exists p = function + | Empty -> false + | Leaf { v; d } -> p v d + | Node { l; v; d; r; _ } -> p v d || exists p l || exists p r + +let rec filter p tree = + match tree with + | Empty -> Empty + | Leaf { v; d } -> + if p v d then + tree + else + empty + | Node { l; v; d; r; _ } as m -> + (* call [p] in the expected left-to-right order *) + let l' = filter p l in + let pvd = p v d in + let r' = filter p r in + if pvd then + if l == l' && r == r' then + m + else + join l' v d r' + else + concat l' r' + +let rec cardinal = function + | Empty -> 0 + | Leaf _ -> 1 + | Node { l; r; _ } -> cardinal l + 1 + cardinal r + +let rec bindings_aux accu tree = + match tree with + | Empty -> accu + | Leaf _ -> ~!!tree :: accu + | Node { l; v; d; r; _ } -> bindings_aux ((v, d) :: bindings_aux accu r) l + +let bindings s = bindings_aux [] s + +type ('k, 'v) t1 = ('k, 'v) t0 = + | Empty + | Leaf of { + v: 'k; + d: 'v; + } + | Node of { + h: int; + v: 'k; + d: 'v; + l: ('k, 'v) t0; + r: ('k, 'v) t0; + } + +module type OrderedType = sig + type t + + val compare : t -> t -> int + (* val equal : t -> t -> bool *) +end + +module type S = sig + type key + + type +'a t + + val empty : 'a t + + val is_empty : 'a t -> bool + + val mem : key -> 'a t -> bool + + val add : key -> 'a -> 'a t -> 'a t + + val update : key -> ('a option -> 'a option) -> 'a t -> 'a t + + val adjust : key -> ('a option -> 'a) -> 'a t -> 'a t + + val singleton : key -> 'a -> 'a t + + (* when [remove k map] failed to remove [k], the original [map] is returned *) + val remove : key -> 'a t -> 'a t + + val merge : (key -> 'a option -> 'b option -> 'c option) -> 'a t -> 'b t -> 'c t + + val union : (key -> 'a -> 'a -> 'a option) -> 'a t -> 'a t -> 'a t + + val compare : ('a -> 'a -> int) -> 'a t -> 'a t -> int + + val equal : ('a -> 'a -> bool) -> 'a t -> 'a t -> bool + + val iter : (key -> 'a -> unit) -> 'a t -> unit + + val fold : (key -> 'a -> 'b -> 'b) -> 'a t -> 'b -> 'b + + val for_all : (key -> 'a -> bool) -> 'a t -> bool + + val exists : (key -> 'a -> bool) -> 'a t -> bool + + val filter : (key -> 'a -> bool) -> 'a t -> 'a t + + val partition : (key -> 'a -> bool) -> 'a t -> 'a t * 'a t + + val cardinal : 'a t -> int + + val bindings : 'a t -> (key * 'a) list + + val min_binding : 'a t -> key * 'a + + val min_binding_opt : 'a t -> (key * 'a) option + + val max_binding : 'a t -> key * 'a + + val max_binding_opt : 'a t -> (key * 'a) option + + val keys : 'a t -> key list + + val ordered_keys : 'a t -> key list + + val ident_map_key : ?combine:('a -> 'a -> 'a) -> (key -> key) -> 'a t -> 'a t + + val choose : 'a t -> key * 'a + + val choose_opt : 'a t -> (key * 'a) option + + val split : key -> 'a t -> 'a t * 'a option * 'a t + + val find : key -> 'a t -> 'a + + val find_opt : key -> 'a t -> 'a option + + val map : ('a -> 'b) -> 'a t -> 'b t + + val mapi : (key -> 'a -> 'b) -> 'a t -> 'b t + + val of_increasing_iterator_unchecked : (unit -> key * 'a) -> int -> 'a t + + val of_sorted_array_unchecked : (key * 'a) array -> 'a t +end + +module Make (Ord : OrderedType) : S with type key = Ord.t = struct + type key = Ord.t + + type 'a t = (key, 'a) t1 + + let rec add x data m = + match m with + | Empty -> singleton x data + | Leaf { v; d } -> + let c = Ord.compare x v in + if c = 0 then + if d == data then + m + else + Leaf { v; d = data } + else if c < 0 then + sorted_two_nodes_smaller x data m + else + sorted_two_nodes_larger m x data + | Node { l; v; d; r; h } as m -> + let c = Ord.compare x v in + if c = 0 then + if d == data then + m + else + Node { l; v = x; d = data; r; h } + else if c < 0 then + let ll = add x data l in + if l == ll then + m + else + bal ll v d r + else + let rr = add x data r in + if r == rr then + m + else + bal l v d rr + + let rec find x = function + | Empty -> raise Not_found + | Leaf { v; d } -> + let c = Ord.compare x v in + if c = 0 then + d + else + raise Not_found + | Node { l; v; d; r; _ } -> + let c = Ord.compare x v in + if c = 0 then + d + else + find + x + ( if c < 0 then + l + else + r + ) + + let rec find_opt x = function + | Empty -> None + | Leaf { v; d } -> + let c = Ord.compare x v in + if c = 0 then + Some d + else + None + | Node { l; v; d; r; _ } -> + let c = Ord.compare x v in + if c = 0 then + Some d + else + find_opt + x + ( if c < 0 then + l + else + r + ) + + let rec mem x = function + | Empty -> false + | Leaf { v; _ } -> Ord.compare x v = 0 + | Node { l; v; r; _ } -> + let c = Ord.compare x v in + c = 0 + || mem + x + ( if c < 0 then + l + else + r + ) + + let rec remove x tree = + match tree with + | Empty -> tree + | Leaf { v; _ } -> + let c = Ord.compare x v in + if c = 0 then + empty + else + tree + | Node { l; v; d; r; _ } as m -> + let c = Ord.compare x v in + if c = 0 then + internal_merge l r + else if c < 0 then + let ll = remove x l in + if l == ll then + m + else + bal ll v d r + else + let rr = remove x r in + if r == rr then + m + else + bal l v d rr + + let rec adjust x (f : 'a option -> 'a) tree = + match tree with + | Empty -> + let data = f None in + singleton x data + | Leaf { v; d } -> + (* check *) + let c = Ord.compare x v in + if c = 0 then + let data = f (Some d) in + if d == data then + tree + else + Leaf { v; d = data } + else + let data = f None in + if c < 0 then + sorted_two_nodes_smaller x data tree + else + sorted_two_nodes_larger tree x data + | Node { l; v; d; r; h } as m -> + let c = Ord.compare x v in + if c = 0 then + let data = f (Some d) in + if d == data then + m + else + Node { l; v = x; d = data; r; h } + else if c < 0 then + let ll = adjust x f l in + if l == ll then + m + else + bal ll v d r + else + let rr = adjust x f r in + if r == rr then + m + else + bal l v d rr + + let rec update x f tree = + match tree with + | Empty -> begin + match f None with + | None -> Empty + | Some data -> singleton x data + end + | Leaf { v; d } -> + (* check *) + let c = Ord.compare x v in + if c = 0 then + match f (Some d) with + | None -> empty (* It exists, None means deletion *) + | Some data -> + if d == data then + tree + else + Leaf { v; d = data } + else begin + match f None with + | None -> tree + | Some data -> + if c < 0 then + sorted_two_nodes_smaller x data tree + else + sorted_two_nodes_larger tree x data + end + | Node { l; v; d; r; h } as m -> + let c = Ord.compare x v in + if c = 0 then + match f (Some d) with + | None -> internal_merge l r + | Some data -> + if d == data then + m + else + Node { l; v = x; d = data; r; h } + else if c < 0 then + let ll = update x f l in + if l == ll then + m + else + bal ll v d r + else + let rr = update x f r in + if r == rr then + m + else + bal l v d rr + + let rec split x tree = + match tree with + | Empty -> (Empty, None, Empty) + | Leaf { v; d } -> + let c = Ord.compare x v in + if c = 0 then + (empty, Some d, empty) + else if c < 0 then + (empty, None, tree) + else + (tree, None, empty) + | Node { l; v; d; r; _ } -> + let c = Ord.compare x v in + if c = 0 then + (l, Some d, r) + else if c < 0 then + let (ll, pres, rl) = split x l in + (ll, pres, join rl v d r) + else + let (lr, pres, rr) = split x r in + (join l v d lr, pres, rr) + + let rec merge f s1 s2 = + match (s1, s2) with + | (Empty, Empty) -> Empty + | (Leaf { v; d }, Empty) -> begin + match f v (Some d) None with + | None -> empty + | Some data -> Leaf { v; d = data } + end + | (Empty, Leaf { v; d }) -> begin + match f v None (Some d) with + | None -> empty + | Some data -> Leaf { v; d = data } + end + | (Leaf { v = v1; d = d1 }, Leaf _) -> + let (l2, d2, r2) = split v1 s2 in + concat_or_join (merge f empty l2) v1 (f v1 (Some d1) d2) (merge f empty r2) + | (Node { l = l1; v = v1; d = d1; r = r1; h = h1 }, _) when h1 >= height s2 -> + let (l2, d2, r2) = split v1 s2 in + concat_or_join (merge f l1 l2) v1 (f v1 (Some d1) d2) (merge f r1 r2) + | (_, Node { l = l2; v = v2; d = d2; r = r2; _ }) -> + let (l1, d1, r1) = split v2 s1 in + concat_or_join (merge f l1 l2) v2 (f v2 d1 (Some d2)) (merge f r1 r2) + | (Node _, (Empty | Leaf _)) -> assert false + + let rec union f s1 s2 = + match (s1, s2) with + | (Empty, s) + | (s, Empty) -> + s + | (s, Leaf { v; d }) -> + update + v + (fun d2 -> + match d2 with + | None -> Some d + | Some d2 -> f v d2 d) + s + | (Leaf { v; d }, s) -> + (* add v d s *) + update + v + (fun d2 -> + match d2 with + | None -> Some d + | Some d2 -> f v d d2) + s + | ( Node { l = l1; v = v1; d = d1; r = r1; h = h1 }, + Node { l = l2; v = v2; d = d2; r = r2; h = h2 } + ) -> + if h1 >= h2 then + let (l2, d2, r2) = split v1 s2 in + let l = union f l1 l2 and r = union f r1 r2 in + match d2 with + | None -> join l v1 d1 r + | Some d2 -> concat_or_join l v1 (f v1 d1 d2) r + else + let (l1, d1, r1) = split v2 s1 in + let l = union f l1 l2 and r = union f r1 r2 in + (match d1 with + | None -> join l v2 d2 r + | Some d1 -> concat_or_join l v2 (f v2 d1 d2) r) + + let rec partition p tree = + match tree with + | Empty -> (Empty, Empty) + | Leaf { v; d } -> + if p v d then + (tree, empty) + else + (empty, tree) + | Node { l; v; d; r; _ } -> + (* call [p] in the expected left-to-right order *) + let (lt, lf) = partition p l in + let pvd = p v d in + let (rt, rf) = partition p r in + if pvd then + (join lt v d rt, concat lf rf) + else + (concat lt rt, join lf v d rf) + + let compare cmp m1 m2 = + let rec compare_aux e1 e2 = + match (e1, e2) with + | (End, End) -> 0 + | (End, _) -> -1 + | (_, End) -> 1 + | (More (v1, d1, r1, e1), More (v2, d2, r2, e2)) -> + let c = Ord.compare v1 v2 in + if c <> 0 then + c + else + let c = cmp d1 d2 in + if c <> 0 then + c + else + compare_aux (cons_enum r1 e1) (cons_enum r2 e2) + in + compare_aux (cons_enum m1 End) (cons_enum m2 End) + + let equal cmp m1 m2 = + let rec equal_aux e1 e2 = + match (e1, e2) with + | (End, End) -> true + | (End, _) -> false + | (_, End) -> false + | (More (v1, d1, r1, e1), More (v2, d2, r2, e2)) -> + Ord.compare v1 v2 = 0 && cmp d1 d2 && equal_aux (cons_enum r1 e1) (cons_enum r2 e2) + in + equal_aux (cons_enum m1 End) (cons_enum m2 End) + + let cardinal = cardinal + + let bindings = bindings + + let keys = keys + + let choose = min_binding + + let choose_opt = min_binding_opt + + let empty = empty + + let singleton = singleton + + let is_empty = is_empty + + let min_binding = min_binding + + let min_binding_opt = min_binding_opt + + let max_binding = max_binding + + let max_binding_opt = max_binding_opt + + let fold = fold + + let iter = iter + + let for_all = for_all + + let exists = exists + + let mapi = mapi + + let map = map + + let filter = filter + + let ordered_keys = keys + + let of_increasing_iterator_unchecked = of_increasing_iterator_unchecked + + let of_sorted_array_unchecked = of_sorted_array_unchecked + + let ident_map_key ?combine f map = + let (map_, changed) = + fold + (fun key item (map_, changed) -> + let new_key = f key in + ( (* add ?combine new_key item map_ *) + (match combine with + | None -> add new_key item map_ + | Some combine -> + adjust + new_key + (fun opt -> + match opt with + | None -> item + | Some old_value -> combine old_value item) + map_), + changed || new_key != key + )) + map + (empty, false) + in + if changed then + map_ + else + map +end diff --git a/compiler/flow_parser/collections/third-party/flow_set.ml b/compiler/flow_parser/collections/third-party/flow_set.ml new file mode 100644 index 00000000000..55f58e8c0c4 --- /dev/null +++ b/compiler/flow_parser/collections/third-party/flow_set.ml @@ -0,0 +1,865 @@ +(* Portions Copyright (c) Meta Platforms, Inc. and affiliates. *) + +(*********************************************************************** + * * + * Objective Caml * + * * + * Xavier Leroy, projet Cristal, INRIA Rocquencourt * + * * + * Copyright 1996 Institut National de Recherche en Informatique et * + * en Automatique. All rights reserved. This file is distributed * + * under the terms of the GNU Library General Public License, with * + * the special exception on linking described in file LICENSE. * + * * + ***********************************************************************) + +(* This module has been inspired from the OCaml standard library. + * There are some modifications to make it run fast. + * - It adds a Leaf node to avoid excessive allocation for singleton set + * - In the hot [bal] function when we we know it has to be [Node], we do + * an unsafe cast to avoid some unneeded tests + * - Functions not need comparison functions are lifted outside functors + * - We can add more utilities relying on the internals in the future + *) + +module type OrderedType = sig + type t + + val compare : t -> t -> int +end + +module type S = sig + type elt + + type t + + val empty : t + + val is_empty : t -> bool + + val mem : elt -> t -> bool + + val add : elt -> t -> t + + val singleton : elt -> t + + val remove : elt -> t -> t + + val union : t -> t -> t + + val inter : t -> t -> t + + val disjoint : t -> t -> bool + + val diff : t -> t -> t + + val compare : t -> t -> int + + val equal : t -> t -> bool + + val subset : t -> t -> bool + + val iter : (elt -> unit) -> t -> unit + + val map : (elt -> elt) -> t -> t + + val fold : (elt -> 'a -> 'a) -> t -> 'a -> 'a + + val for_all : (elt -> bool) -> t -> bool + + val exists : (elt -> bool) -> t -> bool + + val filter : (elt -> bool) -> t -> t + + val partition : (elt -> bool) -> t -> t * t + + val cardinal : t -> int + + val elements : t -> elt list + + val min_elt : t -> elt + + val min_elt_opt : t -> elt option + + val max_elt : t -> elt + + val max_elt_opt : t -> elt option + + val choose : t -> elt + + val choose_opt : t -> elt option + + val find : elt -> t -> elt + + val find_opt : elt -> t -> elt option + + val to_seq : t -> elt Seq.t + + val of_list : elt list -> t + + val make_pp : (Format.formatter -> elt -> unit) -> Format.formatter -> t -> unit + + val of_increasing_iterator_unchecked : (unit -> elt) -> int -> t + + val of_sorted_array_unchecked : elt array -> t + + val find_first_opt : (elt -> bool) -> t -> elt option +end + +type 'elt t0 = + | Empty + | Leaf of 'elt + | Node of { + h: int; + v: 'elt; + l: 'elt t0; + r: 'elt t0; + } + +type 'elt partial_node = { + h: int; + v: 'elt; + l: 'elt t0; + r: 'elt t0; +} + +external ( ~! ) : 'elt t0 -> 'elt partial_node = "%identity" + +type ('elt, 't) enumeration0 = + | End + | More of 'elt * 't * ('elt, 't) enumeration0 + +let rec cons_enum s e = + match s with + | Empty -> e + | Leaf v -> More (v, Empty, e) + | Node { l; v; r; _ } -> cons_enum l (More (v, r, e)) + +let rec seq_of_enum_ c () = + match c with + | End -> Seq.Nil + | More (x, t, rest) -> Seq.Cons (x, seq_of_enum_ (cons_enum t rest)) + +let to_seq c = seq_of_enum_ (cons_enum c End) + +let[@inline] height = function + | Empty -> 0 + | Leaf _ -> 1 + | Node { h; _ } -> h + +let[@inline] singleton x = Leaf x + +(* FIXME: we should check to avoid creating unneeded Node + - node + - Node + This function produce Node of height at least [1] +*) +let unsafe_node ~l ~v ~r = + match (l, r) with + | (Empty, Empty) -> singleton v + | (Leaf _, Empty) + | (Leaf _, Leaf _) + | (Empty, Leaf _) -> + Node { l; v; r; h = 2 } + | (Node { h; _ }, (Leaf _ | Empty)) + | ((Leaf _ | Empty), Node { h; _ }) -> + Node { l; v; r; h = h + 1 } + | (Node { h = hl; _ }, Node { h = hr; _ }) -> + let h = + if hl >= hr then + hl + 1 + else + hr + 1 + in + Node { l; v; r; h } + +(* Creates a new node with left son l, value v and right son r. + We must have all elements of l < v < all elements of r. + l and r must be balanced and | height l - height r | <= 2. + Inline expansion of height for better speed. *) + +let create l v r = + let hl = height l in + let hr = height r in + Node + { + l; + v; + r; + h = + ( if hl >= hr then + hl + 1 + else + hr + 1 + ); + } + +let rec of_increasing_iterator_unchecked f = function + | 0 -> Empty + | 1 -> + let v = f () in + Leaf v + | n -> + let lenl = n lsr 1 in + let lenr = n - lenl - 1 in + let l = of_increasing_iterator_unchecked f lenl in + let v = f () in + let r = of_increasing_iterator_unchecked f lenr in + Node { l; v; r; h = height l + 1 } + +let of_sorted_array_unchecked xs = + let len = Array.length xs in + let i = ref 0 in + let f () = + let x = xs.(!i) in + incr i; + x + in + of_increasing_iterator_unchecked f len + +(* Same as create, but performs one step of rebalancing if necessary. + Assumes l and r balanced and | height l - height r | <= 3. + Inline expansion of create for better speed in the most frequent case + where no rebalancing is required. *) + +let bal l v r = + let hl = height l in + let hr = height r in + if hl > hr + 2 then + (* hl is at least of height > 2 [3], so it should be [Node] + Note having in-efficient nodes like [Node (empty,v,empty)] won't affect + correctness here, since it will be even more likely to be [Node] + But we are stricter with height + *) + let { l = ll; v = lv; r = lr; _ } = ~!l in + if height ll >= height lr then + create ll lv (unsafe_node ~l:lr ~v ~r) + else + (* Int his path hlr > hll while hl = hlr + 1 so [hlr] > 1, so it should be [Node]*) + let { l = lrl; v = lrv; r = lrr; _ } = ~!lr in + create (unsafe_node ~l:ll ~v:lv ~r:lrl) lrv (unsafe_node ~l:lrr ~v ~r) + else if hr > hl + 2 then + (* hr is at least of height > 2 [3], so it should be [Node] *) + let { l = rl; v = rv; r = rr; _ } = ~!r in + if height rr >= height rl then + create (unsafe_node ~l ~v ~r:rl) rv rr + else + (* In this path hrl > hrr while hr = hrl + 1, so [hrl] > 1, so it should be [Node] *) + let { l = rll; v = rlv; r = rlr; _ } = ~!rl in + create (unsafe_node ~l ~v ~r:rll) rlv (unsafe_node ~l:rlr ~v:rv ~r:rr) + else + unsafe_node ~l ~v ~r + +(* Beware: those two functions assume that the added v is *strictly* + smaller (or bigger) than all the present elements in the tree; it + does not test for equality with the current min (or max) element. + Indeed, they are only used during the "join" operation which + respects this precondition. +*) + +let rec add_min_element x = function + | Empty -> singleton x + | Leaf v -> unsafe_node ~l:(singleton x) ~v ~r:Empty + | Node { l; v; r; _ } -> bal (add_min_element x l) v r + +let rec add_max_element x = function + | Empty -> singleton x + | Leaf v -> unsafe_node ~l:Empty ~v ~r:(singleton x) + | Node { l; v; r; _ } -> bal l v (add_max_element x r) + +(* Same as create and bal, but no assumptions are made on the + relative heights of l and r. *) + +let rec join l v r = + match (l, r) with + | (Empty, _) -> add_min_element v r + | (_, Empty) -> add_max_element v l + | (Leaf _, Leaf _) -> unsafe_node ~l ~v ~r + | (Leaf _, Node { l = rl; v = rv; r = rr; h = rh }) -> + if rh > 3 then + bal (join l v rl) rv rr + else + create l v r + | (Node { l = ll; v = lv; r = lr; h = lh }, Leaf _) -> + if lh > 3 then + bal ll lv (join lr v r) + else + create l v r + | (Node { l = ll; v = lv; r = lr; h = lh }, Node { l = rl; v = rv; r = rr; h = rh }) -> + if lh > rh + 2 then + bal ll lv (join lr v r) + else if rh > lh + 2 then + bal (join l v rl) rv rr + else + create l v r + +(* Smallest and greatest element of a set *) + +let rec min_elt = function + | Empty -> raise Not_found + | Leaf v -> v + | Node { l = Empty; v; _ } -> v + | Node { l; _ } -> min_elt l + +let rec min_elt_opt = function + | Empty -> None + | Leaf v -> Some v + | Node { l = Empty; v; _ } -> Some v + | Node { l; _ } -> min_elt_opt l + +let rec max_elt = function + | Empty -> raise Not_found + | Node { v; r = Empty; _ } -> v + | Leaf v -> v + | Node { r; _ } -> max_elt r + +let rec max_elt_opt = function + | Empty -> None + | Node { v; r = Empty; _ } -> Some v + | Leaf v -> Some v + | Node { r; _ } -> max_elt_opt r + +(* Remove the smallest element of the given set *) + +let rec remove_min_elt = function + | Empty -> invalid_arg "Set.remove_min_elt" + | Leaf _ -> Empty + | Node { l = Empty; r; _ } -> r + | Node { l; v; r; _ } -> bal (remove_min_elt l) v r + +(* Merge two trees l and r into one. + All elements of l must precede the elements of r. + Assume | height l - height r | <= 2. *) + +let merge t1 t2 = + match (t1, t2) with + | (Empty, t) -> t + | (t, Empty) -> t + | (_, _) -> bal t1 (min_elt t2) (remove_min_elt t2) + +(* Merge two trees l and r into one. + All elements of l must precede the elements of r. + No assumption on the heights of l and r. *) + +let concat t1 t2 = + match (t1, t2) with + | (Empty, t) -> t + | (t, Empty) -> t + | (_, _) -> join t1 (min_elt t2) (remove_min_elt t2) + +let rec cardinal = function + | Empty -> 0 + | Leaf _ -> 1 + | Node { l; r; _ } -> cardinal l + 1 + cardinal r + +let rec elements_aux accu = function + | Empty -> accu + | Leaf v -> v :: accu + | Node { l; v; r; _ } -> elements_aux (v :: elements_aux accu r) l + +let elements s = elements_aux [] s + +let empty = Empty + +let[@inline] is_empty = function + | Empty -> true + | _ -> false + +let of_sorted_list l = + let rec sub n l = + match (n, l) with + | (0, l) -> (Empty, l) + | (1, x0 :: l) -> (singleton x0, l) + | (2, x0 :: x1 :: l) -> (Node { l = singleton x0; v = x1; r = Empty; h = 2 }, l) + | (3, x0 :: x1 :: x2 :: l) -> (Node { l = singleton x0; v = x1; r = singleton x2; h = 2 }, l) + | (n, l) -> + let nl = n / 2 in + let (left, l) = sub nl l in + (match l with + | [] -> assert false + | mid :: l -> + let (right, l) = sub (n - nl - 1) l in + (create left mid right, l)) + in + fst (sub (List.length l) l) + +type 'a t1 = 'a t0 = private + | Empty + | Leaf of 'a + | Node of { + h: int; + v: 'a; + l: 'a t0; + r: 'a t0; + } + +module Make (Ord : OrderedType) : S with type elt = Ord.t = struct + type elt = Ord.t + + type t = elt t1 + + let singleton = singleton + + (* Insertion of one element *) + let min_elt_opt = min_elt_opt + + let max_elt_opt = max_elt_opt + + let min_elt = min_elt + + let max_elt = max_elt + + let elements = elements + + let cardinal = cardinal + + let is_empty = is_empty + + let empty = empty + + let choose = min_elt + + let choose_opt = min_elt_opt + + let rec add x t = + match t with + | Empty -> singleton x + | Leaf v -> + let c = Ord.compare x v in + if c = 0 then + t + else if c < 0 then + unsafe_node ~l:(singleton x) ~v ~r:empty + else + unsafe_node ~l:t ~v:x ~r:empty + | Node { l; v; r; _ } as t -> + let c = Ord.compare x v in + if c = 0 then + t + else if c < 0 then + let ll = add x l in + if l == ll then + t + else + bal ll v r + else + let rr = add x r in + if r == rr then + t + else + bal l v rr + + let ( @> ) = add + (* Splitting. split x s returns a triple (l, present, r) where + - l is the set of elements of s that are < x + - r is the set of elements of s that are > x + - present is false if s contains no element equal to x, + or true if s contains an element equal to x. *) + + let rec split x tree = + match tree with + | Empty -> (empty, false, empty) + | Leaf v -> + let c = Ord.compare x v in + if c = 0 then + (empty, true, empty) + else if c < 0 then + (empty, false, tree) + else + (tree, false, empty) + | Node { l; v; r; _ } -> + let c = Ord.compare x v in + if c = 0 then + (l, true, r) + else if c < 0 then + let (ll, pres, rl) = split x l in + (ll, pres, join rl v r) + else + let (lr, pres, rr) = split x r in + (join l v lr, pres, rr) + + (* Implementation of the set operations *) + + let rec mem x = function + | Empty -> false + | Leaf v -> + let c = Ord.compare x v in + c = 0 + | Node { l; v; r; _ } -> + let c = Ord.compare x v in + c = 0 + || mem + x + ( if c < 0 then + l + else + r + ) + + let rec remove x tree = + match tree with + | Empty -> empty + | Leaf v -> + let c = Ord.compare x v in + if c = 0 then + empty + else + tree + | Node { l; v; r; _ } as t -> + let c = Ord.compare x v in + if c = 0 then + merge l r + else if c < 0 then + let ll = remove x l in + if l == ll then + t + else + bal ll v r + else + let rr = remove x r in + if r == rr then + t + else + bal l v rr + + let rec union s1 s2 = + match (s1, s2) with + | (Empty, t2) -> t2 + | (t1, Empty) -> t1 + | (Leaf v, s2) -> add v s2 + | (s1, Leaf v) -> add v s1 + | (Node { l = l1; v = v1; r = r1; h = h1 }, Node { l = l2; v = v2; r = r2; h = h2 }) -> + if h1 >= h2 then + if h2 = 1 then + add v2 s1 + else + let (l2, _, r2) = split v1 s2 in + join (union l1 l2) v1 (union r1 r2) + else if h1 = 1 then + add v1 s2 + else + let (l1, _, r1) = split v2 s1 in + join (union l1 l2) v2 (union r1 r2) + + let rec inter s1 s2 = + match (s1, s2) with + | (Empty, _) -> empty + | (_, Empty) -> empty + | (Leaf v, _) -> + if mem v s2 then + s1 + else + empty + | (Node { l = l1; v = v1; r = r1; _ }, t2) -> + (match split v1 t2 with + | (l2, false, r2) -> concat (inter l1 l2) (inter r1 r2) + | (l2, true, r2) -> join (inter l1 l2) v1 (inter r1 r2)) + + (* Same as split, but compute the left and right subtrees + only if the pivot element is not in the set. The right subtree + is computed on demand. *) + + type split_bis = + | Found + | NotFound of t * (unit -> t) + + let rec split_bis x = function + | Empty -> NotFound (empty, (fun () -> empty)) + | Leaf v -> + let c = Ord.compare x v in + if c = 0 then + Found + else + NotFound (empty, (fun () -> empty)) + | Node { l; v; r; _ } -> + let c = Ord.compare x v in + if c = 0 then + Found + else if c < 0 then + match split_bis x l with + | Found -> Found + | NotFound (ll, rl) -> NotFound (ll, (fun () -> join (rl ()) v r)) + else ( + match split_bis x r with + | Found -> Found + | NotFound (lr, rr) -> NotFound (join l v lr, rr) + ) + + let rec disjoint s1 s2 = + match (s1, s2) with + | (Empty, _) + | (_, Empty) -> + true + | (Leaf v, s) + | (s, Leaf v) -> + not (mem v s) + | (Node { l = l1; v = v1; r = r1; _ }, t2) -> + if s1 == s2 then + false + else ( + match split_bis v1 t2 with + | NotFound (l2, r2) -> disjoint l1 l2 && disjoint r1 (r2 ()) + | Found -> false + ) + + let rec diff s1 s2 = + match (s1, s2) with + | (Empty, _) -> empty + | (t1, Empty) -> t1 + | (Leaf v, _) -> + if mem v s2 then + empty + else + s1 + | (Node { l = l1; v = v1; r = r1; _ }, t2) -> + (match split v1 t2 with + | (l2, false, r2) -> join (diff l1 l2) v1 (diff r1 r2) + | (l2, true, r2) -> concat (diff l1 l2) (diff r1 r2)) + + let rec compare_aux e1 e2 = + match (e1, e2) with + | (End, End) -> 0 + | (End, _) -> -1 + | (_, End) -> 1 + | (More (v1, r1, e1), More (v2, r2, e2)) -> + let c = Ord.compare v1 v2 in + if c <> 0 then + c + else + compare_aux (cons_enum r1 e1) (cons_enum r2 e2) + + let compare s1 s2 = compare_aux (cons_enum s1 End) (cons_enum s2 End) + + let equal s1 s2 = compare s1 s2 = 0 + + let rec subset s1 s2 = + match (s1, s2) with + | (Empty, _) -> true + | (_, Empty) -> false + | (Leaf v1, Leaf v2) -> + let c = Ord.compare v1 v2 in + if c = 0 then + true + else + false + | (Node { v = v1; h; _ }, Leaf v2) -> + h = 1 + && (* conservative here *) + Ord.compare v1 v2 = 0 + | (Leaf v1, Node { l = l2; v = v2; r = r2; _ }) -> + let c = Ord.compare v1 v2 in + if c = 0 then + true + else if c < 0 then + subset s1 l2 + else + subset s1 r2 + | (Node { l = l1; v = v1; r = r1; _ }, (Node { l = l2; v = v2; r = r2; _ } as t2)) -> + let c = Ord.compare v1 v2 in + if c = 0 then + subset l1 l2 && subset r1 r2 + else if c < 0 then + (* Better to keep invariant here, since our unsafe code relies on such invariant + *) + subset (unsafe_node ~l:l1 ~v:v1 ~r:empty) l2 && subset r1 t2 + else + subset (unsafe_node ~l:empty ~v:v1 ~r:r1) r2 && subset l1 t2 + + let rec iter f = function + | Empty -> () + | Leaf v -> f v + | Node { l; v; r; _ } -> + iter f l; + f v; + iter f r + + let rec fold f s accu = + match s with + | Empty -> accu + | Leaf v -> f v accu + | Node { l; v; r; _ } -> fold f r (f v (fold f l accu)) + + let rec for_all p = function + | Empty -> true + | Leaf v -> p v + | Node { l; v; r; _ } -> p v && for_all p l && for_all p r + + let rec exists p = function + | Empty -> false + | Leaf v -> p v + | Node { l; v; r; _ } -> p v || exists p l || exists p r + + let rec filter p tree = + match tree with + | Empty -> empty + | Leaf v -> + let pv = p v in + if pv then + tree + else + empty + | Node { l; v; r; _ } as t -> + (* call [p] in the expected left-to-right order *) + let l' = filter p l in + let pv = p v in + let r' = filter p r in + if pv then + if l == l' && r == r' then + t + else + join l' v r' + else + concat l' r' + + let rec partition p tree = + match tree with + | Empty -> (empty, empty) + | Leaf v -> + let pv = p v in + if pv then + (tree, empty) + else + (empty, tree) + | Node { l; v; r; _ } -> + (* call [p] in the expected left-to-right order *) + let (lt, lf) = partition p l in + let pv = p v in + let (rt, rf) = partition p r in + if pv then + (join lt v rt, concat lf rf) + else + (concat lt rt, join lf v rf) + + let rec find x = function + | Empty -> raise Not_found + | Leaf v -> + let c = Ord.compare x v in + if c = 0 then + v + else + raise Not_found + | Node { l; v; r; _ } -> + let c = Ord.compare x v in + if c = 0 then + v + else + find + x + ( if c < 0 then + l + else + r + ) + + let rec find_opt x = function + | Empty -> None + | Leaf v -> + let c = Ord.compare x v in + if c = 0 then + Some v + else + None + | Node { l; v; r; _ } -> + let c = Ord.compare x v in + if c = 0 then + Some v + else + find_opt + x + ( if c < 0 then + l + else + r + ) + + let try_join l v r = + (* [join l v r] can only be called when (elements of l < v < + elements of r); use [try_join l v r] when this property may + not hold, but you hope it does hold in the common case *) + if (is_empty l || Ord.compare (max_elt l) v < 0) && (is_empty r || Ord.compare v (min_elt r) < 0) + then + join l v r + else + union l (add v r) + + let rec map f tree = + match tree with + | Empty -> empty + | Leaf v -> + let v' = f v in + if v == v' then + tree + else + singleton v' + | Node { l; v; r; _ } as t -> + (* enforce left-to-right evaluation order *) + let l' = map f l in + let v' = f v in + let r' = map f r in + if l == l' && v == v' && r == r' then + t + else + try_join l' v' r' + + let of_list l = + match l with + | [] -> empty + | [x0] -> singleton x0 + | [x0; x1] -> x1 @> singleton x0 + | [x0; x1; x2] -> x2 @> x1 @> singleton x0 + | [x0; x1; x2; x3] -> x3 @> x2 @> x1 @> singleton x0 + | [x0; x1; x2; x3; x4] -> x4 @> x3 @> x2 @> x1 @> singleton x0 + | _ -> of_sorted_list (List.sort_uniq Ord.compare l) + + let to_seq = to_seq + + let make_pp pp_key fmt iset = + Format.fprintf fmt "@[<2>{"; + let elements = elements iset in + (match elements with + | [] -> () + | _ -> Format.fprintf fmt " "); + ignore + (List.fold_left + (fun sep s -> + if sep then Format.fprintf fmt ";@ "; + pp_key fmt s; + true) + false + elements + ); + (match elements with + | [] -> () + | _ -> Format.fprintf fmt " "); + Format.fprintf fmt "@,}@]" + + let of_increasing_iterator_unchecked = of_increasing_iterator_unchecked + + let of_sorted_array_unchecked = of_sorted_array_unchecked + + let rec find_first_opt_aux v0 f = function + | Empty -> Some v0 + | Leaf v -> + if f v then + Some v + else + Some v0 + | Node { l; v; r; _ } -> + if f v then + find_first_opt_aux v f l + else + find_first_opt_aux v0 f r + + let rec find_first_opt f = function + | Empty -> None + | Leaf v -> + if f v then + Some v + else + None + | Node { l; v; r; _ } -> + if f v then + find_first_opt_aux v f l + else + find_first_opt f r +end diff --git a/compiler/flow_parser/collections/union_find.ml b/compiler/flow_parser/collections/union_find.ml new file mode 100644 index 00000000000..2b4b466f511 --- /dev/null +++ b/compiler/flow_parser/collections/union_find.ml @@ -0,0 +1,90 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type ident = int + +exception Tvar_not_found of ident + +module Make (Constraints : sig + type t +end) = +struct + (** A root structure carries the actual non-trivial state of a tvar, and + consists of: + + - rank, which is a quantity roughly corresponding to the longest chain of + gotos pointing to the tvar. It's an implementation detail of the unification + algorithm that simply has to do with efficiently finding the root of a tree. + We merge a tree with another tree by converting the root with the lower rank + to a goto node, and making it point to the root with the higher rank. See + http://en.wikipedia.org/wiki/Disjoint-set_data_structure for more details on + this data structure and supported operations. + + - constraints, which carry type information that narrows down the possible + solutions of the tvar (see below). *) + type root = { + mutable rank: int; + mutable constraints: Constraints.t; + } + + (** Type variables are unknowns, and we are ultimately interested in constraints + on their solutions for type inference. + + Type variables form nodes in a "union-find" forest: each tree denotes a set + of type variables that are considered by the type system to be equivalent. + + There are two kinds of nodes: Goto nodes and Root nodes. + + - All Goto nodes of a tree point, directly or indirectly, to the Root node + of the tree. + - A Root node holds the actual non-trivial state of a tvar, represented by a + root structure (see below). *) + type node_ = + | Goto of { mutable parent: ident } + | Root of root + + type node = node_ ref + + type graph = node IMap.t + + let create_root constraints = ref (Root { rank = 0; constraints }) + + let create_goto parent = ref (Goto { parent }) + + (* Find the root of a type variable, potentially traversing a chain of type + variables, while short-circuiting all the type variables in the chain to the + root during traversal to speed up future traversals. *) + let rec find_root graph id = + match IMap.find_opt id graph with + | None -> raise (Tvar_not_found id) + | Some node -> + (match !node with + | Root root -> (id, node, root) + | Goto goto -> + let ((root_id, _, _) as root) = find_root graph goto.parent in + goto.parent <- root_id; + root) + + let find_root_id graph id = + let (root_id, _, _) = find_root graph id in + root_id + + (* Find the constraints of a type variable in the graph. + + Recall that type variables are either roots or goto nodes. (See + Constraint for details.) If the type variable is a root, the + constraints are stored with the type variable. Otherwise, the type variable + is a goto node, and it points to another type variable: a linked list of such + type variables must be traversed until a root is reached. *) + let find_constraints graph id = + let (root_id, _, root) = find_root graph id in + (root_id, root.constraints) + + let find_graph graph id = + let (_, constraints) = find_constraints graph id in + constraints +end diff --git a/compiler/flow_parser/collections/wrappedMap.ml b/compiler/flow_parser/collections/wrappedMap.ml new file mode 100644 index 00000000000..777ed8ad484 --- /dev/null +++ b/compiler/flow_parser/collections/wrappedMap.ml @@ -0,0 +1,157 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module type S = WrappedMap_sig.S + +module Make (Ord : Map.OrderedType) : S with type key = Ord.t = struct + include Flow_map.Make (Ord) + + let union ?combine x y = + let combine = + match combine with + | None -> (fun _ fst _ -> Some fst) + | Some f -> f + in + union combine x y + + let rec fold_left_env env l ~init ~f = + match l with + | [] -> (env, init) + | x :: xs -> + let (env, init) = f env init x in + fold_left_env env xs ~init ~f + + let merge_env env s1 s2 ~combine = + let (env, map) = + fold_left_env + env + ~init:empty + ~f:(fun env map (key, v2) -> + let v1opt = find_opt key s1 in + let (env, vopt) = combine env key v1opt (Some v2) in + let map = + match vopt with + | None -> map + | Some v -> add key v map + in + (env, map)) + (bindings s2) + in + fold_left_env + env + ~init:map + ~f:(fun env map (key, v1) -> + let v2opt = find_opt key s2 in + match v2opt with + | None -> + let (env, vopt) = combine env key (Some v1) None in + let map = + match vopt with + | None -> map + | Some v -> add key v map + in + (env, map) + | Some _ -> (env, map)) + (bindings s1) + + let union_env env s1 s2 ~combine = + let f env key o1 o2 = + match (o1, o2) with + | (None, None) -> (env, None) + | (Some v, None) + | (None, Some v) -> + (env, Some v) + | (Some v1, Some v2) -> combine env key v1 v2 + in + merge_env env s1 s2 ~combine:f + + let values m = fold (fun _ v acc -> v :: acc) m [] + + let fold_env env f m init = fold (fun key v (env, acc) -> f env key v acc) m (env, init) + + let elements m = fold (fun k v acc -> (k, v) :: acc) m [] + + let map_env f env m = + fold_env + env + (fun env key v map -> + let (env, v) = f env key v in + (env, add key v map)) + m + empty + + let of_list elts = + List.fold_left + begin + (fun acc (key, value) -> add key value acc) + end + empty + elts + + let of_function domain f = + List.fold_left + begin + (fun acc key -> add key (f key) acc) + end + empty + domain + + let add ?combine key new_value map = + match combine with + | None -> add key new_value map + | Some combine -> + adjust + key + (fun opt -> + match opt with + | None -> new_value + | Some old_value -> combine old_value new_value) + map + + let ident_map f coll = + let changed = ref false in + let new_map = + map + (fun x -> + let new_item = f x in + if new_item != x then changed := true; + new_item) + coll + in + if !changed then + new_map + else + coll + + let for_all2 ~f m1 m2 = + let key_bool_map = merge (fun k v1opt v2opt -> Some (f k v1opt v2opt)) m1 m2 in + for_all (fun _k b -> b) key_bool_map + + let make_pp pp_key pp_data fmt x = + Format.fprintf fmt "@[{"; + let bindings = bindings x in + (match bindings with + | [] -> () + | _ -> Format.fprintf fmt " "); + ignore + (List.fold_left + (fun sep (key, data) -> + if sep then Format.fprintf fmt ";@ "; + Format.fprintf fmt "@["; + pp_key fmt key; + Format.fprintf fmt " ->@ "; + pp_data fmt data; + Format.fprintf fmt "@]"; + true) + false + bindings + ); + (match bindings with + | [] -> () + | _ -> Format.fprintf fmt " "); + Format.fprintf fmt "}@]" +end diff --git a/compiler/flow_parser/collections/wrappedMap.mli b/compiler/flow_parser/collections/wrappedMap.mli new file mode 100644 index 00000000000..cc0e9bad665 --- /dev/null +++ b/compiler/flow_parser/collections/wrappedMap.mli @@ -0,0 +1,10 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module type S = WrappedMap_sig.S + +module Make (Ord : Map.OrderedType) : S with type key = Ord.t diff --git a/compiler/flow_parser/collections/wrappedMap_sig.ml b/compiler/flow_parser/collections/wrappedMap_sig.ml new file mode 100644 index 00000000000..d8abefca90f --- /dev/null +++ b/compiler/flow_parser/collections/wrappedMap_sig.ml @@ -0,0 +1,55 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module type S = sig + include Flow_map.S + + val add : ?combine:('a -> 'a -> 'a) -> key -> 'a -> 'a t -> 'a t + + val union : ?combine:(key -> 'a -> 'a -> 'a option) -> 'a t -> 'a t -> 'a t + + val union_env : + 'a -> 'b t -> 'b t -> combine:('a -> key -> 'b -> 'b -> 'a * 'b option) -> 'a * 'b t + + val merge_env : + 'a -> + 'b t -> + 'c t -> + combine:('a -> key -> 'b option -> 'c option -> 'a * 'd option) -> + 'a * 'd t + + val keys : 'a t -> key list + + val ordered_keys : 'a t -> key list + + val values : 'a t -> 'a list + + val fold_env : 'a -> ('a -> key -> 'b -> 'c -> 'a * 'c) -> 'b t -> 'c -> 'a * 'c + + val map_env : ('c -> key -> 'a -> 'c * 'b) -> 'c -> 'a t -> 'c * 'b t + + val of_list : (key * 'a) list -> 'a t + + val of_function : key list -> (key -> 'a) -> 'a t + + val elements : 'a t -> (key * 'a) list + + val ident_map : ('a -> 'a) -> 'a t -> 'a t + + val ident_map_key : ?combine:('a -> 'a -> 'a) -> (key -> key) -> 'a t -> 'a t + + val for_all2 : f:(key -> 'a option -> 'b option -> bool) -> 'a t -> 'b t -> bool + + val make_pp : + (Format.formatter -> key -> unit) -> + (Format.formatter -> 'a -> unit) -> + Format.formatter -> + 'a t -> + unit + + val of_increasing_iterator_unchecked : (unit -> key * 'a) -> int -> 'a t +end diff --git a/compiler/flow_parser/flow_sedlexing/LICENSE b/compiler/flow_parser/flow_sedlexing/LICENSE new file mode 100644 index 00000000000..630eb99d847 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright 2005, 2014 by Alain Frisch and LexiFi. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/compiler/flow_parser/flow_sedlexing/dune b/compiler/flow_parser/flow_sedlexing/dune new file mode 100644 index 00000000000..00afcea04a7 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing/dune @@ -0,0 +1,3 @@ +(library + (name flow_sedlexing) + (wrapped false)) diff --git a/compiler/flow_parser/flow_sedlexing/flow_sedlexing.ml b/compiler/flow_parser/flow_sedlexing/flow_sedlexing.ml new file mode 100644 index 00000000000..1aec0e2ee51 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing/flow_sedlexing.ml @@ -0,0 +1,287 @@ +(* The package sedlex is released under the terms of an MIT-like license. *) +(* See the attached LICENSE file. *) +(* Copyright 2005, 2013 by Alain Frisch and LexiFi. *) +external ( .!()<- ) : int array -> int -> int -> unit = "%array_unsafe_set" +external ( .!() ) : int array -> int -> int = "%array_unsafe_get" +external ( .![] ) : string -> int -> char = "%string_unsafe_get" +external ( .![]<- ) : bytes -> int -> char -> unit = "%bytes_unsafe_set" + +exception InvalidCodepoint of int + +exception MalFormed + +(* Absolute position from the beginning of the stream *) +type apos = int + +(* critical states: + [pos] [curr_bol] [curr_line] + The state of [curr_bol] and [curr_line] only changes when we hit a newline + [marked_pos] [marked_bol] [marked_line] + [start_pos] [start_bol] [start_line] + get reset whenever we get a new token +*) +type lexbuf = { + buf: int array; + (* Number of meaningful char in buffer *) + len: int; + (* pos is the index in the buffer *) + mutable pos: int; + (* bol is the index in the input stream but not buffer *) + mutable curr_bol: int; + (* start from 1, if it is 0, we would not track postion info for you *) + mutable curr_line: int; + (* First char we need to keep visible *) + mutable start_pos: int; + mutable start_bol: int; + mutable start_line: int; + mutable marked_pos: int; + mutable marked_bol: int; + mutable marked_line: int; + mutable marked_val: int; +} + + +let lexbuf_clone (x : lexbuf) : lexbuf = + { + buf = x.buf; + len = x.len; + pos = x.pos; + curr_bol = x.curr_bol; + curr_line = x.curr_line; + start_pos = x.start_pos; + start_bol = x.start_bol; + start_line = x.start_line; + marked_pos = x.marked_pos; + marked_bol = x.marked_bol; + marked_line = x.marked_line; + marked_val = x.marked_val; + } + +let empty_lexbuf = + { + buf = [||]; + len = 0; + pos = 0; + curr_bol = 0; + curr_line = 0; + start_pos = 0; + start_bol = 0; + start_line = 0; + marked_pos = 0; + marked_bol = 0; + marked_line = 0; + marked_val = 0; + } + +let from_int_array a = + let len = Array.length a in + { empty_lexbuf with buf = a; len } + +let from_int_sub_array a len = + { empty_lexbuf with buf = a; len } + +let new_line lexbuf = + if lexbuf.curr_line != 0 then lexbuf.curr_line <- lexbuf.curr_line + 1; + lexbuf.curr_bol <- lexbuf.pos + +let next lexbuf : Stdlib.Uchar.t option = + if lexbuf.pos = lexbuf.len then + None + else + let ret = lexbuf.buf.!(lexbuf.pos) in + lexbuf.pos <- lexbuf.pos + 1; + if ret = 10 then new_line lexbuf; + Some (Stdlib.Uchar.unsafe_of_int ret) + +let __private__next_int lexbuf : int = + if lexbuf.pos = lexbuf.len then + -1 + else + let ret = lexbuf.buf.!(lexbuf.pos) in + lexbuf.pos <- lexbuf.pos + 1; + if ret = 10 then new_line lexbuf; + ret + +let mark lexbuf i = + lexbuf.marked_pos <- lexbuf.pos; + lexbuf.marked_bol <- lexbuf.curr_bol; + lexbuf.marked_line <- lexbuf.curr_line; + lexbuf.marked_val <- i + +let start lexbuf = + lexbuf.start_pos <- lexbuf.pos; + lexbuf.start_bol <- lexbuf.curr_bol; + lexbuf.start_line <- lexbuf.curr_line; + mark lexbuf (-1) + +let backtrack lexbuf = + lexbuf.pos <- lexbuf.marked_pos; + lexbuf.curr_bol <- lexbuf.marked_bol; + lexbuf.curr_line <- lexbuf.marked_line; + lexbuf.marked_val + +let rollback lexbuf = + lexbuf.pos <- lexbuf.start_pos; + lexbuf.curr_bol <- lexbuf.start_bol; + lexbuf.curr_line <- lexbuf.start_line + +let lexeme_start lexbuf = lexbuf.start_pos +let set_lexeme_start lexbuf pos = lexbuf.start_pos <- pos +let lexeme_end lexbuf = lexbuf.pos + +let loc lexbuf = (lexbuf.start_pos , lexbuf.pos ) + +let lexeme_length lexbuf = lexbuf.pos - lexbuf.start_pos + +let sub_lexeme lexbuf pos len = Array.sub lexbuf.buf (lexbuf.start_pos + pos) len + +let lexeme lexbuf = Array.sub lexbuf.buf lexbuf.start_pos (lexbuf.pos - lexbuf.start_pos) + +let current_code_point lexbuf = lexbuf.buf.(lexbuf.start_pos) +(* Decode UTF-8 encoded [s] into codepoints in [a], returning the length of the + * decoded string. + * + * To call this function safely: + * - ensure that [slen] is not greater than the length of [s] + * - ensure that [a] has enough capacity to hold the decoded value + *) +let unsafe_utf8_of_string (s : string) slen (a : int array) : int = + let spos = ref 0 in + let apos = ref 0 in + while !spos < slen do + let spos_code = s.![!spos] in + (match spos_code with + | '\000' .. '\127' as c -> + (* U+0000 - U+007F: 0xxxxxxx *) + a.!(!apos) <- Char.code c; + incr spos + | '\192' .. '\223' as c -> + (* U+0080 - U+07FF: 110xxxxx 10xxxxxx *) + let n1 = Char.code c in + let n2 = Char.code s.![!spos + 1] in + if n2 lsr 6 != 0b10 then raise MalFormed; + a.!(!apos) <- ((n1 land 0x1f) lsl 6) lor (n2 land 0x3f); + spos := !spos + 2 + | '\224' .. '\239' as c -> + (* U+0800 - U+FFFF: 1110xxxx 10xxxxxx 10xxxxxx + U+D800 - U+DFFF are reserved for surrogate halves (RFC 3629) *) + let n1 = Char.code c in + let n2 = Char.code s.![!spos + 1] in + let n3 = Char.code s.![!spos + 2] in + let p = ((n1 land 0x0f) lsl 12) lor ((n2 land 0x3f) lsl 6) lor (n3 land 0x3f) in + if (n2 lsr 6 != 0b10 || n3 lsr 6 != 0b10) || (p >= 0xd800 && p <= 0xdfff) then raise MalFormed; + a.!(!apos) <- p; + spos := !spos + 3 + | '\240' .. '\247' as c -> + (* U+10000 - U+1FFFFF: 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx + > U+10FFFF are invalid (RFC 3629) *) + let n1 = Char.code c in + let n2 = Char.code s.![!spos + 1] in + let n3 = Char.code s.![!spos + 2] in + let n4 = Char.code s.![!spos + 3] in + if n2 lsr 6 != 0b10 || n3 lsr 6 != 0b10 || n4 lsr 6 != 0b10 then raise MalFormed; + let p = + ((n1 land 0x07) lsl 18) + lor ((n2 land 0x3f) lsl 12) + lor ((n3 land 0x3f) lsl 6) + lor (n4 land 0x3f) + in + if p > 0x10ffff then raise MalFormed; + a.!(!apos) <- p; + spos := !spos + 4 + | _ -> raise MalFormed); + incr apos + done; + !apos + +(* Encode the decoded codepoints in [a] as UTF-8 into [b], returning the length + * of the encoded string. + * + * To call this function safely: + * - ensure that [offset + len] is not greater than the length of [a] + * - ensure that [b] has sufficient capacity to hold the encoded value + *) +let unsafe_string_of_utf8 (a : int array) ~(offset : int) ~(len : int) (b : bytes) : int = + let apos = ref offset in + let len = ref len in + let i = ref 0 in + while !len > 0 do + let u = a.!(!apos) in + if u < 0 then + raise MalFormed + else if u <= 0x007F then begin + b.![!i] <- Char.unsafe_chr u; + incr i + end else if u <= 0x07FF then ( + b.![!i] <- Char.unsafe_chr (0xC0 lor (u lsr 6)); + b.![!i + 1] <- Char.unsafe_chr (0x80 lor (u land 0x3F)); + i := !i + 2 + ) else if u <= 0xFFFF then ( + b.![!i] <- Char.unsafe_chr (0xE0 lor (u lsr 12)); + b.![!i + 1] <- Char.unsafe_chr (0x80 lor ((u lsr 6) land 0x3F)); + b.![!i + 2] <- Char.unsafe_chr (0x80 lor (u land 0x3F)); + i := !i + 3 + ) else if u <= 0x10FFFF then ( + b.![!i] <- Char.unsafe_chr (0xF0 lor (u lsr 18)); + b.![!i + 1] <- Char.unsafe_chr (0x80 lor ((u lsr 12) land 0x3F)); + b.![!i + 2] <- Char.unsafe_chr (0x80 lor ((u lsr 6) land 0x3F)); + b.![!i + 3] <- Char.unsafe_chr (0x80 lor (u land 0x3F)); + i := !i + 4 + ) else + raise MalFormed; + incr apos; + decr len + done; + !i + +module Utf8 = struct + let from_string s = + let slen = String.length s in + let a = Array.make slen 0 in + let len = unsafe_utf8_of_string s slen a in + from_int_sub_array a len + + let sub_lexeme lexbuf pos len : string = + let offset = lexbuf.start_pos + pos in + let b = Bytes.create (len * 4) in + let buf = lexbuf.buf in + (* Assertion needed, since we make use of unsafe API below *) + assert (offset + len <= Array.length buf); + let i = unsafe_string_of_utf8 buf ~offset ~len b in + Bytes.sub_string b 0 i + + let lexeme lexbuf : string = + let offset = lexbuf.start_pos in + let len = lexbuf.pos - offset in + let b = Bytes.create (len * 4) in + let buf = lexbuf.buf in + let i = unsafe_string_of_utf8 buf ~offset ~len b in + Bytes.sub_string b 0 i + + let lexeme_to_buffer lexbuf buffer : unit = + let offset = lexbuf.start_pos in + let len = lexbuf.pos - offset in + let b = Bytes.create (len * 4) in + let buf = lexbuf.buf in + let i = unsafe_string_of_utf8 buf ~offset ~len b in + Buffer.add_subbytes buffer b 0 i + + let lexeme_to_buffer2 lexbuf buf1 buf2 : unit = + let offset = lexbuf.start_pos in + let len = lexbuf.pos - offset in + let b = Bytes.create (len * 4) in + let buf = lexbuf.buf in + let i = unsafe_string_of_utf8 buf ~offset ~len b in + Buffer.add_subbytes buf1 b 0 i; + Buffer.add_subbytes buf2 b 0 i +end + +let string_of_utf8 (lexbuf : int array) : string = + let offset = 0 in + let len = Array.length lexbuf in + let b = Bytes.create (len * 4) in + let i = unsafe_string_of_utf8 lexbuf ~offset ~len b in + Bytes.sub_string b 0 i + +let backoff lexbuf npos = + lexbuf.pos <- lexbuf.pos - npos diff --git a/compiler/flow_parser/flow_sedlexing/flow_sedlexing.mli b/compiler/flow_parser/flow_sedlexing/flow_sedlexing.mli new file mode 100644 index 00000000000..bbae88219dc --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing/flow_sedlexing.mli @@ -0,0 +1,47 @@ + +(** This is a module provides the minimal Sedlexing suppport + It is mostly a subset of Sedlexing with two functions for performance reasons: + - Utf8.lexeme_to_buffer + - Utf8.lexeme_to_buffer2 +*) +exception InvalidCodepoint of int +exception MalFormed +type apos = int +type lexbuf +val lexbuf_clone : lexbuf -> lexbuf + +val from_int_array : int array -> lexbuf +val new_line : lexbuf -> unit +val next : lexbuf -> Uchar.t option + +(**/**) +val __private__next_int : lexbuf -> int +(**/**) + +val mark : lexbuf -> int -> unit +val start : lexbuf -> unit +val backtrack : lexbuf -> int +val rollback : lexbuf -> unit +val lexeme_start : lexbuf -> int +val lexeme_end : lexbuf -> int +val loc : lexbuf -> int * int +val lexeme_length : lexbuf -> int +val sub_lexeme : lexbuf -> int -> int -> int array +val lexeme : lexbuf -> int array +module Utf8 : sig + val from_string : string -> lexbuf + val sub_lexeme : lexbuf -> int -> int -> string + val lexeme : lexbuf -> string + (* This API avoids another allocation. *) + val lexeme_to_buffer : lexbuf -> Buffer.t -> unit + val lexeme_to_buffer2 : lexbuf -> Buffer.t -> Buffer.t -> unit +end + +val string_of_utf8 : int array -> string + +(** Two APIs used when we want to do customize lexing + instead of using the regex based engine +*) +val current_code_point : lexbuf -> int +val backoff : lexbuf -> int -> unit +val set_lexeme_start : lexbuf -> int -> unit diff --git a/compiler/flow_parser/flow_sedlexing_ppx/LICENSE b/compiler/flow_parser/flow_sedlexing_ppx/LICENSE new file mode 100644 index 00000000000..630eb99d847 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing_ppx/LICENSE @@ -0,0 +1,22 @@ +The MIT License (MIT) + +Copyright 2005, 2014 by Alain Frisch and LexiFi. + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/compiler/flow_parser/flow_sedlexing_ppx/dune b/compiler/flow_parser/flow_sedlexing_ppx/dune new file mode 100644 index 00000000000..8c8933bf8a4 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing_ppx/dune @@ -0,0 +1,9 @@ +(library + (name flow_sedlexing_ppx) + (kind ppx_rewriter) + (libraries ppxlib flow_sedlexing) + (ppx_runtime_libraries flow_sedlexing) + (preprocess + (pps ppxlib.metaquot)) + (flags + (:standard -w -9))) diff --git a/compiler/flow_parser/flow_sedlexing_ppx/flow_sedlex.ml b/compiler/flow_parser/flow_sedlexing_ppx/flow_sedlex.ml new file mode 100644 index 00000000000..4ff7f765bf6 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing_ppx/flow_sedlex.ml @@ -0,0 +1,145 @@ +(* The package sedlex is released under the terms of an MIT-like license. *) +(* See the attached LICENSE file. *) +(* Copyright 2005, 2013 by Alain Frisch and LexiFi. *) + +module Cset = Sedlex_cset + +(* NFA *) + +type node = { + id : int; + mutable eps : node list; + mutable trans : (Cset.t * node) list; +} + +(* Compilation regexp -> NFA *) + +type regexp = node -> node + +let cur_id = ref 0 +let new_node () = + incr cur_id; + { id = !cur_id; eps = []; trans = [] } + +let seq r1 r2 succ = r1 (r2 succ) + +let is_chars final = function + | {eps = []; trans = [c, f]} when f == final -> Some c + | _ -> None + +let chars c succ = + let n = new_node () in + n.trans <- [c,succ]; + n + +let alt r1 r2 succ = + let nr1 = r1 succ and nr2 = r2 succ in + match is_chars succ nr1, is_chars succ nr2 with + | Some c1, Some c2 -> chars (Cset.union c1 c2) succ + | _ -> + let n = new_node () in + n.eps <- [nr1; nr2]; + n + +let rep r succ = + let n = new_node () in + n.eps <- [r n; succ]; + n + +let plus r succ = + let n = new_node () in + let nr = r n in + n.eps <- [nr; succ]; + nr + +let eps succ = succ (* eps for epsilon *) + +let compl r = + let n = new_node () in + match is_chars n (r n) with + | Some c -> + Some (chars (Cset.difference Cset.any c)) + | _ -> + None + +let pair_op f r0 r1 = (* Construct subtract or intersection *) + let n = new_node () in + let to_chars r = is_chars n (r n) in + match to_chars r0, to_chars r1 with + | Some c0, Some c1 -> + Some (chars (f c0 c1)) + | _ -> + None + +let subtract = pair_op Cset.difference + +let intersection = pair_op Cset.intersection + +let compile_re re = + let final = new_node () in + (re final, final) + +(* Determinization *) + +type state = node list + (* A state of the DFA corresponds to a set of nodes in the NFA. *) + +let rec add_node state node = + if List.memq node state then state else add_nodes (node::state) node.eps +and add_nodes state nodes = + List.fold_left add_node state nodes + + +let transition (state : state) = + (* Merge transition with the same target *) + let rec norm = function + | (c1, n1)::((c2, n2)::q as l) -> + if n1 == n2 then norm ((Cset.union c1 c2, n1)::q) + else (c1, n1)::(norm l) + | l -> l in + let t = List.concat (List.map (fun n -> n.trans) state) in + let t = norm (List.sort (fun (_, n1) (_, n2) -> n1.id - n2.id) t) in + + (* Split char sets so as to make them disjoint *) + let split (all, t) (c0, n0) = + let t = + (Cset.difference c0 all, [n0]) :: + List.map (fun (c, ns) -> (Cset.intersection c c0, n0::ns)) t @ + List.map (fun (c, ns) -> (Cset.difference c c0, ns)) t + in + Cset.union all c0, + List.filter (fun (c, _) -> not (Cset.is_empty c)) t + in + + let (_,t) = List.fold_left split (Cset.empty,[]) t in + + (* Epsilon closure of targets *) + let t = List.map (fun (c, ns) -> (c, add_nodes [] ns)) t in + + (* Canonical ordering *) + let t = Array.of_list t in + Array.sort (fun (c1, _) (c2, _) -> compare c1 c2) t; + t + +let compile rs = + let rs = Array.map compile_re rs in + let counter = ref 0 in + let states = Hashtbl.create 31 in + let states_def = Hashtbl.create 31 in + let rec aux state = + try Hashtbl.find states state + with Not_found -> + let i = !counter in + incr counter; + Hashtbl.add states state i; + let trans = transition state in + let trans = Array.map (fun (p, t) -> (p, aux t)) trans in + let finals = Array.map (fun (_, f) -> List.memq f state) rs in + Hashtbl.add states_def i (trans, finals); + i + in + let init = ref [] in + Array.iter (fun (i,_) -> init := add_node !init i) rs; + let i = aux !init in + assert(i = 0); + Array.init !counter (Hashtbl.find states_def) diff --git a/compiler/flow_parser/flow_sedlexing_ppx/flow_sedlex.mli b/compiler/flow_parser/flow_sedlexing_ppx/flow_sedlex.mli new file mode 100644 index 00000000000..d0e03926aa3 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing_ppx/flow_sedlex.mli @@ -0,0 +1,24 @@ +(* The package sedlex is released under the terms of an MIT-like license. *) +(* See the attached LICENSE file. *) +(* Copyright 2005, 2013 by Alain Frisch and LexiFi. *) + +type regexp + +val chars: Sedlex_cset.t -> regexp +val seq: regexp -> regexp -> regexp +val alt: regexp -> regexp -> regexp +val rep: regexp -> regexp +val plus: regexp -> regexp +val eps: regexp + +val compl: regexp -> regexp option + (* If the argument is a single [chars] regexp, returns a regexp + which matches the complement set. Otherwise returns [None]. *) +val subtract: regexp -> regexp -> regexp option + (* If each argument is a single [chars] regexp, returns a regexp + which matches the set (arg1 - arg2). Otherwise returns [None]. *) +val intersection: regexp -> regexp -> regexp option + (* If each argument is a single [chars] regexp, returns a regexp + which matches the intersection set. Otherwise returns [None]. *) + +val compile: regexp array -> ((Sedlex_cset.t * int) array * bool array) array diff --git a/compiler/flow_parser/flow_sedlexing_ppx/ppx_sedlex.ml b/compiler/flow_parser/flow_sedlexing_ppx/ppx_sedlex.ml new file mode 100644 index 00000000000..80f0db4bfcb --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing_ppx/ppx_sedlex.ml @@ -0,0 +1,648 @@ +(* The package sedlex is released under the terms of an MIT-like license. *) +(* See the attached LICENSE file. *) +(* Copyright 2005, 2013 by Alain Frisch and LexiFi. *) + +open Ppxlib +open Ast_builder.Default +open Ast_helper + +(* let ocaml_version = Versions.ocaml_408 *) + +module Sedlexing = Flow_sedlexing +module Cset = Sedlex_cset + +module UnicodeProperties = struct + + let id_start = + [0x41, 0x5a; 0x61, 0x7a; 0xaa, 0xaa; 0xb5, 0xb5; 0xba, 0xba; + 0xc0, 0xd6; 0xd8, 0xf6; 0xf8, 0x1ba; 0x1bb, 0x1bb; 0x1bc, 0x1bf; + 0x1c0, 0x1c3; 0x1c4, 0x293; 0x294, 0x294; 0x295, 0x2af; 0x2b0, 0x2c1; + 0x2c6, 0x2d1; 0x2e0, 0x2e4; 0x2ec, 0x2ec; 0x2ee, 0x2ee; 0x370, 0x373; + 0x374, 0x374; 0x376, 0x377; 0x37a, 0x37a; 0x37b, 0x37d; 0x37f, 0x37f; + 0x386, 0x386; 0x388, 0x38a; 0x38c, 0x38c; 0x38e, 0x3a1; 0x3a3, 0x3f5; + 0x3f7, 0x481; 0x48a, 0x52f; 0x531, 0x556; 0x559, 0x559; 0x560, 0x588; + 0x5d0, 0x5ea; 0x5ef, 0x5f2; 0x620, 0x63f; 0x640, 0x640; 0x641, 0x64a; + 0x66e, 0x66f; 0x671, 0x6d3; 0x6d5, 0x6d5; 0x6e5, 0x6e6; 0x6ee, 0x6ef; + 0x6fa, 0x6fc; 0x6ff, 0x6ff; 0x710, 0x710; 0x712, 0x72f; 0x74d, 0x7a5; + 0x7b1, 0x7b1; 0x7ca, 0x7ea; 0x7f4, 0x7f5; 0x7fa, 0x7fa; 0x800, 0x815; + 0x81a, 0x81a; 0x824, 0x824; 0x828, 0x828; 0x840, 0x858; 0x860, 0x86a; + 0x870, 0x887; 0x889, 0x88e; 0x8a0, 0x8c8; 0x8c9, 0x8c9; 0x904, 0x939; + 0x93d, 0x93d; 0x950, 0x950; 0x958, 0x961; 0x971, 0x971; 0x972, 0x980; + 0x985, 0x98c; 0x98f, 0x990; 0x993, 0x9a8; 0x9aa, 0x9b0; 0x9b2, 0x9b2; + 0x9b6, 0x9b9; 0x9bd, 0x9bd; 0x9ce, 0x9ce; 0x9dc, 0x9dd; 0x9df, 0x9e1; + 0x9f0, 0x9f1; 0x9fc, 0x9fc; 0xa05, 0xa0a; 0xa0f, 0xa10; 0xa13, 0xa28; + 0xa2a, 0xa30; 0xa32, 0xa33; 0xa35, 0xa36; 0xa38, 0xa39; 0xa59, 0xa5c; + 0xa5e, 0xa5e; 0xa72, 0xa74; 0xa85, 0xa8d; 0xa8f, 0xa91; 0xa93, 0xaa8; + 0xaaa, 0xab0; 0xab2, 0xab3; 0xab5, 0xab9; 0xabd, 0xabd; 0xad0, 0xad0; + 0xae0, 0xae1; 0xaf9, 0xaf9; 0xb05, 0xb0c; 0xb0f, 0xb10; 0xb13, 0xb28; + 0xb2a, 0xb30; 0xb32, 0xb33; 0xb35, 0xb39; 0xb3d, 0xb3d; 0xb5c, 0xb5d; + 0xb5f, 0xb61; 0xb71, 0xb71; 0xb83, 0xb83; 0xb85, 0xb8a; 0xb8e, 0xb90; + 0xb92, 0xb95; 0xb99, 0xb9a; 0xb9c, 0xb9c; 0xb9e, 0xb9f; 0xba3, 0xba4; + 0xba8, 0xbaa; 0xbae, 0xbb9; 0xbd0, 0xbd0; 0xc05, 0xc0c; 0xc0e, 0xc10; + 0xc12, 0xc28; 0xc2a, 0xc39; 0xc3d, 0xc3d; 0xc58, 0xc5a; 0xc5d, 0xc5d; + 0xc60, 0xc61; 0xc80, 0xc80; 0xc85, 0xc8c; 0xc8e, 0xc90; 0xc92, 0xca8; + 0xcaa, 0xcb3; 0xcb5, 0xcb9; 0xcbd, 0xcbd; 0xcdd, 0xcde; 0xce0, 0xce1; + 0xcf1, 0xcf2; 0xd04, 0xd0c; 0xd0e, 0xd10; 0xd12, 0xd3a; 0xd3d, 0xd3d; + 0xd4e, 0xd4e; 0xd54, 0xd56; 0xd5f, 0xd61; 0xd7a, 0xd7f; 0xd85, 0xd96; + 0xd9a, 0xdb1; 0xdb3, 0xdbb; 0xdbd, 0xdbd; 0xdc0, 0xdc6; 0xe01, 0xe30; + 0xe32, 0xe33; 0xe40, 0xe45; 0xe46, 0xe46; 0xe81, 0xe82; 0xe84, 0xe84; + 0xe86, 0xe8a; 0xe8c, 0xea3; 0xea5, 0xea5; 0xea7, 0xeb0; 0xeb2, 0xeb3; + 0xebd, 0xebd; 0xec0, 0xec4; 0xec6, 0xec6; 0xedc, 0xedf; 0xf00, 0xf00; + 0xf40, 0xf47; 0xf49, 0xf6c; 0xf88, 0xf8c; 0x1000, 0x102a; 0x103f, 0x103f; + 0x1050, 0x1055; 0x105a, 0x105d; 0x1061, 0x1061; 0x1065, 0x1066; 0x106e, 0x1070; + 0x1075, 0x1081; 0x108e, 0x108e; 0x10a0, 0x10c5; 0x10c7, 0x10c7; 0x10cd, 0x10cd; + 0x10d0, 0x10fa; 0x10fc, 0x10fc; 0x10fd, 0x10ff; 0x1100, 0x1248; 0x124a, 0x124d; + 0x1250, 0x1256; 0x1258, 0x1258; 0x125a, 0x125d; 0x1260, 0x1288; 0x128a, 0x128d; + 0x1290, 0x12b0; 0x12b2, 0x12b5; 0x12b8, 0x12be; 0x12c0, 0x12c0; 0x12c2, 0x12c5; + 0x12c8, 0x12d6; 0x12d8, 0x1310; 0x1312, 0x1315; 0x1318, 0x135a; 0x1380, 0x138f; + 0x13a0, 0x13f5; 0x13f8, 0x13fd; 0x1401, 0x166c; 0x166f, 0x167f; 0x1681, 0x169a; + 0x16a0, 0x16ea; 0x16ee, 0x16f0; 0x16f1, 0x16f8; 0x1700, 0x1711; 0x171f, 0x1731; + 0x1740, 0x1751; 0x1760, 0x176c; 0x176e, 0x1770; 0x1780, 0x17b3; 0x17d7, 0x17d7; + 0x17dc, 0x17dc; 0x1820, 0x1842; 0x1843, 0x1843; 0x1844, 0x1878; 0x1880, 0x1884; + 0x1885, 0x1886; 0x1887, 0x18a8; 0x18aa, 0x18aa; 0x18b0, 0x18f5; 0x1900, 0x191e; + 0x1950, 0x196d; 0x1970, 0x1974; 0x1980, 0x19ab; 0x19b0, 0x19c9; 0x1a00, 0x1a16; + 0x1a20, 0x1a54; 0x1aa7, 0x1aa7; 0x1b05, 0x1b33; 0x1b45, 0x1b4c; 0x1b83, 0x1ba0; + 0x1bae, 0x1baf; 0x1bba, 0x1be5; 0x1c00, 0x1c23; 0x1c4d, 0x1c4f; 0x1c5a, 0x1c77; + 0x1c78, 0x1c7d; 0x1c80, 0x1c88; 0x1c90, 0x1cba; 0x1cbd, 0x1cbf; 0x1ce9, 0x1cec; + 0x1cee, 0x1cf3; 0x1cf5, 0x1cf6; 0x1cfa, 0x1cfa; 0x1d00, 0x1d2b; 0x1d2c, 0x1d6a; + 0x1d6b, 0x1d77; 0x1d78, 0x1d78; 0x1d79, 0x1d9a; 0x1d9b, 0x1dbf; 0x1e00, 0x1f15; + 0x1f18, 0x1f1d; 0x1f20, 0x1f45; 0x1f48, 0x1f4d; 0x1f50, 0x1f57; 0x1f59, 0x1f59; + 0x1f5b, 0x1f5b; 0x1f5d, 0x1f5d; 0x1f5f, 0x1f7d; 0x1f80, 0x1fb4; 0x1fb6, 0x1fbc; + 0x1fbe, 0x1fbe; 0x1fc2, 0x1fc4; 0x1fc6, 0x1fcc; 0x1fd0, 0x1fd3; 0x1fd6, 0x1fdb; + 0x1fe0, 0x1fec; 0x1ff2, 0x1ff4; 0x1ff6, 0x1ffc; 0x2071, 0x2071; 0x207f, 0x207f; + 0x2090, 0x209c; 0x2102, 0x2102; 0x2107, 0x2107; 0x210a, 0x2113; 0x2115, 0x2115; + 0x2118, 0x2118; 0x2119, 0x211d; 0x2124, 0x2124; 0x2126, 0x2126; 0x2128, 0x2128; + 0x212a, 0x212d; 0x212e, 0x212e; 0x212f, 0x2134; 0x2135, 0x2138; 0x2139, 0x2139; + 0x213c, 0x213f; 0x2145, 0x2149; 0x214e, 0x214e; 0x2160, 0x2182; 0x2183, 0x2184; + 0x2185, 0x2188; 0x2c00, 0x2c7b; 0x2c7c, 0x2c7d; 0x2c7e, 0x2ce4; 0x2ceb, 0x2cee; + 0x2cf2, 0x2cf3; 0x2d00, 0x2d25; 0x2d27, 0x2d27; 0x2d2d, 0x2d2d; 0x2d30, 0x2d67; + 0x2d6f, 0x2d6f; 0x2d80, 0x2d96; 0x2da0, 0x2da6; 0x2da8, 0x2dae; 0x2db0, 0x2db6; + 0x2db8, 0x2dbe; 0x2dc0, 0x2dc6; 0x2dc8, 0x2dce; 0x2dd0, 0x2dd6; 0x2dd8, 0x2dde; + 0x3005, 0x3005; 0x3006, 0x3006; 0x3007, 0x3007; 0x3021, 0x3029; 0x3031, 0x3035; + 0x3038, 0x303a; 0x303b, 0x303b; 0x303c, 0x303c; 0x3041, 0x3096; 0x309b, 0x309c; + 0x309d, 0x309e; 0x309f, 0x309f; 0x30a1, 0x30fa; 0x30fc, 0x30fe; 0x30ff, 0x30ff; + 0x3105, 0x312f; 0x3131, 0x318e; 0x31a0, 0x31bf; 0x31f0, 0x31ff; 0x3400, 0x4dbf; + 0x4e00, 0xa014; 0xa015, 0xa015; 0xa016, 0xa48c; 0xa4d0, 0xa4f7; 0xa4f8, 0xa4fd; + 0xa500, 0xa60b; 0xa60c, 0xa60c; 0xa610, 0xa61f; 0xa62a, 0xa62b; 0xa640, 0xa66d; + 0xa66e, 0xa66e; 0xa67f, 0xa67f; 0xa680, 0xa69b; 0xa69c, 0xa69d; 0xa6a0, 0xa6e5; + 0xa6e6, 0xa6ef; 0xa717, 0xa71f; 0xa722, 0xa76f; 0xa770, 0xa770; 0xa771, 0xa787; + 0xa788, 0xa788; 0xa78b, 0xa78e; 0xa78f, 0xa78f; 0xa790, 0xa7ca; 0xa7d0, 0xa7d1; + 0xa7d3, 0xa7d3; 0xa7d5, 0xa7d9; 0xa7f2, 0xa7f4; 0xa7f5, 0xa7f6; 0xa7f7, 0xa7f7; + 0xa7f8, 0xa7f9; 0xa7fa, 0xa7fa; 0xa7fb, 0xa801; 0xa803, 0xa805; 0xa807, 0xa80a; + 0xa80c, 0xa822; 0xa840, 0xa873; 0xa882, 0xa8b3; 0xa8f2, 0xa8f7; 0xa8fb, 0xa8fb; + 0xa8fd, 0xa8fe; 0xa90a, 0xa925; 0xa930, 0xa946; 0xa960, 0xa97c; 0xa984, 0xa9b2; + 0xa9cf, 0xa9cf; 0xa9e0, 0xa9e4; 0xa9e6, 0xa9e6; 0xa9e7, 0xa9ef; 0xa9fa, 0xa9fe; + 0xaa00, 0xaa28; 0xaa40, 0xaa42; 0xaa44, 0xaa4b; 0xaa60, 0xaa6f; 0xaa70, 0xaa70; + 0xaa71, 0xaa76; 0xaa7a, 0xaa7a; 0xaa7e, 0xaaaf; 0xaab1, 0xaab1; 0xaab5, 0xaab6; + 0xaab9, 0xaabd; 0xaac0, 0xaac0; 0xaac2, 0xaac2; 0xaadb, 0xaadc; 0xaadd, 0xaadd; + 0xaae0, 0xaaea; 0xaaf2, 0xaaf2; 0xaaf3, 0xaaf4; 0xab01, 0xab06; 0xab09, 0xab0e; + 0xab11, 0xab16; 0xab20, 0xab26; 0xab28, 0xab2e; 0xab30, 0xab5a; 0xab5c, 0xab5f; + 0xab60, 0xab68; 0xab69, 0xab69; 0xab70, 0xabbf; 0xabc0, 0xabe2; 0xac00, 0xd7a3; + 0xd7b0, 0xd7c6; 0xd7cb, 0xd7fb; 0xf900, 0xfa6d; 0xfa70, 0xfad9; 0xfb00, 0xfb06; + 0xfb13, 0xfb17; 0xfb1d, 0xfb1d; 0xfb1f, 0xfb28; 0xfb2a, 0xfb36; 0xfb38, 0xfb3c; + 0xfb3e, 0xfb3e; 0xfb40, 0xfb41; 0xfb43, 0xfb44; 0xfb46, 0xfbb1; 0xfbd3, 0xfd3d; + 0xfd50, 0xfd8f; 0xfd92, 0xfdc7; 0xfdf0, 0xfdfb; 0xfe70, 0xfe74; 0xfe76, 0xfefc; + 0xff21, 0xff3a; 0xff41, 0xff5a; 0xff66, 0xff6f; 0xff70, 0xff70; 0xff71, 0xff9d; + 0xff9e, 0xff9f; 0xffa0, 0xffbe; 0xffc2, 0xffc7; 0xffca, 0xffcf; 0xffd2, 0xffd7; + 0xffda, 0xffdc; 0x10000, 0x1000b; 0x1000d, 0x10026; 0x10028, 0x1003a; 0x1003c, 0x1003d; + 0x1003f, 0x1004d; 0x10050, 0x1005d; 0x10080, 0x100fa; 0x10140, 0x10174; 0x10280, 0x1029c; + 0x102a0, 0x102d0; 0x10300, 0x1031f; 0x1032d, 0x10340; 0x10341, 0x10341; 0x10342, 0x10349; + 0x1034a, 0x1034a; 0x10350, 0x10375; 0x10380, 0x1039d; 0x103a0, 0x103c3; 0x103c8, 0x103cf; + 0x103d1, 0x103d5; 0x10400, 0x1044f; 0x10450, 0x1049d; 0x104b0, 0x104d3; 0x104d8, 0x104fb; + 0x10500, 0x10527; 0x10530, 0x10563; 0x10570, 0x1057a; 0x1057c, 0x1058a; 0x1058c, 0x10592; + 0x10594, 0x10595; 0x10597, 0x105a1; 0x105a3, 0x105b1; 0x105b3, 0x105b9; 0x105bb, 0x105bc; + 0x10600, 0x10736; 0x10740, 0x10755; 0x10760, 0x10767; 0x10780, 0x10785; 0x10787, 0x107b0; + 0x107b2, 0x107ba; 0x10800, 0x10805; 0x10808, 0x10808; 0x1080a, 0x10835; 0x10837, 0x10838; + 0x1083c, 0x1083c; 0x1083f, 0x10855; 0x10860, 0x10876; 0x10880, 0x1089e; 0x108e0, 0x108f2; + 0x108f4, 0x108f5; 0x10900, 0x10915; 0x10920, 0x10939; 0x10980, 0x109b7; 0x109be, 0x109bf; + 0x10a00, 0x10a00; 0x10a10, 0x10a13; 0x10a15, 0x10a17; 0x10a19, 0x10a35; 0x10a60, 0x10a7c; + 0x10a80, 0x10a9c; 0x10ac0, 0x10ac7; 0x10ac9, 0x10ae4; 0x10b00, 0x10b35; 0x10b40, 0x10b55; + 0x10b60, 0x10b72; 0x10b80, 0x10b91; 0x10c00, 0x10c48; 0x10c80, 0x10cb2; 0x10cc0, 0x10cf2; + 0x10d00, 0x10d23; 0x10e80, 0x10ea9; 0x10eb0, 0x10eb1; 0x10f00, 0x10f1c; 0x10f27, 0x10f27; + 0x10f30, 0x10f45; 0x10f70, 0x10f81; 0x10fb0, 0x10fc4; 0x10fe0, 0x10ff6; 0x11003, 0x11037; + 0x11071, 0x11072; 0x11075, 0x11075; 0x11083, 0x110af; 0x110d0, 0x110e8; 0x11103, 0x11126; + 0x11144, 0x11144; 0x11147, 0x11147; 0x11150, 0x11172; 0x11176, 0x11176; 0x11183, 0x111b2; + 0x111c1, 0x111c4; 0x111da, 0x111da; 0x111dc, 0x111dc; 0x11200, 0x11211; 0x11213, 0x1122b; + 0x11280, 0x11286; 0x11288, 0x11288; 0x1128a, 0x1128d; 0x1128f, 0x1129d; 0x1129f, 0x112a8; + 0x112b0, 0x112de; 0x11305, 0x1130c; 0x1130f, 0x11310; 0x11313, 0x11328; 0x1132a, 0x11330; + 0x11332, 0x11333; 0x11335, 0x11339; 0x1133d, 0x1133d; 0x11350, 0x11350; 0x1135d, 0x11361; + 0x11400, 0x11434; 0x11447, 0x1144a; 0x1145f, 0x11461; 0x11480, 0x114af; 0x114c4, 0x114c5; + 0x114c7, 0x114c7; 0x11580, 0x115ae; 0x115d8, 0x115db; 0x11600, 0x1162f; 0x11644, 0x11644; + 0x11680, 0x116aa; 0x116b8, 0x116b8; 0x11700, 0x1171a; 0x11740, 0x11746; 0x11800, 0x1182b; + 0x118a0, 0x118df; 0x118ff, 0x11906; 0x11909, 0x11909; 0x1190c, 0x11913; 0x11915, 0x11916; + 0x11918, 0x1192f; 0x1193f, 0x1193f; 0x11941, 0x11941; 0x119a0, 0x119a7; 0x119aa, 0x119d0; + 0x119e1, 0x119e1; 0x119e3, 0x119e3; 0x11a00, 0x11a00; 0x11a0b, 0x11a32; 0x11a3a, 0x11a3a; + 0x11a50, 0x11a50; 0x11a5c, 0x11a89; 0x11a9d, 0x11a9d; 0x11ab0, 0x11af8; 0x11c00, 0x11c08; + 0x11c0a, 0x11c2e; 0x11c40, 0x11c40; 0x11c72, 0x11c8f; 0x11d00, 0x11d06; 0x11d08, 0x11d09; + 0x11d0b, 0x11d30; 0x11d46, 0x11d46; 0x11d60, 0x11d65; 0x11d67, 0x11d68; 0x11d6a, 0x11d89; + 0x11d98, 0x11d98; 0x11ee0, 0x11ef2; 0x11fb0, 0x11fb0; 0x12000, 0x12399; 0x12400, 0x1246e; + 0x12480, 0x12543; 0x12f90, 0x12ff0; 0x13000, 0x1342e; 0x14400, 0x14646; 0x16800, 0x16a38; + 0x16a40, 0x16a5e; 0x16a70, 0x16abe; 0x16ad0, 0x16aed; 0x16b00, 0x16b2f; 0x16b40, 0x16b43; + 0x16b63, 0x16b77; 0x16b7d, 0x16b8f; 0x16e40, 0x16e7f; 0x16f00, 0x16f4a; 0x16f50, 0x16f50; + 0x16f93, 0x16f9f; 0x16fe0, 0x16fe1; 0x16fe3, 0x16fe3; 0x17000, 0x187f7; 0x18800, 0x18cd5; + 0x18d00, 0x18d08; 0x1aff0, 0x1aff3; 0x1aff5, 0x1affb; 0x1affd, 0x1affe; 0x1b000, 0x1b122; + 0x1b150, 0x1b152; 0x1b164, 0x1b167; 0x1b170, 0x1b2fb; 0x1bc00, 0x1bc6a; 0x1bc70, 0x1bc7c; + 0x1bc80, 0x1bc88; 0x1bc90, 0x1bc99; 0x1d400, 0x1d454; 0x1d456, 0x1d49c; 0x1d49e, 0x1d49f; + 0x1d4a2, 0x1d4a2; 0x1d4a5, 0x1d4a6; 0x1d4a9, 0x1d4ac; 0x1d4ae, 0x1d4b9; 0x1d4bb, 0x1d4bb; + 0x1d4bd, 0x1d4c3; 0x1d4c5, 0x1d505; 0x1d507, 0x1d50a; 0x1d50d, 0x1d514; 0x1d516, 0x1d51c; + 0x1d51e, 0x1d539; 0x1d53b, 0x1d53e; 0x1d540, 0x1d544; 0x1d546, 0x1d546; 0x1d54a, 0x1d550; + 0x1d552, 0x1d6a5; 0x1d6a8, 0x1d6c0; 0x1d6c2, 0x1d6da; 0x1d6dc, 0x1d6fa; 0x1d6fc, 0x1d714; + 0x1d716, 0x1d734; 0x1d736, 0x1d74e; 0x1d750, 0x1d76e; 0x1d770, 0x1d788; 0x1d78a, 0x1d7a8; + 0x1d7aa, 0x1d7c2; 0x1d7c4, 0x1d7cb; 0x1df00, 0x1df09; 0x1df0a, 0x1df0a; 0x1df0b, 0x1df1e; + 0x1e100, 0x1e12c; 0x1e137, 0x1e13d; 0x1e14e, 0x1e14e; 0x1e290, 0x1e2ad; 0x1e2c0, 0x1e2eb; + 0x1e7e0, 0x1e7e6; 0x1e7e8, 0x1e7eb; 0x1e7ed, 0x1e7ee; 0x1e7f0, 0x1e7fe; 0x1e800, 0x1e8c4; + 0x1e900, 0x1e943; 0x1e94b, 0x1e94b; 0x1ee00, 0x1ee03; 0x1ee05, 0x1ee1f; 0x1ee21, 0x1ee22; + 0x1ee24, 0x1ee24; 0x1ee27, 0x1ee27; 0x1ee29, 0x1ee32; 0x1ee34, 0x1ee37; 0x1ee39, 0x1ee39; + 0x1ee3b, 0x1ee3b; 0x1ee42, 0x1ee42; 0x1ee47, 0x1ee47; 0x1ee49, 0x1ee49; 0x1ee4b, 0x1ee4b; + 0x1ee4d, 0x1ee4f; 0x1ee51, 0x1ee52; 0x1ee54, 0x1ee54; 0x1ee57, 0x1ee57; 0x1ee59, 0x1ee59; + 0x1ee5b, 0x1ee5b; 0x1ee5d, 0x1ee5d; 0x1ee5f, 0x1ee5f; 0x1ee61, 0x1ee62; 0x1ee64, 0x1ee64; + 0x1ee67, 0x1ee6a; 0x1ee6c, 0x1ee72; 0x1ee74, 0x1ee77; 0x1ee79, 0x1ee7c; 0x1ee7e, 0x1ee7e; + 0x1ee80, 0x1ee89; 0x1ee8b, 0x1ee9b; 0x1eea1, 0x1eea3; 0x1eea5, 0x1eea9; 0x1eeab, 0x1eebb; + 0x20000, 0x2a6df; 0x2a700, 0x2b738; 0x2b740, 0x2b81d; 0x2b820, 0x2cea1; 0x2ceb0, 0x2ebe0; + 0x30000, 0x3134a; 0x2f800, 0x2fa1d] + + let white_space = + [0x9, 0xd; 0x20, 0x20; 0x85, 0x85; 0xa0, 0xa0; 0x1680, 0x1680; + 0x2000, 0x200a; 0x2028, 0x2028; 0x2029, 0x2029; 0x202f, 0x202f; 0x205f, 0x205f; + 0x3000, 0x3000] + + let list = [ + ("id_start", id_start); + ("white_space", white_space) + ] + +end + + +(* Decision tree for partitions *) + +let default_loc = Location.none + +let lident_loc ~loc s = { + loc; + txt= lident s +} + +type decision_tree = + | Lte of int * decision_tree * decision_tree + | Table of int * int array + | Return of int + +let rec simplify_decision_tree ( x : decision_tree) = + match x with + | Table _ | Return _ -> x + | Lte (_, (Return a as l), Return b) when a = b -> l + | Lte (i, l, r) -> + let l = simplify_decision_tree l in + let r = simplify_decision_tree r in + match l, r with + | Return a, Return b when a = b -> l + | _ -> Lte (i, l,r) + +let decision l = + let l = List.map (fun (a, b, i) -> (a, b, Return i)) l in + let rec merge2 = function + | (a1, b1, d1) :: (a2, b2, d2) :: rest -> + let x = + if b1 + 1 = a2 then d2 + else Lte (a2 - 1, Return (-1), d2) + in + (a1, b2, Lte (b1, d1, x)) :: merge2 rest + | rest -> rest + in + let rec aux = function + | [(a, b, d)] -> Lte (a - 1, Return (-1), Lte (b, d, Return (-1))) + | [] -> Return (-1) + | l -> aux (merge2 l) + in + aux l + +let limit = 8192 + +let decision_table l = + let rec aux m accu = function + | ((a, b, i) as x)::rem when b < limit && i < 255-> + aux (min a m) (x :: accu) rem + | rem -> m, accu, rem + in + let (min, table, rest) = aux max_int [] l in + match table with + | [] -> decision l + | [(min, max, i)] -> + Lte (min - 1, Return (-1), (Lte (max, Return i, decision rest))) + | (_, max, _) :: _ -> + let arr = Array.make (max - min + 1) 0 in + let set (a, b, i) = for j = a to b do arr.(j - min) <- i + 1 done in + List.iter set table; + Lte (min - 1, Return (-1), Lte (max, Table (min, arr), decision rest)) + +let rec simplify min max = function + | Lte (i,yes,no) -> + if i >= max then simplify min max yes + else if i < min then simplify min max no + else Lte (i, simplify min i yes, simplify (i+1) max no) + | x -> x + +let segments_of_partition p = + let seg = ref [] in + Array.iteri + (fun i c -> List.iter (fun (a, b) -> seg := (a, b, i) :: !seg) c) + p; + List.sort (fun (a1,_,_) (a2,_,_) -> compare a1 a2) !seg + +let decision_table p = + simplify (-1) (Cset.max_code) (decision_table (segments_of_partition p)) + + +(* Helpers to build AST *) + +let appfun s l = + let loc = default_loc in + eapply ~loc (evar ~loc s) l + +let glb_value name def = + let loc = default_loc in + pstr_value ~loc Nonrecursive [value_binding ~loc ~pat:(pvar ~loc name) ~expr:def] + +(* Named regexps *) + +module StringMap = Map.Make(struct + type t = string + let compare = compare +end) + +let builtin_regexps = + List.fold_left (fun acc (n, c) -> StringMap.add n (Flow_sedlex.chars c) acc) + StringMap.empty + ([ + "any", Cset.any; + "eof", Cset.eof] @ + UnicodeProperties.list) + +(* Tables (indexed mapping: codepoint -> next state) *) + +let tables = Hashtbl.create 31 +let table_counter = ref 0 +let get_tables () = Hashtbl.fold (fun key x accu -> (x, key) :: accu) tables [] + +let table_name x = + try Hashtbl.find tables x + with Not_found -> + incr table_counter; + let s = Printf.sprintf "__sedlex_table_%i" !table_counter in + Hashtbl.add tables x s; + s + +let table (name, v) = + let n = Array.length v in + let s = Bytes.create n in + for i = 0 to n - 1 do Bytes.set s i (Char.chr v.(i)) done; + glb_value name (estring ~loc:default_loc (Bytes.to_string s)) + +(* Partition (function: codepoint -> next state) *) + +let partitions = Hashtbl.create 31 +let partition_counter = ref 0 +let get_partitions () = Hashtbl.fold (fun key x accu -> (x, key) :: accu) partitions [] + +let partition_name x = + try Hashtbl.find partitions x + with Not_found -> + incr partition_counter; + let s = Printf.sprintf "__sedlex_partition_%i" !partition_counter in + Hashtbl.add partitions x s; + s + +(* We duplicate the body for the EOF (-1) case rather than creating + an interior utility function. *) +let partition (name, p) = + let loc = default_loc in + let rec gen_tree = function + | Lte (i, yes, no) -> + [%expr if c <= [%e eint ~loc i] then [%e gen_tree yes] else [%e gen_tree no]] + | Return i -> eint ~loc:default_loc i + | Table (offset, t) -> + let c = if offset = 0 then [%expr c] else [%expr c - [%e eint ~loc offset]] in + [%expr Char.code (String.unsafe_get [%e evar ~loc (table_name t)] [%e c]) - 1] + in + let body = gen_tree (simplify_decision_tree (decision_table p)) in + glb_value name + [%expr fun c -> + [%e body] + ] + +(* Code generation for the automata *) + +let best_final final = + let fin = ref None in + for i = Array.length final - 1 downto 0 do + if final.(i) then fin := Some i + done; + !fin + +let state_fun state = Printf.sprintf "__sedlex_state_%i" state + +let call_state lexbuf auto state = + let (trans, final) = auto.(state) in + if Array.length trans = 0 + then match best_final final with + | Some i -> eint ~loc:default_loc i + | None -> assert false + else appfun (state_fun state) [evar ~loc:default_loc lexbuf] + +let gen_state lexbuf auto i (trans, final) = + let loc = default_loc in + let partition = Array.map fst trans in + let cases = Array.mapi (fun i (_, j) -> case ~lhs:(pint ~loc i) ~guard:None ~rhs:(call_state lexbuf auto j)) trans in + let cases = Array.to_list cases in + let body () = + pexp_match ~loc + (appfun (partition_name partition) [[%expr Sedlexing.__private__next_int [%e evar ~loc lexbuf]]]) + (cases @ [case ~lhs:[%pat? _] ~guard:None ~rhs:[%expr Sedlexing.backtrack [%e evar ~loc lexbuf]]]) + in + let ret body = + let lhs = pvar ~loc lexbuf in + [ + value_binding ~loc + ~pat:(pvar ~loc (state_fun i)) + ~expr:(Exp.fun_ ~loc Nolabel None lhs body); + ] + in + match best_final final with + | None -> ret (body ()) + | Some _ when Array.length trans = 0 -> [] + | Some i -> ret [%expr Sedlexing.mark [%e evar ~loc lexbuf] [%e eint ~loc i]; [%e body ()]] + +let gen_recflag auto = + (* The generated function is not recursive if the transitions end + in states with no further transitions. *) + try + Array.iter + (fun (trans_i, _) -> + Array.iter + (fun (_, j) -> + let (trans_j, _) = auto.(j) in + if Array.length trans_j > 0 then raise Exit) + trans_i) + auto; + Nonrecursive + with + Exit -> Recursive + +let gen_definition lexbuf l error = + let loc = default_loc in + let brs = Array.of_list l in + let auto = Flow_sedlex.compile (Array.map fst brs) in + let cases = Array.to_list (Array.mapi (fun i (_, e) -> case ~lhs:(pint ~loc i) ~guard:None ~rhs:e) brs) in + let states = Array.mapi (gen_state lexbuf auto) auto in + let states = List.flatten (Array.to_list states) in + pexp_let ~loc (gen_recflag auto) states + (pexp_sequence ~loc + [%expr Sedlexing.start [%e evar ~loc lexbuf]] + (pexp_match ~loc (appfun (state_fun 0) [evar ~loc lexbuf]) + (cases @ [case ~lhs:(ppat_any ~loc) ~guard:None ~rhs:error]) + ) + ) + +(* Lexer specification parser *) + +let codepoint i = + if i < 0 || i > Cset.max_code then + failwith (Printf.sprintf "Invalid Unicode code point: %i" i); + i + +let regexp_for_char c = + Flow_sedlex.chars (Cset.singleton (Char.code c)) + +let regexp_for_string s = + let rec aux n = + if n = String.length s then Flow_sedlex.eps + else + Flow_sedlex.seq (regexp_for_char s.[n]) (aux (succ n)) + in aux 0 + +let err loc s = + raise (Location.Error (Location.Error.createf ~loc "Sedlex: %s" s)) + +let rec repeat r = function + | 0, 0 -> Flow_sedlex.eps + | 0, m -> Flow_sedlex.alt Flow_sedlex.eps (Flow_sedlex.seq r (repeat r (0, m - 1))) + | n, m -> Flow_sedlex.seq r (repeat r (n - 1, m - 1)) + +let regexp_of_pattern env = + let rec char_pair_op func name p tuple = + (* Construct something like Sub(a,b) *) + match tuple with + | Some { ppat_desc = Ppat_tuple [p0; p1] } -> begin + match func (aux p0) (aux p1) with + | Some r -> r + | None -> + err p.ppat_loc @@ "the " ^ name + ^ " operator can only applied to single-character length \ + regexps" + end + | _ -> + err p.ppat_loc @@ "the " ^ name + ^ " operator requires two arguments, like " ^ name ^ "(a,b)" + and aux p = + (* interpret one pattern node *) + match p.ppat_desc with + | Ppat_or (p1, p2) -> Flow_sedlex.alt (aux p1) (aux p2) + | Ppat_tuple (p :: pl) -> + List.fold_left (fun r p -> Flow_sedlex.seq r (aux p)) (aux p) pl + | Ppat_construct ({ txt = Lident "Star" }, Some (_, p)) -> + Flow_sedlex.rep (aux p) + | Ppat_construct ({ txt = Lident "Plus" }, Some (_, p)) -> + Flow_sedlex.plus (aux p) + | Ppat_construct + ( { txt = Lident "Rep" }, + Some + ( _, + { + ppat_desc = + Ppat_tuple + [ + p0; + { + ppat_desc = + Ppat_constant (i1 as i2) | Ppat_interval (i1, i2); + }; + ]; + } ) ) -> begin + match (i1, i2) with + | Pconst_integer (i1, _), Pconst_integer (i2, _) -> + let i1 = int_of_string i1 in + let i2 = int_of_string i2 in + if 0 <= i1 && i1 <= i2 then repeat (aux p0) (i1, i2) + else err p.ppat_loc "Invalid range for Rep operator" + | _ -> + err p.ppat_loc "Rep must take an integer constant or interval" + end + | Ppat_construct ({ txt = Lident "Rep" }, _) -> + err p.ppat_loc "the Rep operator takes 2 arguments" + | Ppat_construct ({ txt = Lident "Opt" }, Some (_, p)) -> + Flow_sedlex.alt Flow_sedlex.eps (aux p) + | Ppat_construct ({ txt = Lident "Compl" }, arg) -> begin + match arg with + | Some (_, p0) -> begin + match Flow_sedlex.compl (aux p0) with + | Some r -> r + | None -> + err p.ppat_loc + "the Compl operator can only applied to a \ + single-character length regexp" + end + | _ -> err p.ppat_loc "the Compl operator requires an argument" + end + | Ppat_construct ({ txt = Lident "Sub" }, arg) -> + char_pair_op Flow_sedlex.subtract "Sub" p + (Option.map (fun (_, arg) -> arg) arg) + | Ppat_construct ({ txt = Lident "Intersect" }, arg) -> + char_pair_op Flow_sedlex.intersection "Intersect" p + (Option.map (fun (_, arg) -> arg) arg) + | Ppat_construct ({ txt = Lident "Chars" }, arg) -> ( + let const = + match arg with + | Some (_, { ppat_desc = Ppat_constant const }) -> Some const + | _ -> None + in + match const with + | Some (Pconst_string (s, _, _)) -> + let c = ref Cset.empty in + for i = 0 to String.length s - 1 do + c := Cset.union !c (Cset.singleton (Char.code s.[i])) + done; + Flow_sedlex.chars !c + | _ -> + err p.ppat_loc "the Chars operator requires a string argument") + | Ppat_interval (i_start, i_end) -> begin + match (i_start, i_end) with + | Pconst_char c1, Pconst_char c2 -> + Flow_sedlex.chars (Cset.interval (Char.code c1) (Char.code c2)) + | Pconst_integer (i1, _), Pconst_integer (i2, _) -> + Flow_sedlex.chars + (Cset.interval + (codepoint (int_of_string i1)) + (codepoint (int_of_string i2))) + | _ -> err p.ppat_loc "this pattern is not a valid interval regexp" + end + | Ppat_constant const -> begin + match const with + | Pconst_string (s, _, _) -> regexp_for_string s + | Pconst_char c -> regexp_for_char c + | Pconst_integer (i, _) -> + Flow_sedlex.chars (Cset.singleton (codepoint (int_of_string i))) + | _ -> err p.ppat_loc "this pattern is not a valid regexp" + end + | Ppat_var { txt = x } -> begin + try StringMap.find x env + with Not_found -> + err p.ppat_loc (Printf.sprintf "unbound regexp %s" x) + end + | _ -> err p.ppat_loc "this pattern is not a valid regexp" + in + aux + + +let previous = ref [] +let regexps = ref [] +let should_set_cookies = ref false + +let mapper = + object(this) + inherit Ast_traverse.map as super + + val env = builtin_regexps + + method define_regexp name p = + {< env = StringMap.add name (regexp_of_pattern env p) env >} + + method! expression e = + match e with + | [%expr [%sedlex [%e? {pexp_desc=Pexp_match (lexbuf, cases)}]]] -> + let lexbuf = + match lexbuf with + | {pexp_desc=Pexp_ident{txt=Lident lexbuf}} -> lexbuf + | _ -> + err lexbuf.pexp_loc "the matched expression must be a single identifier" + in + let cases = List.rev cases in + let error = + match List.hd cases with + | {pc_lhs = [%pat? _]; pc_rhs = e; pc_guard = None} -> super # expression e + | {pc_lhs = p} -> + err p.ppat_loc "the last branch must be a catch-all error case" + in + let cases = List.rev (List.tl cases) in + let cases = + List.map + (function + | {pc_lhs = p; pc_rhs = e; pc_guard = None} -> regexp_of_pattern env p, super # expression e + | {pc_guard = Some e} -> + err e.pexp_loc "'when' guards are not supported" + ) cases + in + gen_definition lexbuf cases error + | [%expr let [%p? {ppat_desc=Ppat_var{txt=name}}] = [%sedlex.regexp? [%p? p]] in [%e? body]] -> + (this # define_regexp name p) # expression body + | [%expr [%sedlex [%e? _]]] -> + err e.pexp_loc "the %sedlex extension is only recognized on match expressions" + | _ -> super # expression e + + + val toplevel = true + + method structure_with_regexps l = + let mapper = ref this in + let regexps = ref [] in + let l = List.concat + (List.map + (function + | [%stri let [%p? {ppat_desc=Ppat_var{txt=name}}] = [%sedlex.regexp? [%p? p]]] as i -> + regexps := i :: !regexps; + mapper := !mapper # define_regexp name p; + [] + | i -> + [ !mapper # structure_item i ] + ) l) in + (l, List.rev !regexps) + + method! structure l = + if toplevel then + let sub = {< toplevel = false >} in + let l, regexps' = sub # structure_with_regexps (!previous @ l) in + let parts = List.map partition (get_partitions ()) in + let tables = List.map table (get_tables ()) in + regexps := regexps'; + should_set_cookies := true; + tables @ parts @ l + else + fst (this # structure_with_regexps l) + + end + +let pre_handler cookies = + previous := + match Driver.Cookies.get cookies "sedlex.regexps" Ast_pattern.__ with + | Some {pexp_desc = Pexp_extension (_, PStr l)} -> l + | Some _ -> assert false + | None -> [] + +let post_handler cookies = + if !should_set_cookies then + let loc = default_loc in + Driver.Cookies.set cookies "sedlex.regexps" (pexp_extension ~loc ( {loc; txt="regexps"}, PStr !regexps)) + + +let extensions = + [Extension.declare + "sedlex" + Extension.Context.expression + Ast_pattern.(single_expr_payload __) + (fun ~loc:_ ~path:_ expr -> mapper # expression expr); + ] + +let () = + Driver.Cookies.add_handler pre_handler; + Driver.Cookies.add_post_handler post_handler; + Driver.register_transformation "sedlex" ~impl:(mapper # structure) diff --git a/compiler/flow_parser/flow_sedlexing_ppx/sedlex_cset.ml b/compiler/flow_parser/flow_sedlexing_ppx/sedlex_cset.ml new file mode 100644 index 00000000000..1a3df593f7d --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing_ppx/sedlex_cset.ml @@ -0,0 +1,45 @@ +(* The package sedlex is released under the terms of an MIT-like license. *) +(* See the attached LICENSE file. *) +(* Copyright 2005, 2013 by Alain Frisch and LexiFi. *) + +(* Character sets are represented as lists of intervals. The + intervals must be non-overlapping and not collapsable, and the list + must be ordered in increasing order. *) + +type t = (int * int) list + +let max_code = 0x10ffff (* must be < max_int *) +let min_code = -1 + +let empty = [] +let singleton i = [i,i] +let is_empty = function [] -> true | _ -> false +let interval i j = if i <= j then [i,j] else [j,i] +let eof = singleton (-1) +let any = interval 0 max_code + +let rec union c1 c2 = + match c1,c2 with + | [], _ -> c2 + | _, [] -> c1 + | ((i1, j1) as s1)::r1, (i2, j2)::r2 -> + if (i1 <= i2) then + if j1 + 1 < i2 then s1::(union r1 c2) + else if (j1 < j2) then union r1 ((i1, j2)::r2) + else union c1 r2 + else union c2 c1 + +let complement c = + let rec aux start = function + | [] -> if start <= max_code then [start,max_code] else [] + | (i, j)::l -> (start, i-1)::(aux (succ j) l) + in + match c with + | (-1,j)::l -> aux (succ j) l + | l -> aux (-1) l + +let intersection c1 c2 = + complement (union (complement c1) (complement c2)) + +let difference c1 c2 = + complement (union (complement c1) c2) diff --git a/compiler/flow_parser/flow_sedlexing_ppx/sedlex_cset.mli b/compiler/flow_parser/flow_sedlexing_ppx/sedlex_cset.mli new file mode 100644 index 00000000000..6a020289f48 --- /dev/null +++ b/compiler/flow_parser/flow_sedlexing_ppx/sedlex_cset.mli @@ -0,0 +1,20 @@ +(* The package sedlex is released under the terms of an MIT-like license. *) +(* See the attached LICENSE file. *) +(* Copyright 2005, 2013 by Alain Frisch and LexiFi. *) + +(** Representation of sets of unicode code points. *) + +type t = (int * int) list + +val min_code: int +val max_code: int + +val empty: t +val any: t +val union: t -> t -> t +val difference: t -> t -> t +val intersection: t -> t -> t +val is_empty: t -> bool +val eof: t +val singleton: int -> t +val interval: int -> int -> t diff --git a/compiler/flow_parser/parser/comment_attachment.ml b/compiler/flow_parser/parser/comment_attachment.ml new file mode 100644 index 00000000000..bf1f1e8a440 --- /dev/null +++ b/compiler/flow_parser/parser/comment_attachment.ml @@ -0,0 +1,856 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Ast = Flow_ast +open Flow_ast +open Parser_env + +let id = Flow_ast_mapper.id + +let map_loc = Flow_ast_mapper.map_loc + +let map_opt = Flow_ast_mapper.map_opt + +let id_list_last (map : 'a -> 'a) (lst : 'a list) : 'a list = + match List.rev lst with + | [] -> lst + | hd :: tl -> + let hd' = map hd in + if hd == hd' then + lst + else + List.rev (hd' :: tl) + +(* Mapper that removes all trailing comments that appear after a given position in an AST node *) +class ['loc] trailing_comments_remover ~after_pos = + object (this) + inherit ['loc] Flow_ast_mapper.mapper + + method! syntax comments = + let open Syntax in + let { trailing; _ } = comments in + let trailing' = + List.filter (fun (loc, _) -> Loc.(pos_cmp loc.start after_pos < 0)) trailing + in + if List.length trailing = List.length trailing' then + comments + else + { comments with trailing = trailing' } + + method! array _loc expr = + let open Ast.Expression.Array in + let { comments; _ } = expr in + id this#syntax_opt comments expr (fun comments' -> { expr with comments = comments' }) + + method! array_type t = + let open Ast.Type.Array in + let { comments; _ } = t in + id this#syntax_opt comments t (fun comments' -> { t with comments = comments' }) + + method! assignment _loc expr = + let open Ast.Expression.Assignment in + let { right; comments; _ } = expr in + let right' = this#expression right in + let comments' = this#syntax_opt comments in + if right == right' && comments == comments' then + expr + else + { expr with right = right'; comments = comments' } + + method! binary _loc expr = + let open Ast.Expression.Binary in + let { right; comments; _ } = expr in + let right' = this#expression right in + let comments' = this#syntax_opt comments in + if right == right' && comments == comments' then + expr + else + { expr with right = right'; comments = comments' } + + method! block _loc stmt = + let open Ast.Statement.Block in + let { comments; _ } = stmt in + id this#syntax_opt comments stmt (fun comments' -> { stmt with comments = comments' }) + + method! call _annot expr = + let open Ast.Expression.Call in + let { arguments; comments; _ } = expr in + let arguments' = this#arg_list arguments in + let comments' = this#syntax_opt comments in + if arguments == arguments' && comments == comments' then + expr + else + { expr with arguments = arguments'; comments = comments' } + + method! arg_list arg_list = + let open Ast.Expression.ArgList in + let (loc, { arguments; comments }) = arg_list in + id this#syntax_opt comments arg_list (fun comments' -> + (loc, { arguments; comments = comments' }) + ) + + method! call_type_args targs = + let open Ast.Expression.CallTypeArgs in + let (loc, { arguments; comments }) = targs in + id this#syntax_opt comments targs (fun comments' -> (loc, { arguments; comments = comments' })) + + method! class_ _loc cls = + let open Ast.Class in + let { body; comments; _ } = cls in + let body' = this#class_body body in + let comments' = this#syntax_opt comments in + if body == body' && comments == comments' then + cls + else + { cls with body = body'; comments = comments' } + + method! class_body body = + let open Ast.Class.Body in + let (loc, { body = _body; comments }) = body in + id this#syntax_opt comments body (fun comments' -> + (loc, { body = _body; comments = comments' }) + ) + + method! class_extends _loc extends = + let open Ast.Class.Extends in + let { expr; targs; _ } = extends in + if targs = None then + id this#expression expr extends (fun expr' -> { extends with expr = expr' }) + else + id (map_opt this#type_args) targs extends (fun targs' -> { extends with targs = targs' }) + + method! class_implements implements = + let open Ast.Class.Implements in + let (loc, { interfaces; comments }) = implements in + id (id_list_last this#class_implements_interface) interfaces implements (fun interfaces' -> + (loc, { interfaces = interfaces'; comments }) + ) + + method! class_implements_interface interface = + let open Ast.Class.Implements.Interface in + let (loc, { id = id_; targs }) = interface in + if targs = None then + id this#identifier id_ interface (fun id' -> (loc, { id = id'; targs })) + else + id (map_opt this#type_args) targs interface (fun targs' -> + (loc, { id = id_; targs = targs' }) + ) + + method! component_declaration _loc component = + let open Ast.Statement.ComponentDeclaration in + let { body; comments; _ } = component in + let body' = this#component_body body in + let comments' = this#syntax_opt comments in + if body == body' && comments == comments' then + component + else + { component with body = body'; comments = comments' } + + method! component_params (loc, params) = + let open Ast.Statement.ComponentDeclaration.Params in + let { comments; _ } = params in + id this#syntax_opt comments (loc, params) (fun comments' -> + (loc, { params with comments = comments' }) + ) + + method! computed_key key = + let open Ast.ComputedKey in + let (loc, { expression; comments }) = key in + id this#syntax_opt comments key (fun comments' -> (loc, { expression; comments = comments' })) + + method! conditional _loc expr = + let open Ast.Expression.Conditional in + let { alternate; comments; _ } = expr in + let alternate' = this#expression alternate in + let comments' = this#syntax_opt comments in + if alternate == alternate' && comments == comments' then + expr + else + { expr with alternate = alternate'; comments = comments' } + + method! function_ _loc func = + let open Ast.Function in + let { body; comments; _ } = func in + let body' = this#function_body_any body in + let comments' = this#syntax_opt comments in + if body == body' && comments == comments' then + func + else + { func with body = body'; comments = comments' } + + method! function_params (loc, params) = + let open Ast.Function.Params in + let { comments; _ } = params in + id this#syntax_opt comments (loc, params) (fun comments' -> + (loc, { params with comments = comments' }) + ) + + method! function_type _loc func = + let open Ast.Type.Function in + let { return; comments; _ } = func in + let return' = this#function_type_return_annotation return in + let comments' = this#syntax_opt comments in + if return == return' && comments == comments' then + func + else + { func with return = return'; comments = comments' } + + method! generic_identifier_type git = + let open Ast.Type.Generic.Identifier in + match git with + | Unqualified i -> id this#identifier i git (fun i -> Unqualified i) + | Qualified (loc, ({ id; _ } as qualified)) -> + let id' = this#identifier id in + if id == id' then + git + else + Qualified (loc, { qualified with id = id' }) + + method! import _loc expr = + let open Ast.Expression.Import in + let { comments; _ } = expr in + id this#syntax_opt comments expr (fun comments' -> { expr with comments = comments' }) + + method! interface_type _loc t = + let open Ast.Type.Interface in + let { body; comments; _ } = t in + let body' = map_loc this#object_type body in + let comments' = this#syntax_opt comments in + if body == body' && comments == comments' then + t + else + { t with body = body'; comments = comments' } + + method! intersection_type _loc t = + let { Ast.Type.Intersection.types = (t0, t1, ts); comments } = t in + let (t1', ts') = + match ts with + | [] -> (this#type_ t1, []) + | _ -> (t1, id_list_last this#type_ ts) + in + let comments' = this#syntax_opt comments in + if t1 == t1' && ts == ts' && comments == comments' then + t + else + { Ast.Type.Intersection.types = (t0, t1', ts'); comments = comments' } + + method! jsx_element _loc elem = + let open Ast.JSX in + let { comments; _ } = elem in + id this#syntax_opt comments elem (fun comments' -> { elem with comments = comments' }) + + method! jsx_fragment _loc frag = + let open Ast.JSX in + let { frag_comments = comments; _ } = frag in + id this#syntax_opt comments frag (fun comments' -> { frag with frag_comments = comments' }) + + method! logical _loc expr = + let open Ast.Expression.Logical in + let { right; comments; _ } = expr in + let right' = this#expression right in + let comments' = this#syntax_opt comments in + if right == right' && comments == comments' then + expr + else + { expr with right = right'; comments = comments' } + + method! new_ _loc expr = + let open Ast.Expression.New in + let { callee; targs; arguments; comments } = expr in + let comments' = this#syntax_opt comments in + match (targs, arguments) with + (* new Callee() *) + | (_, Some _) -> + let arguments' = map_opt this#arg_list arguments in + if arguments == arguments' && comments == comments' then + expr + else + { expr with arguments = arguments'; comments = comments' } + (* new Callee *) + | (Some _, _) -> + let targs' = map_opt this#call_type_args targs in + if targs == targs' && comments == comments' then + expr + else + { expr with targs = targs'; comments = comments' } + (* new Callee *) + | (None, None) -> + let callee' = this#expression callee in + if callee == callee' && comments == comments' then + expr + else + { expr with callee = callee'; comments = comments' } + + method! member _loc expr = + let open Ast.Expression.Member in + let { property; comments; _ } = expr in + let property' = this#member_property property in + let comments' = this#syntax_opt comments in + if property == property' && comments == comments' then + expr + else + { expr with property = property'; comments = comments' } + + method! object_ _loc expr = + let open Ast.Expression.Object in + let { comments; _ } = expr in + id this#syntax_opt comments expr (fun comments' -> { expr with comments = comments' }) + + method! object_type _loc obj = + let open Ast.Type.Object in + let { comments; _ } = obj in + id this#syntax_opt comments obj (fun comments' -> { obj with comments = comments' }) + + method! predicate pred = + let open Ast.Type.Predicate in + let (loc, { kind; comments }) = pred in + id this#syntax_opt comments pred (fun comments' -> (loc, { kind; comments = comments' })) + + method! sequence _loc expr = + let open Ast.Expression.Sequence in + let { expressions; comments } = expr in + let expressions' = id_list_last this#expression expressions in + let comments' = this#syntax_opt comments in + if expressions == expressions' && comments == comments' then + expr + else + { expressions = expressions'; comments = comments' } + + method! template_literal _loc expr = + let open Ast.Expression.TemplateLiteral in + let { comments; _ } = expr in + id this#syntax_opt comments expr (fun comments' -> { expr with comments = comments' }) + + method! tuple_type t = + let open Ast.Type.Tuple in + let { comments; _ } = t in + id this#syntax_opt comments t (fun comments' -> { t with comments = comments' }) + + method! type_cast _loc expr = + let open Ast.Expression.TypeCast in + let { comments; _ } = expr in + id this#syntax_opt comments expr (fun comments' -> { expr with comments = comments' }) + + method! type_params ~kind:_ tparams = + let open Ast.Type.TypeParams in + let (loc, { params; comments }) = tparams in + id this#syntax_opt comments tparams (fun comments' -> (loc, { params; comments = comments' })) + + method! union_type _loc t = + let { Ast.Type.Union.types = (t0, t1, ts); comments } = t in + let (t1', ts') = + match ts with + | [] -> (this#type_ t1, []) + | _ -> (t1, id_list_last this#type_ ts) + in + let comments' = this#syntax_opt comments in + if t1 == t1' && ts == ts' && comments == comments' then + t + else + { Ast.Type.Union.types = (t0, t1', ts'); comments = comments' } + + method! variable_declarator ~kind decl = + let open Ast.Statement.VariableDeclaration.Declarator in + let (loc, { id = ident; init }) = decl in + match init with + | None -> + id (this#variable_declarator_pattern ~kind) ident decl (fun ident' -> + (loc, { id = ident'; init }) + ) + | Some init -> + id this#expression init decl (fun init' -> (loc, { id = ident; init = Some init' })) + end + +type trailing_and_remover_result = { + trailing: Loc.t Comment.t list; + remove_trailing: 'a. 'a -> (Loc.t trailing_comments_remover -> 'a -> 'a) -> 'a; +} + +(* Returns a remover function which removes comments beginning after the previous token. + No trailing comments are returned, since all comments since the last loc should be removed. *) +let trailing_and_remover_after_last_loc : Parser_env.env -> trailing_and_remover_result = + fun env -> + let open Loc in + let remover = + match Parser_env.last_loc env with + | None -> None + | Some _ when not (Peek.has_eaten_comments env) -> None + | Some last_loc -> + Parser_env.consume_comments_until env last_loc._end; + let remover = new trailing_comments_remover ~after_pos:last_loc._end in + Some remover + in + { + trailing = []; + remove_trailing = + (fun node f -> + match remover with + | None -> node + | Some remover -> f remover node); + } + +(* Consumes and returns comments on the same line as the previous token. Also returns a remover + function which can be used to remove comments beginning after the previous token's line. *) +let trailing_and_remover_after_last_line : Parser_env.env -> trailing_and_remover_result = + fun env -> + let open Loc in + let (trailing, remover) = + match Parser_env.last_loc env with + | None -> ([], None) + | Some _ when not (Peek.has_eaten_comments env) -> (Eat.comments_until_next_line env, None) + | Some last_loc -> + Parser_env.consume_comments_until env last_loc._end; + let trailing = Eat.comments_until_next_line env in + let next_line_start = { line = last_loc._end.line + 1; column = 0 } in + let remover = new trailing_comments_remover ~after_pos:next_line_start in + (trailing, Some remover) + in + { + trailing; + remove_trailing = + (fun node f -> + match remover with + | None -> node + | Some remover -> f remover node); + } + +let trailing_and_remover : Parser_env.env -> trailing_and_remover_result = + fun env -> + if Peek.is_line_terminator env then + trailing_and_remover_after_last_line env + else + trailing_and_remover_after_last_loc env + +let id_remove_trailing env id = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing id (fun remover id -> remover#identifier id) + +let expression_remove_trailing env expr = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing expr (fun remover expr -> remover#expression expr) + +let block_remove_trailing env block = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing block (fun remover (loc, str) -> (loc, remover#block loc str)) + +let type_params_remove_trailing env ~kind tparams = + match tparams with + | None -> None + | Some tparams -> + let { remove_trailing; _ } = trailing_and_remover env in + Some (remove_trailing tparams (fun remover tparams -> remover#type_params ~kind tparams)) + +let type_remove_trailing env ty = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing ty (fun remover ty -> remover#type_ ty) + +let type_annotation_hint_remove_trailing env annot = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing annot (fun remover annot -> remover#type_annotation_hint annot) + +let component_renders_annotation_remove_trailing env annot = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing annot (fun remover annot -> remover#component_renders_annotation annot) + +let return_annotation_remove_trailing env annot = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing annot (fun remover annot -> remover#function_return_annotation annot) + +let function_params_remove_trailing env params = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing params (fun remover params -> remover#function_params params) + +let component_params_remove_trailing env params = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing params (fun remover params -> remover#component_params params) + +let component_type_params_remove_trailing env params = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing params (fun remover params -> remover#component_type_params params) + +let predicate_remove_trailing env pred = + match pred with + | None -> None + | Some pred -> + let { remove_trailing; _ } = trailing_and_remover env in + Some (remove_trailing pred (fun remover pred -> remover#predicate pred)) + +let object_key_remove_trailing env key = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing key (fun remover key -> remover#object_key key) + +let generic_type_remove_trailing env ty = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing ty (fun remover ty -> map_loc remover#generic_type ty) + +let generic_type_list_remove_trailing env extends = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing extends (fun remover extends -> + id_list_last (map_loc remover#generic_type) extends + ) + +let class_implements_remove_trailing env implements = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing implements (fun remover impl -> remover#class_implements impl) + +let string_literal_remove_trailing env str = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing str (fun remover (loc, str) -> (loc, remover#string_literal loc str)) + +let statement_add_comments + ((loc, stmt) : (Loc.t, Loc.t) Statement.t) (comments : (Loc.t, unit) Syntax.t option) : + (Loc.t, Loc.t) Statement.t = + let open Statement in + let merge_comments inner = Flow_ast_utils.merge_comments ~inner ~outer:comments in + let merge_comments_with_internal inner = + Flow_ast_utils.merge_comments_with_internal ~inner ~outer:comments + in + ( loc, + match stmt with + | Block ({ Block.comments; _ } as s) -> + Block { s with Block.comments = merge_comments_with_internal comments } + | Break ({ Break.comments; _ } as s) -> + Break { s with Break.comments = merge_comments comments } + | ClassDeclaration ({ Class.comments; _ } as s) -> + ClassDeclaration { s with Class.comments = merge_comments comments } + | ComponentDeclaration ({ ComponentDeclaration.comments; _ } as s) -> + ComponentDeclaration { s with ComponentDeclaration.comments = merge_comments comments } + | Continue ({ Continue.comments; _ } as s) -> + Continue { s with Continue.comments = merge_comments comments } + | Debugger { Debugger.comments } -> Debugger { Debugger.comments = merge_comments comments } + | DeclareClass ({ DeclareClass.comments; _ } as s) -> + DeclareClass { s with DeclareClass.comments = merge_comments comments } + | DeclareComponent ({ DeclareComponent.comments; _ } as s) -> + DeclareComponent { s with DeclareComponent.comments = merge_comments comments } + | DeclareEnum ({ EnumDeclaration.comments; _ } as s) -> + DeclareEnum { s with EnumDeclaration.comments = merge_comments comments } + | DeclareExportDeclaration ({ DeclareExportDeclaration.comments; _ } as s) -> + DeclareExportDeclaration + { s with DeclareExportDeclaration.comments = merge_comments comments } + | DeclareFunction ({ DeclareFunction.comments; _ } as s) -> + DeclareFunction { s with DeclareFunction.comments = merge_comments comments } + | DeclareInterface ({ Interface.comments; _ } as s) -> + DeclareInterface { s with Interface.comments = merge_comments comments } + | DeclareModule ({ DeclareModule.comments; _ } as s) -> + DeclareModule { s with DeclareModule.comments = merge_comments comments } + | DeclareModuleExports ({ DeclareModuleExports.comments; _ } as s) -> + DeclareModuleExports { s with DeclareModuleExports.comments = merge_comments comments } + | DeclareNamespace ({ DeclareNamespace.comments; _ } as s) -> + DeclareNamespace { s with DeclareNamespace.comments = merge_comments comments } + | DeclareTypeAlias ({ TypeAlias.comments; _ } as s) -> + DeclareTypeAlias { s with TypeAlias.comments = merge_comments comments } + | DeclareOpaqueType ({ OpaqueType.comments; _ } as s) -> + DeclareOpaqueType { s with OpaqueType.comments = merge_comments comments } + | DeclareVariable ({ DeclareVariable.comments; _ } as s) -> + DeclareVariable { s with DeclareVariable.comments = merge_comments comments } + | DoWhile ({ DoWhile.comments; _ } as s) -> + DoWhile { s with DoWhile.comments = merge_comments comments } + | Empty { Empty.comments } -> Empty { Empty.comments = merge_comments comments } + | EnumDeclaration ({ EnumDeclaration.comments; _ } as s) -> + EnumDeclaration { s with EnumDeclaration.comments = merge_comments comments } + | ExportDefaultDeclaration ({ ExportDefaultDeclaration.comments; _ } as s) -> + ExportDefaultDeclaration + { s with ExportDefaultDeclaration.comments = merge_comments comments } + | ExportNamedDeclaration ({ ExportNamedDeclaration.comments; _ } as s) -> + ExportNamedDeclaration { s with ExportNamedDeclaration.comments = merge_comments comments } + | Expression ({ Expression.comments; _ } as s) -> + Expression { s with Expression.comments = merge_comments comments } + | For ({ For.comments; _ } as s) -> For { s with For.comments = merge_comments comments } + | ForIn ({ ForIn.comments; _ } as s) -> + ForIn { s with ForIn.comments = merge_comments comments } + | ForOf ({ ForOf.comments; _ } as s) -> + ForOf { s with ForOf.comments = merge_comments comments } + | FunctionDeclaration ({ Function.comments; _ } as s) -> + FunctionDeclaration { s with Function.comments = merge_comments comments } + | If ({ If.comments; _ } as s) -> If { s with If.comments = merge_comments comments } + | ImportDeclaration ({ ImportDeclaration.comments; _ } as s) -> + ImportDeclaration { s with ImportDeclaration.comments = merge_comments comments } + | InterfaceDeclaration ({ Interface.comments; _ } as s) -> + InterfaceDeclaration { s with Interface.comments = merge_comments comments } + | Labeled ({ Labeled.comments; _ } as s) -> + Labeled { s with Labeled.comments = merge_comments comments } + | Match ({ Match.comments; _ } as s) -> + Match { s with Match.comments = merge_comments comments } + | Return ({ Return.comments; _ } as s) -> + Return { s with Return.comments = merge_comments comments } + | Switch ({ Switch.comments; _ } as s) -> + Switch { s with Switch.comments = merge_comments comments } + | Throw ({ Throw.comments; _ } as s) -> + Throw { s with Throw.comments = merge_comments comments } + | Try ({ Try.comments; _ } as s) -> Try { s with Try.comments = merge_comments comments } + | TypeAlias ({ TypeAlias.comments; _ } as s) -> + TypeAlias { s with TypeAlias.comments = merge_comments comments } + | OpaqueType ({ OpaqueType.comments; _ } as s) -> + OpaqueType { s with OpaqueType.comments = merge_comments comments } + | VariableDeclaration ({ VariableDeclaration.comments; _ } as s) -> + VariableDeclaration { s with VariableDeclaration.comments = merge_comments comments } + | While ({ While.comments; _ } as s) -> + While { s with While.comments = merge_comments comments } + | With ({ With.comments; _ } as s) -> With { s with With.comments = merge_comments comments } + ) + +(* Collects the first leading and last trailing comment on an AST node or its children. + The first leading comment is the first attached comment that begins before the given node's loc, + and the last trailing comment is the last attached comment that begins after the given node's loc. *) +class ['loc] comment_bounds_collector ~loc = + object (this) + inherit ['loc] Flow_ast_mapper.mapper + + val mutable first_leading = None + + val mutable last_trailing = None + + method comment_bounds = (first_leading, last_trailing) + + method collect_comments : 'internal. ('loc, 'internal) Syntax.t -> unit = + function + | { Syntax.leading; trailing; _ } -> + List.iter this#visit_leading_comment leading; + List.iter this#visit_trailing_comment trailing + + method collect_comments_opt = + function + | None -> () + | Some comments -> this#collect_comments comments + + method visit_leading_comment ((comment_loc, _) as comment) = + let open Loc in + match first_leading with + | None -> if pos_cmp comment_loc.start loc.start < 0 then first_leading <- Some comment + | Some (current_first_loc, _) -> + if pos_cmp comment_loc.start current_first_loc.start < 0 then first_leading <- Some comment + + method visit_trailing_comment ((comment_loc, _) as comment) = + let open Loc in + match last_trailing with + | None -> if pos_cmp comment_loc.start loc._end >= 0 then last_trailing <- Some comment + | Some (current_last_loc, _) -> + if pos_cmp current_last_loc.start comment_loc.start < 0 then last_trailing <- Some comment + + method! syntax comments = + this#collect_comments comments; + comments + + method! block _loc block = + let { Statement.Block.comments; _ } = block in + this#collect_comments_opt comments; + block + end + +(* Given an AST node and a function to collect all its comments, return the first leading + and last trailing comment on the node. *) +let comment_bounds loc node f = + let collector = new comment_bounds_collector ~loc in + ignore (f collector node); + collector#comment_bounds + +(* Expand node's loc to include its attached comments *) +let expand_loc_with_comment_bounds loc (first_leading, last_trailing) = + let open Loc in + let start = + match first_leading with + | None -> loc + | Some (first_leading_loc, _) -> first_leading_loc + in + let _end = + match last_trailing with + | None -> loc + | Some (last_trailing_loc, _) -> last_trailing_loc + in + btwn start _end + +(* Remove the trailing comment bound if it is a line comment *) +let comment_bounds_without_trailing_line_comment (leading, trailing) = + match trailing with + | Some (_, { Ast.Comment.kind = Ast.Comment.Line; _ }) -> (leading, None) + | _ -> (leading, trailing) + +let collect_without_trailing_line_comment collector = + comment_bounds_without_trailing_line_comment collector#comment_bounds + +(* Return the first leading and last trailing comment of a statement *) +let statement_comment_bounds ((loc, _) as stmt : (Loc.t, Loc.t) Statement.t) : + Loc.t Comment.t option * Loc.t Comment.t option = + let collector = new comment_bounds_collector ~loc in + ignore (collector#statement stmt); + collector#comment_bounds + +let expression_comment_bounds ((loc, _) as expr) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#expression expr); + collector#comment_bounds + +let type_comment_bounds ((loc, _) as ty) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#type_ ty); + collector#comment_bounds + +let block_comment_bounds (loc, block) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#block loc block); + collector#comment_bounds + +let object_property_comment_bounds property = + let open Ast.Expression.Object in + let collector = + match property with + | Property ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#object_property p); + collector + | SpreadProperty ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#spread_property p); + collector + in + collect_without_trailing_line_comment collector + +let object_type_property_comment_bounds property = + let open Ast.Type.Object in + let collector = + match property with + | Property ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#object_property_type p); + collector + | SpreadProperty ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#object_spread_property_type p); + collector + | Indexer ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#object_indexer_property_type p); + collector + | InternalSlot ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#object_internal_slot_property_type p); + collector + | CallProperty ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#object_call_property_type p); + collector + | MappedType ((loc, _) as p) -> + let collector = new comment_bounds_collector ~loc in + ignore (collector#object_mapped_type_property p); + collector + in + collect_without_trailing_line_comment collector + +let object_pattern_property_comment_bounds loc property = + let collector = new comment_bounds_collector ~loc in + ignore (collector#pattern_object_p property); + collect_without_trailing_line_comment collector + +let match_expression_case_comment_bounds (loc, case) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#match_case ~on_case_body:collector#expression (loc, case)); + collector#comment_bounds + +let match_statement_case_comment_bounds (loc, case) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#match_case ~on_case_body:collector#statement (loc, case)); + collector#comment_bounds + +let switch_case_comment_bounds (loc, case) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#switch_case (loc, case)); + collector#comment_bounds + +let function_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#function_param (loc, param)); + collect_without_trailing_line_comment collector + +let function_rest_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#function_rest_param (loc, param)); + collect_without_trailing_line_comment collector + +let function_this_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#function_this_param (loc, param)); + collect_without_trailing_line_comment collector + +let function_type_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#function_param_type (loc, param)); + collect_without_trailing_line_comment collector + +let function_type_rest_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#function_rest_param_type (loc, param)); + collect_without_trailing_line_comment collector + +let function_type_this_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#function_this_param_type (loc, param)); + collect_without_trailing_line_comment collector + +let component_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#component_param (loc, param)); + collect_without_trailing_line_comment collector + +let component_rest_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#component_rest_param (loc, param)); + collect_without_trailing_line_comment collector + +let component_type_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#component_type_param (loc, param)); + collect_without_trailing_line_comment collector + +let component_type_rest_param_comment_bounds (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#component_type_rest_param (loc, param)); + collect_without_trailing_line_comment collector + +let array_element_comment_bounds loc element = + let collector = new comment_bounds_collector ~loc in + ignore (collector#array_element element); + collect_without_trailing_line_comment collector + +let array_pattern_element_comment_bounds loc element = + let collector = new comment_bounds_collector ~loc in + ignore (collector#pattern_array_e element); + collect_without_trailing_line_comment collector + +let expression_or_spread_comment_bounds loc expr_or_spread = + let collector = new comment_bounds_collector ~loc in + ignore (collector#expression_or_spread expr_or_spread); + collect_without_trailing_line_comment collector + +let call_type_arg_comment_bounds loc arg = + let collector = new comment_bounds_collector ~loc in + ignore (collector#call_type_arg arg); + collect_without_trailing_line_comment collector + +let type_param_comment_bounds ~kind (loc, param) = + let collector = new comment_bounds_collector ~loc in + ignore (collector#type_param ~kind (loc, param)); + collect_without_trailing_line_comment collector + +let function_body_comment_bounds body = + let loc = + match body with + | Ast.Function.BodyBlock (loc, _) -> loc + | Ast.Function.BodyExpression (loc, _) -> loc + in + let collector = new comment_bounds_collector ~loc in + ignore (collector#function_body_any body); + collector#comment_bounds + +let if_alternate_statement_comment_bounds loc alternate = + let collector = new comment_bounds_collector ~loc in + ignore (collector#if_alternate_statement loc alternate); + collector#comment_bounds + +let member_property_comment_bounds loc property = + let collector = new comment_bounds_collector ~loc in + ignore (collector#member_property property); + collector#comment_bounds diff --git a/compiler/flow_parser/parser/comment_utils.ml b/compiler/flow_parser/parser/comment_utils.ml new file mode 100644 index 00000000000..738f230ef68 --- /dev/null +++ b/compiler/flow_parser/parser/comment_utils.ml @@ -0,0 +1,47 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* returns all of the comments that start before `loc`, and discards the rest *) +let comments_before_loc loc comments = + let rec helper loc acc = function + | ((c_loc, _) as comment) :: rest when Loc.compare c_loc loc < 0 -> + helper loc (comment :: acc) rest + | _ -> List.rev acc + in + helper loc [] comments + +class ['loc] inline_comments_stripper = + object + inherit ['loc] Flow_ast_mapper.mapper + + method! syntax_opt + : 'internal. + ('loc, 'internal) Flow_ast.Syntax.t option -> ('loc, 'internal) Flow_ast.Syntax.t option = + (fun _ -> None) + end + +let strip_inlined_comments p = (new inline_comments_stripper)#program p + +let strip_inlined_comments_expression expr = (new inline_comments_stripper)#expression expr + +let strip_comments_list + ?(preserve_docblock = false) ((loc, program) : ('loc, 'loc) Flow_ast.Program.t) = + let { Flow_ast.Program.all_comments; _ } = program in + ( loc, + { + program with + Flow_ast.Program.all_comments = + ( if preserve_docblock then + comments_before_loc loc all_comments + else + [] + ); + } + ) + +let strip_all_comments ?(preserve_docblock = false) p = + p |> strip_comments_list ~preserve_docblock |> strip_inlined_comments diff --git a/compiler/flow_parser/parser/declaration_parser.ml b/compiler/flow_parser/parser/declaration_parser.ml new file mode 100644 index 00000000000..d4dbd23c806 --- /dev/null +++ b/compiler/flow_parser/parser/declaration_parser.ml @@ -0,0 +1,640 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Token +open Parser_common +open Parser_env +open Flow_ast +open Comment_attachment + +module Declaration (Parse : Parser_common.PARSER) (Type : Parser_common.TYPE) : + Parser_common.DECLARATION = struct + module Enum = Enum_parser.Enum (Parse) + + let check_param = + let rec pattern ((env, _) as check_env) (loc, p) = + Pattern.( + match p with + | Object o -> _object check_env o + | Array arr -> _array check_env arr + | Identifier id -> identifier_pattern check_env id + | Expression _ -> + error_at env (loc, Parse_error.ExpectedPatternFoundExpression); + check_env + ) + and _object check_env o = List.fold_left object_property check_env o.Pattern.Object.properties + and object_property check_env = + let open Pattern.Object in + function + | Property (_, { Property.pattern = patt; key = _; shorthand = _; default = _ }) -> + pattern check_env patt + | RestElement (_, { Pattern.RestElement.argument; comments = _ }) -> + pattern check_env argument + and _array check_env arr = List.fold_left array_element check_env arr.Pattern.Array.elements + and array_element check_env = + let open Pattern.Array in + function + | Hole _ -> check_env + | Element (_, { Element.argument; default = _ }) -> pattern check_env argument + | RestElement (_, { Pattern.RestElement.argument; comments = _ }) -> + pattern check_env argument + and identifier_pattern check_env { Pattern.Identifier.name = id; _ } = identifier check_env id + and identifier (env, param_names) ((loc, { Identifier.name; comments = _ }) as id) = + if SSet.mem name param_names then error_at env (loc, Parse_error.StrictParamDupe); + let (env, param_names) = identifier_no_dupe_check (env, param_names) id in + (env, SSet.add name param_names) + and identifier_no_dupe_check (env, param_names) (loc, { Identifier.name; comments = _ }) = + if is_restricted name then strict_error_at env (loc, Parse_error.StrictParamName); + if is_strict_reserved name then strict_error_at env (loc, Parse_error.StrictReservedWord); + (env, param_names) + in + pattern + + (** Errors if there are any duplicate formal parameters + + https://tc39.es/ecma262/#sec-parameter-lists-static-semantics-early-errors *) + let check_unique_formal_parameters env params = + let (_, { Ast.Function.Params.params; rest; this_ = _; comments = _ }) = params in + let acc = + List.fold_left + (fun acc (_, { Function.Param.argument; default = _ }) -> check_param acc argument) + (env, SSet.empty) + params + in + match rest with + | Some (_, { Function.RestParam.argument; comments = _ }) -> ignore (check_param acc argument) + | None -> () + + (** This does the same check as check_unique_formal_parameters. However, it converts the component + * params to a single object destructure, then runs the check. This is done to best match the behavior + * of components still using function syntax. *) + let check_unique_component_formal_parameters env params = + let (_, { Ast.Statement.ComponentDeclaration.Params.params; rest; comments = _ }) = params in + let pattern_obj_props = + List.map + (fun (_, { Ast.Statement.ComponentDeclaration.Param.name; local; default; shorthand }) -> + let key = + match name with + | Ast.Statement.ComponentDeclaration.Param.StringLiteral (_, lit) -> + Ast.Pattern.Object.Property.StringLiteral (Loc.none, lit) + | Ast.Statement.ComponentDeclaration.Param.Identifier id -> + Ast.Pattern.Object.Property.Identifier id + in + Ast.Pattern.Object.Property + (Loc.none, { Ast.Pattern.Object.Property.key; pattern = local; default; shorthand })) + params + in + let obj_param = + ( Loc.none, + Ast.Pattern.Object + { + Ast.Pattern.Object.properties = pattern_obj_props; + annot = Ast.Type.Missing Loc.none; + comments = None; + } + ) + in + let acc = check_param (env, SSet.empty) obj_param in + match rest with + | Some (_, { Ast.Statement.ComponentDeclaration.RestParam.argument; comments = _ }) -> + ignore (check_param acc argument) + | None -> () + + type param_type = + | FunctionParams of (Loc.t, Loc.t) Ast.Function.Params.t + | ComponentParams of (Loc.t, Loc.t) Ast.Statement.ComponentDeclaration.Params.t + + let strict_post_check env ~contains_use_strict id params = + let strict_mode = Parser_env.in_strict_mode env in + let simple = + match params with + | FunctionParams p -> is_simple_parameter_list p + | ComponentParams _ -> + (* Component params are equivalent to an object destructure so not simple *) + false + in + (* If we were already in strict mode and therefore already threw strict + errors, we want to do these checks outside of strict mode. If we + were in non-strict mode but the function contains "use strict", then + we want to do these checks in strict mode *) + let env = + if strict_mode then + with_strict false env + else + with_strict contains_use_strict env + in + if contains_use_strict || strict_mode || not simple then ( + (match id with + | Some (loc, { Identifier.name; comments = _ }) -> + if is_restricted name then strict_error_at env (loc, Parse_error.StrictFunctionName); + if is_strict_reserved name then strict_error_at env (loc, Parse_error.StrictReservedWord) + | None -> ()); + match params with + | FunctionParams p -> check_unique_formal_parameters env p + | ComponentParams p -> check_unique_component_formal_parameters env p + ) + + let strict_function_post_check env ~contains_use_strict id params = + strict_post_check env ~contains_use_strict id (FunctionParams params) + + let strict_component_post_check env ~contains_use_strict id params = + strict_post_check env ~contains_use_strict (Some id) (ComponentParams params) + + let rest_param env t = + if t = T_ELLIPSIS then + let leading = Peek.comments env in + let (loc, id) = + with_loc + (fun env -> + Expect.token env T_ELLIPSIS; + Parse.pattern env Parse_error.StrictParamName) + env + in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Some (loc, id, comments) + else + None + + let function_params = + let rec param = + with_loc (fun env -> + if Peek.token env = T_THIS then error env Parse_error.ThisParamMustBeFirst; + let argument = Parse.pattern env Parse_error.StrictParamName in + let default = + if Peek.token env = T_ASSIGN then ( + Expect.token env T_ASSIGN; + Some (Parse.assignment env) + ) else + None + in + { Function.Param.argument; default } + ) + and param_list env acc = + match Peek.token env with + | (T_EOF | T_RPAREN | T_ELLIPSIS) as t -> + let rest = + rest_param env t + |> Option.map (fun (loc, id, comments) -> + (loc, { Function.RestParam.argument = id; comments }) + ) + in + if Peek.token env <> T_RPAREN then error env Parse_error.ParameterAfterRestParameter; + (List.rev acc, rest) + | _ -> + let the_param = param env in + if Peek.token env <> T_RPAREN then Expect.token env T_COMMA; + param_list env (the_param :: acc) + in + let this_param_annotation env = + if should_parse_types env && Peek.token env = T_THIS then ( + let leading = Peek.comments env in + let (this_loc, this_param) = + with_loc + (fun env -> + Expect.token env T_THIS; + if Peek.token env <> T_COLON then begin + error env Parse_error.ThisParamAnnotationRequired; + None + end else + Some (Type.annotation env)) + env + in + match this_param with + | None -> None + | Some annot -> + if Peek.token env = T_COMMA then Eat.token env; + Some + ( this_loc, + { + Ast.Function.ThisParam.annot; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + ) else + None + in + fun ~await ~yield -> + with_loc (fun env -> + let env = + env + |> with_allow_await await + |> with_allow_yield yield + |> with_in_formal_parameters true + in + let leading = Peek.comments env in + Expect.token env T_LPAREN; + let this_ = this_param_annotation env in + let (params, rest) = param_list env [] in + let internal = Peek.comments env in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + { + Ast.Function.Params.params; + rest; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + this_; + } + ) + + let function_or_component_body env ~async ~generator ~expression ~simple_params = + let env = enter_function env ~async ~generator ~simple_params in + Parse.function_block_body env ~expression + + let function_body env ~async ~generator ~expression ~simple_params = + let (body_block, contains_use_strict) = + function_or_component_body env ~async ~generator ~expression ~simple_params + in + (Function.BodyBlock body_block, contains_use_strict) + + let variance env ~parse_readonly is_async is_generator = + let loc = Peek.loc env in + let variance = + match Peek.token env with + | T_PLUS -> + let leading = Peek.comments env in + Eat.token env; + Some + ( loc, + { Variance.kind = Variance.Plus; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + | T_MINUS -> + let leading = Peek.comments env in + Eat.token env; + Some + ( loc, + { + Variance.kind = Variance.Minus; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + | T_IDENTIFIER { raw = "readonly"; _ } when parse_readonly -> + let leading = Peek.comments env in + Eat.token env; + Some + ( loc, + { + Variance.kind = Variance.Readonly; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + | _ -> None + in + match variance with + | Some (loc, _) when is_async || is_generator -> + error_at env (loc, Parse_error.UnexpectedVariance); + None + | _ -> variance + + let generator env = + if Peek.token env = T_MULT then ( + let leading = Peek.comments env in + Eat.token env; + (true, leading) + ) else + (false, []) + + (* Returns true and consumes a token if the token is `async` and the token after it is on + the same line (see https://tc39.github.io/ecma262/#sec-async-function-definitions) *) + let async env = + if Peek.token env = T_ASYNC && not (Peek.ith_is_line_terminator ~i:1 env) then + let leading = Peek.comments env in + let () = Eat.token env in + (true, leading) + else + (false, []) + + let _function = + with_loc (fun env -> + let (async, leading_async) = async env in + let (sig_loc, (generator, effect_, tparams, id, params, return, predicate, leading)) = + with_loc + (fun env -> + let leading_function = Peek.comments env in + let (effect_, (generator, leading_generator)) = + match Peek.token env with + | T_FUNCTION -> + Eat.token env; + (Function.Arbitrary, generator env) + | T_IDENTIFIER { raw = "hook"; _ } when not async -> + Eat.token env; + (Function.Hook, (false, [])) + | t -> + Expect.error env t; + (Function.Arbitrary, generator env) + in + let leading = List.concat [leading_async; leading_function; leading_generator] in + let (tparams, id) = + match (in_export_default env, Peek.token env) with + | (true, T_LPAREN) -> (None, None) + | (true, T_LESS_THAN) -> + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.DeclareFunctionTP + (Type.type_params env) + in + let id = + if Peek.token env = T_LPAREN then + None + else + let id = + id_remove_trailing + env + (Parse.identifier ~restricted_error:Parse_error.StrictFunctionName env) + in + Some id + in + (tparams, id) + | _ -> + let id = + if Peek.is_identifier env then + id_remove_trailing + env + (Parse.identifier ~restricted_error:Parse_error.StrictFunctionName env) + else ( + (* don't consume the identifier here like Parse.identifier does. *) + error_nameless_declaration env "function"; + (Peek.loc env, { Identifier.name = ""; comments = None }) + ) + in + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.DeclareFunctionTP + (Type.type_params env) + in + (tparams, Some id) + in + let params = + let params = function_params ~await:async ~yield:generator env in + if Peek.token env = T_COLON then + params + else + function_params_remove_trailing env params + in + let (return, predicate) = Type.function_return_annotation_and_predicate_opt env in + let (return, predicate) = + match predicate with + | None -> (return_annotation_remove_trailing env return, predicate) + | Some _ -> (return, predicate_remove_trailing env predicate) + in + (generator, effect_, tparams, id, params, return, predicate, leading)) + env + in + let simple_params = is_simple_parameter_list params in + let (body, contains_use_strict) = + function_body env ~async ~generator ~expression:false ~simple_params + in + strict_function_post_check env ~contains_use_strict id params; + Statement.FunctionDeclaration + { + Function.id; + params; + body; + generator; + effect_; + async; + predicate; + return; + tparams; + sig_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + + let variable_declaration_list = + let variable_declaration env = + let (loc, (decl, err)) = + with_loc + (fun env -> + let id = Parse.pattern env Parse_error.StrictVarName in + let (init, err) = + if Eat.maybe env T_ASSIGN then + (Some (Parse.assignment env), None) + else + match id with + | (_, Ast.Pattern.Identifier _) -> (None, None) + | (loc, _) -> (None, Some (loc, Parse_error.NoUninitializedDestructuring)) + in + (Ast.Statement.VariableDeclaration.Declarator.{ id; init }, err)) + env + in + ((loc, decl), err) + in + let rec helper env decls errs = + let (decl, err) = variable_declaration env in + let decls = decl :: decls in + let errs = + match err with + | Some x -> x :: errs + | None -> errs + in + if Eat.maybe env T_COMMA then + helper env decls errs + else + (List.rev decls, List.rev errs) + in + (fun env -> helper env [] []) + + let declarations token env = + let leading = Peek.comments env in + Expect.token env token; + if (parse_options env).enums && token = T_CONST && Peek.token env = T_ENUM then + error env Parse_error.EnumInvalidConstPrefix; + let (declarations, errs) = variable_declaration_list env in + (declarations, leading, errs) + + let var = declarations T_VAR + + let const env = + let env = env |> with_no_let true in + let (declarations, leading_comments, errs) = declarations T_CONST env in + (* Make sure all consts defined are initialized *) + let errs = + List.fold_left + (fun errs decl -> + match decl with + | (loc, { Statement.VariableDeclaration.Declarator.init = None; _ }) -> + (loc, Parse_error.NoUninitializedConst) :: errs + | _ -> errs) + errs + declarations + in + (declarations, leading_comments, List.rev errs) + + let let_ env = + let env = env |> with_no_let true in + declarations T_LET env + + let enum_declaration ?leading = + with_loc (fun env -> + let enum = Enum.declaration ?leading env in + Statement.EnumDeclaration enum + ) + + let component_params = + let rec param = + with_loc (fun env -> + let leading = Peek.comments env in + let (name, local, shorthand) = + match (Peek.token env, Peek.ith_token ~i:1 env) with + (* "prop-key" as propKey *) + | ( T_STRING (loc, value, raw, octal), + ((T_COLON | T_PLING | T_IDENTIFIER { raw = "as"; _ }) as next_token) + ) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + Expect.token env (T_STRING (loc, value, raw, octal)); + let trailing = Eat.trailing_comments env in + let name = + Statement.ComponentDeclaration.Param.StringLiteral + ( loc, + { + StringLiteral.value; + raw; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + in + (match next_token with + | T_COLON + | T_PLING -> + (* This is an error probably due to someone learning component syntax. Let's make a + * good error message and supply a quick fix *) + let optional = next_token = T_PLING in + error + env + Parse_error.(InvalidComponentStringParameterBinding { optional; name = value }); + if optional then Eat.token env; + let loc = Peek.loc env in + let fallback_ident = (loc, { Ast.Identifier.name = ""; comments = None }) in + let annot = Type.annotation_opt env in + let local = + ( loc, + Ast.Pattern.Identifier + { Ast.Pattern.Identifier.name = fallback_ident; annot; optional } + ) + in + (name, local, false) + | _ -> + Eat.token env; + let local = Parse.pattern env Parse_error.StrictParamName in + (name, local, false)) + | (_, T_IDENTIFIER { raw = "as"; _ }) -> + let name = Statement.ComponentDeclaration.Param.Identifier (identifier_name env) in + Expect.identifier env "as"; + (name, Parse.pattern env Parse_error.StrictParamName, false) + | (T_LCURLY, _) -> + error env Parse_error.InvalidComponentParamName; + let fake_name_loc = Peek.loc env in + let fallback_ident = (fake_name_loc, { Ast.Identifier.name = ""; comments = None }) in + let name = Statement.ComponentDeclaration.Param.Identifier fallback_ident in + let local = Parse.pattern env Parse_error.StrictParamName in + (name, local, false) + | (_, _) -> + let id = Parse.identifier_with_type env Parse_error.StrictParamName in + (match id with + | (loc, ({ Ast.Pattern.Identifier.name; _ } as id)) -> + ( Ast.Statement.ComponentDeclaration.Param.Identifier name, + (loc, Ast.Pattern.Identifier id), + true + )) + in + + let default = + if Peek.token env = T_ASSIGN then ( + Expect.token env T_ASSIGN; + Some (Parse.assignment env) + ) else + None + in + { Statement.ComponentDeclaration.Param.name; local; default; shorthand } + ) + and param_list env acc = + match Peek.token env with + | (T_EOF | T_RPAREN | T_ELLIPSIS) as t -> + let rest = + rest_param env t + |> Option.map (fun (loc, id, comments) -> + if Peek.token env = T_COMMA then Eat.token env; + (loc, { Statement.ComponentDeclaration.RestParam.argument = id; comments }) + ) + in + if Peek.token env <> T_RPAREN then error env Parse_error.ParameterAfterRestParameter; + (List.rev acc, rest) + | _ -> + let the_param = param env in + if Peek.token env <> T_RPAREN then Expect.token env T_COMMA; + param_list env (the_param :: acc) + in + with_loc (fun env -> + let env = env |> with_in_formal_parameters true in + let leading = Peek.comments env in + Expect.token env T_LPAREN; + let (params, rest) = param_list env [] in + let internal = Peek.comments env in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + { + Ast.Statement.ComponentDeclaration.Params.params; + rest; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + } + ) + + let component_body env = + function_or_component_body + env + ~async:false + ~generator:false + ~expression:false + ~simple_params:false + + let component = + with_loc (fun env -> + let (sig_loc, (tparams, id, params, renders, leading)) = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.identifier env "component"; + let id = + id_remove_trailing + env + (* Components should have at least the same strictness as functions *) + (Parse.identifier ~restricted_error:Parse_error.StrictFunctionName env) + in + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.DeclareComponentTP + (Type.type_params env) + in + let params = + let params = component_params env in + if Peek.is_renders_ident env then + params + else + component_params_remove_trailing env params + in + let renders = Type.renders_annotation_opt env in + let renders = component_renders_annotation_remove_trailing env renders in + (tparams, id, params, renders, leading)) + env + in + let (body, contains_use_strict) = component_body env in + strict_component_post_check env ~contains_use_strict id params; + Statement.ComponentDeclaration + { + Statement.ComponentDeclaration.id; + params; + body; + renders; + tparams; + sig_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) +end diff --git a/compiler/flow_parser/parser/declaration_parser.mli b/compiler/flow_parser/parser/declaration_parser.mli new file mode 100644 index 00000000000..cd6fe0f4349 --- /dev/null +++ b/compiler/flow_parser/parser/declaration_parser.mli @@ -0,0 +1,8 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Declaration (_ : Parser_common.PARSER) (_ : Parser_common.TYPE) : Parser_common.DECLARATION diff --git a/compiler/flow_parser/parser/dune b/compiler/flow_parser/parser/dune new file mode 100644 index 00000000000..d3851f347d8 --- /dev/null +++ b/compiler/flow_parser/parser/dune @@ -0,0 +1,6 @@ +(library + (name flow_parser) + (wrapped false) + (libraries base wtf8 flow_sedlexing collections) + (preprocess + (pps ppx_gen_rec ppx_deriving.std flow_sedlexing_ppx))) diff --git a/compiler/flow_parser/parser/enum_common.ml b/compiler/flow_parser/parser/enum_common.ml new file mode 100644 index 00000000000..f793c5c0241 --- /dev/null +++ b/compiler/flow_parser/parser/enum_common.ml @@ -0,0 +1,21 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type explicit_type = + | Boolean + | Number + | String + | Symbol + | BigInt +[@@deriving ord] + +let string_of_explicit_type = function + | Boolean -> "boolean" + | Number -> "number" + | String -> "string" + | Symbol -> "symbol" + | BigInt -> "bigint" diff --git a/compiler/flow_parser/parser/enum_parser.ml b/compiler/flow_parser/parser/enum_parser.ml new file mode 100644 index 00000000000..e2a9a5db776 --- /dev/null +++ b/compiler/flow_parser/parser/enum_parser.ml @@ -0,0 +1,502 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Flow_ast +open Parser_common +open Parser_env +open Token + +module Enum (Parse : Parser_common.PARSER) : sig + val declaration : + ?leading:Loc.t Comment.t list -> env -> (Loc.t, Loc.t) Statement.EnumDeclaration.t +end = struct + open Flow_ast.Statement.EnumDeclaration + + type members = { + boolean_members: (Loc.t BooleanLiteral.t, Loc.t) InitializedMember.t list; + number_members: (Loc.t NumberLiteral.t, Loc.t) InitializedMember.t list; + string_members: (Loc.t StringLiteral.t, Loc.t) InitializedMember.t list; + bigint_members: (Loc.t BigIntLiteral.t, Loc.t) InitializedMember.t list; + defaulted_members: Loc.t DefaultedMember.t list; + } + + type acc = { + members: members; + seen_names: SSet.t; + has_unknown_members: bool; + internal_comments: Loc.t Comment.t list; + } + + type init = + | NoInit + | InvalidInit of Loc.t + | BooleanInit of Loc.t * Loc.t BooleanLiteral.t + | NumberInit of Loc.t * Loc.t NumberLiteral.t + | StringInit of Loc.t * Loc.t StringLiteral.t + | BigIntInit of Loc.t * Loc.t BigIntLiteral.t + + let empty_members = + { + boolean_members = []; + number_members = []; + string_members = []; + bigint_members = []; + defaulted_members = []; + } + + let empty_acc = + { + members = empty_members; + seen_names = SSet.empty; + has_unknown_members = false; + internal_comments = []; + } + + let end_of_member_init env = + match Peek.token env with + | T_SEMICOLON + | T_COMMA + | T_RCURLY -> + true + | _ -> false + + let number_init env loc ~neg ~leading ~kind ~raw = + let value = Parse.number env kind raw in + let (value, raw) = + if neg then + (-.value, "-" ^ raw) + else + (value, raw) + in + let trailing = Eat.trailing_comments env in + if end_of_member_init env then + NumberInit + ( loc, + { + NumberLiteral.value; + raw; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + else + InvalidInit loc + + let member_init env = + let loc = Peek.loc env in + let leading = Peek.comments env in + match Peek.token env with + | T_MINUS -> + Eat.token env; + (match Peek.token env with + | T_NUMBER { kind; raw } -> number_init env loc ~neg:true ~leading ~kind ~raw + | _ -> InvalidInit loc) + | T_NUMBER { kind; raw } -> number_init env loc ~neg:false ~leading ~kind ~raw + | T_STRING (loc, value, raw, octal) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + Eat.token env; + let trailing = Eat.trailing_comments env in + if end_of_member_init env then + StringInit + ( loc, + { + StringLiteral.value; + raw; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + else + InvalidInit loc + | (T_TRUE | T_FALSE) as token -> + Eat.token env; + let trailing = Eat.trailing_comments env in + if end_of_member_init env then + BooleanInit + ( loc, + { + BooleanLiteral.value = token = T_TRUE; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + else + InvalidInit loc + | T_BIGINT { kind; raw } -> + let value = Parse.bigint env kind raw in + let trailing = Eat.trailing_comments env in + if end_of_member_init env then + BigIntInit + ( loc, + { + BigIntLiteral.value; + raw; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + else + InvalidInit loc + | _ -> + Eat.token env; + InvalidInit loc + + let member_raw = + with_loc (fun env -> + let id = identifier_name env in + let init = + match Peek.token env with + | T_ASSIGN -> + Expect.token env T_ASSIGN; + member_init env + | T_COLON -> + let (_, { Identifier.name = member_name; _ }) = id in + error env (Parse_error.EnumInvalidInitializerSeparator { member_name }); + Expect.token env T_COLON; + member_init env + | _ -> NoInit + in + (id, init) + ) + + let check_explicit_type_mismatch env ~enum_name ~explicit_type ~member_name literal_type loc = + match explicit_type with + | Some enum_type when enum_type <> literal_type -> + error_at + env + (loc, Parse_error.EnumInvalidMemberInitializer { enum_name; explicit_type; member_name }) + | _ -> () + + let is_a_to_z c = c >= 'a' && c <= 'z' + + let enum_member ~enum_name ~explicit_type acc env = + let { members; seen_names; _ } = acc in + let (member_loc, (id, init)) = member_raw env in + let (id_loc, { Identifier.name = member_name; _ }) = id in + (* if we parsed an empty name, something has gone wrong and we should abort analysis *) + if member_name = "" then + acc + else ( + if is_a_to_z @@ member_name.[0] then + error_at env (id_loc, Parse_error.EnumInvalidMemberName { enum_name; member_name }); + if SSet.mem member_name seen_names then + error_at env (id_loc, Parse_error.EnumDuplicateMemberName { enum_name; member_name }); + let acc = { acc with seen_names = SSet.add member_name seen_names } in + let check_explicit_type_mismatch = + check_explicit_type_mismatch env ~enum_name ~explicit_type ~member_name + in + match init with + | BooleanInit (loc, value) -> + check_explicit_type_mismatch Enum_common.Boolean loc; + let member = (member_loc, { InitializedMember.id; init = (loc, value) }) in + { acc with members = { members with boolean_members = member :: members.boolean_members } } + | NumberInit (loc, value) -> + check_explicit_type_mismatch Enum_common.Number loc; + let member = (member_loc, { InitializedMember.id; init = (loc, value) }) in + { acc with members = { members with number_members = member :: members.number_members } } + | StringInit (loc, value) -> + check_explicit_type_mismatch Enum_common.String loc; + let member = (member_loc, { InitializedMember.id; init = (loc, value) }) in + { acc with members = { members with string_members = member :: members.string_members } } + | BigIntInit (loc, value) -> + check_explicit_type_mismatch Enum_common.BigInt loc; + let member = (member_loc, { InitializedMember.id; init = (loc, value) }) in + { acc with members = { members with bigint_members = member :: members.bigint_members } } + | InvalidInit loc -> + error_at + env + (loc, Parse_error.EnumInvalidMemberInitializer { enum_name; explicit_type; member_name }); + acc + | NoInit -> begin + match explicit_type with + | Some Enum_common.Boolean -> + error_at + env + (member_loc, Parse_error.EnumBooleanMemberNotInitialized { enum_name; member_name }); + acc + | Some Enum_common.Number -> + error_at + env + (member_loc, Parse_error.EnumNumberMemberNotInitialized { enum_name; member_name }); + acc + | Some Enum_common.BigInt -> + error_at + env + (member_loc, Parse_error.EnumBigIntMemberNotInitialized { enum_name; member_name }); + acc + | Some Enum_common.String + | Some Enum_common.Symbol + | None -> + let member = (member_loc, { DefaultedMember.id }) in + { + acc with + members = { members with defaulted_members = member :: members.defaulted_members }; + } + end + ) + + let rec enum_members ~enum_name ~explicit_type acc env = + match Peek.token env with + | T_RCURLY + | T_EOF -> + ( { + boolean_members = List.rev acc.members.boolean_members; + number_members = List.rev acc.members.number_members; + string_members = List.rev acc.members.string_members; + bigint_members = List.rev acc.members.bigint_members; + defaulted_members = List.rev acc.members.defaulted_members; + }, + acc.has_unknown_members, + acc.internal_comments + ) + | T_ELLIPSIS -> + let loc = Peek.loc env in + (* Internal comments may appear before the ellipsis *) + let internal_comments = Peek.comments env in + Eat.token env; + (match Peek.token env with + | T_RCURLY + | T_EOF -> + () + | T_COMMA -> + Expect.token env T_COMMA; + let trailing_comma = + match Peek.token env with + | T_RCURLY + | T_EOF -> + true + | _ -> false + in + error_at env (loc, Parse_error.EnumInvalidEllipsis { trailing_comma }) + | _ -> error_at env (loc, Parse_error.EnumInvalidEllipsis { trailing_comma = false })); + enum_members + ~enum_name + ~explicit_type + { acc with has_unknown_members = true; internal_comments } + env + | _ -> + let acc = enum_member ~enum_name ~explicit_type acc env in + (match Peek.token env with + | T_RCURLY + | T_EOF -> + () + | T_SEMICOLON -> + error env Parse_error.EnumInvalidMemberSeparator; + Expect.token env T_SEMICOLON + | _ -> Expect.token env T_COMMA); + enum_members ~enum_name ~explicit_type acc env + + let string_body + ~env ~enum_name ~is_explicit ~has_unknown_members string_members defaulted_members comments = + let initialized_len = List.length string_members in + let defaulted_len = List.length defaulted_members in + let defaulted_body () = + StringBody + { + StringBody.members = StringBody.Defaulted defaulted_members; + explicit_type = is_explicit; + has_unknown_members; + comments; + } + in + let initialized_body () = + StringBody + { + StringBody.members = StringBody.Initialized string_members; + explicit_type = is_explicit; + has_unknown_members; + comments; + } + in + match (initialized_len, defaulted_len) with + | (0, 0) + | (0, _) -> + defaulted_body () + | (_, 0) -> initialized_body () + | _ when defaulted_len > initialized_len -> + List.iter + (fun (loc, _) -> + error_at env (loc, Parse_error.EnumStringMemberInconsistentlyInitialized { enum_name })) + string_members; + defaulted_body () + | _ -> + List.iter + (fun (loc, _) -> + error_at env (loc, Parse_error.EnumStringMemberInconsistentlyInitialized { enum_name })) + defaulted_members; + initialized_body () + + let parse_explicit_type ~enum_name env = + if Eat.maybe env T_OF then ( + Eat.push_lex_mode env Lex_mode.TYPE; + let result = + match Peek.token env with + | T_BOOLEAN_TYPE BOOLEAN -> Some Enum_common.Boolean + | T_NUMBER_TYPE -> Some Enum_common.Number + | T_STRING_TYPE -> Some Enum_common.String + | T_SYMBOL_TYPE -> Some Enum_common.Symbol + | T_BIGINT_TYPE -> Some Enum_common.BigInt + | T_IDENTIFIER { value; _ } -> + let supplied_type = Some value in + error env (Parse_error.EnumInvalidExplicitType { enum_name; supplied_type }); + None + | _ -> + error env (Parse_error.EnumInvalidExplicitType { enum_name; supplied_type = None }); + None + in + Eat.token env; + Eat.pop_lex_mode env; + result + ) else + None + + let enum_body ~enum_name ~name_loc = + with_loc (fun env -> + let explicit_type = parse_explicit_type ~enum_name env in + let leading = + if explicit_type <> None then + Peek.comments env + else + [] + in + Expect.token env T_LCURLY; + let (members, has_unknown_members, internal) = + enum_members ~enum_name ~explicit_type empty_acc env + in + let internal = internal @ Peek.comments env in + Expect.token env T_RCURLY; + let trailing = + match Peek.token env with + | T_EOF + | T_RCURLY -> + Eat.trailing_comments env + | _ when Peek.is_line_terminator env -> Eat.comments_until_next_line env + | _ -> [] + in + let comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal () + in + let body = + match explicit_type with + | Some Enum_common.Boolean -> + BooleanBody + { + BooleanBody.members = members.boolean_members; + explicit_type = true; + has_unknown_members; + comments; + } + | Some Enum_common.Number -> + NumberBody + { + NumberBody.members = members.number_members; + explicit_type = true; + has_unknown_members; + comments; + } + | Some Enum_common.String -> + string_body + ~env + ~enum_name + ~is_explicit:true + ~has_unknown_members + members.string_members + members.defaulted_members + comments + | Some Enum_common.Symbol -> + SymbolBody + { SymbolBody.members = members.defaulted_members; has_unknown_members; comments } + | Some Enum_common.BigInt -> + BigIntBody + { + BigIntBody.members = members.bigint_members; + explicit_type = true; + has_unknown_members; + comments; + } + | None -> + let bools_len = List.length members.boolean_members in + let nums_len = List.length members.number_members in + let bigints_len = List.length members.bigint_members in + let strs_len = List.length members.string_members in + let defaulted_len = List.length members.defaulted_members in + let empty () = + StringBody + { + StringBody.members = StringBody.Defaulted []; + explicit_type = false; + has_unknown_members; + comments; + } + in + begin + match (bools_len, nums_len, bigints_len, strs_len, defaulted_len) with + | (0, 0, 0, 0, 0) -> empty () + | (0, 0, 0, _, _) -> + string_body + ~env + ~enum_name + ~is_explicit:false + ~has_unknown_members + members.string_members + members.defaulted_members + comments + | (_, 0, 0, 0, _) when bools_len >= defaulted_len -> + List.iter + (fun (loc, { DefaultedMember.id = (_, { Identifier.name = member_name; _ }) }) -> + error_at + env + (loc, Parse_error.EnumBooleanMemberNotInitialized { enum_name; member_name })) + members.defaulted_members; + BooleanBody + { + BooleanBody.members = members.boolean_members; + explicit_type = false; + has_unknown_members; + comments; + } + | (0, _, 0, 0, _) when nums_len >= defaulted_len -> + List.iter + (fun (loc, { DefaultedMember.id = (_, { Identifier.name = member_name; _ }) }) -> + error_at + env + (loc, Parse_error.EnumNumberMemberNotInitialized { enum_name; member_name })) + members.defaulted_members; + NumberBody + { + NumberBody.members = members.number_members; + explicit_type = false; + has_unknown_members; + comments; + } + | (0, 0, _, 0, _) when bigints_len >= defaulted_len -> + List.iter + (fun (loc, { DefaultedMember.id = (_, { Identifier.name = member_name; _ }) }) -> + error_at + env + (loc, Parse_error.EnumNumberMemberNotInitialized { enum_name; member_name })) + members.defaulted_members; + BigIntBody + { + BigIntBody.members = members.bigint_members; + explicit_type = false; + has_unknown_members; + comments; + } + | _ -> + error_at env (name_loc, Parse_error.EnumInconsistentMemberValues { enum_name }); + empty () + end + in + body + ) + + let declaration ?(leading = []) env = + let leading = leading @ Peek.comments env in + Expect.token env T_ENUM; + let id = Parse.identifier env in + let (name_loc, { Identifier.name = enum_name; _ }) = id in + let body = enum_body ~enum_name ~name_loc env in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + { Statement.EnumDeclaration.id; body; comments } +end diff --git a/compiler/flow_parser/parser/estree_translator.ml b/compiler/flow_parser/parser/estree_translator.ml new file mode 100644 index 00000000000..ee435f5341b --- /dev/null +++ b/compiler/flow_parser/parser/estree_translator.ml @@ -0,0 +1,2459 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Ast = Flow_ast + +module type Config = sig + val include_locs : bool +end + +module Translate (Impl : Translator_intf.S) (Config : Config) : sig + type t + + val program : Offset_utils.t option -> (Loc.t, Loc.t) Ast.Program.t -> t + + val expression : Offset_utils.t option -> (Loc.t, Loc.t) Ast.Expression.t -> t + + val errors : (Loc.t * Parse_error.t) list -> t +end +with type t = Impl.t = struct + type t = Impl.t + + type functions = { + program: (Loc.t, Loc.t) Ast.Program.t -> t; + expression: (Loc.t, Loc.t) Ast.Expression.t -> t; + } + + open Ast + open Impl + + let array_of_list fn list = array (List.rev_map fn list |> List.rev) + + let option f = function + | Some v -> f v + | None -> null + + let hint f = function + | Ast.Type.Available v -> f v + | Ast.Type.Missing _ -> null + + let position p = obj [("line", int p.Loc.line); ("column", int p.Loc.column)] + + let loc location = + let source = + match Loc.source location with + | Some (File_key.LibFile src) + | Some (File_key.SourceFile src) + | Some (File_key.JsonFile src) + | Some (File_key.ResourceFile src) -> + string src + | None -> null + in + obj + [ + ("source", source); + ("start", position location.Loc.start); + ("end", position location.Loc._end); + ] + + let errors l = + let error (location, e) = + obj [("loc", loc location); ("message", string (Parse_error.PP.error e))] + in + array_of_list error l + + let format_internal_comments = function + | None -> None + | Some { Ast.Syntax.leading; trailing; internal } -> + Flow_ast_utils.mk_comments_opt ~leading ~trailing:(internal @ trailing) () + + (* This is basically a lightweight class. We close over some state and then return more than one + * function that can access that state. We don't need most class features though, so let's avoid + * the dynamic dispatch and the disruptive change. *) + let make_functions offset_table = + let range offset_table location = + Loc.( + array + [ + int (Offset_utils.offset offset_table location.start); + int (Offset_utils.offset offset_table location._end); + ] + ) + in + let rec node _type location ?comments props = + let locs = + if Config.include_locs then + (* sorted backwards due to the rev_append below *) + let range = + match offset_table with + | Some table -> [("range", range table location)] + | None -> [] + in + range @ [("loc", loc location)] + else + [] + in + let comments = + let open Ast.Syntax in + match comments with + | Some c -> + (match c with + | { leading = _ :: _ as l; trailing = _ :: _ as t; _ } -> + [("leadingComments", comment_list l); ("trailingComments", comment_list t)] + | { leading = _ :: _ as l; trailing = []; _ } -> [("leadingComments", comment_list l)] + | { leading = []; trailing = _ :: _ as t; _ } -> [("trailingComments", comment_list t)] + | _ -> []) + | None -> [] + in + let prefix = locs @ comments @ [("type", string _type)] in + obj (List.rev_append prefix props) + and program (loc, { Ast.Program.statements; interpreter; comments; all_comments }) = + let body = statement_list statements in + let props = [("body", body); ("comments", comment_list all_comments)] in + let props = + match interpreter with + | Some (loc, value) -> + let directive = node "InterpreterDirective" loc [("value", string value)] in + props @ [("interpreter", directive)] + | None -> props + in + node ?comments "Program" loc props + and statement_list statements = array_of_list statement statements + and statement = + let open Statement in + function + | (loc, Empty { Empty.comments }) -> node ?comments "EmptyStatement" loc [] + | (loc, Block b) -> block (loc, b) + | (loc, Expression { Expression.expression = expr; directive; comments }) -> + node + ?comments + "ExpressionStatement" + loc + [("expression", expression expr); ("directive", option string directive)] + | (loc, If { If.test; consequent; alternate; comments }) -> + let alternate = + match alternate with + | None -> null + | Some (_, { If.Alternate.body; comments = alternate_comments }) -> + statement (Comment_attachment.statement_add_comments body alternate_comments) + in + node + ?comments + "IfStatement" + loc + [ + ("test", expression test); ("consequent", statement consequent); ("alternate", alternate); + ] + | (loc, Labeled { Labeled.label; body; comments }) -> + node + ?comments + "LabeledStatement" + loc + [("label", identifier label); ("body", statement body)] + | (loc, Break { Break.label; comments }) -> + node ?comments "BreakStatement" loc [("label", option identifier label)] + | (loc, Continue { Continue.label; comments }) -> + node ?comments "ContinueStatement" loc [("label", option identifier label)] + | (loc, With { With._object; body; comments }) -> + node + ?comments + "WithStatement" + loc + [("object", expression _object); ("body", statement body)] + | (loc, TypeAlias alias) -> type_alias (loc, alias) + | (loc, OpaqueType opaque_t) -> opaque_type ~declare:false (loc, opaque_t) + | (loc, Match { Match.arg; cases; match_keyword_loc = _; comments }) -> + node + ?comments + "MatchStatement" + loc + [("argument", expression arg); ("cases", array_of_list match_statement_case cases)] + | (loc, Switch { Switch.discriminant; cases; comments; exhaustive_out = _ }) -> + node + ?comments + "SwitchStatement" + loc + [("discriminant", expression discriminant); ("cases", array_of_list case cases)] + | (loc, Return { Return.argument; comments; return_out = _ }) -> + node ?comments "ReturnStatement" loc [("argument", option expression argument)] + | (loc, Throw { Throw.argument; comments }) -> + node ?comments "ThrowStatement" loc [("argument", expression argument)] + | (loc, Try { Try.block = block_; handler; finalizer; comments }) -> + node + ?comments + "TryStatement" + loc + [ + ("block", block block_); + ("handler", option catch handler); + ("finalizer", option block finalizer); + ] + | (loc, While { While.test; body; comments }) -> + node ?comments "WhileStatement" loc [("test", expression test); ("body", statement body)] + | (loc, DoWhile { DoWhile.body; test; comments }) -> + node ?comments "DoWhileStatement" loc [("body", statement body); ("test", expression test)] + | (loc, For { For.init = init_; test; update; body; comments }) -> + let init = function + | For.InitDeclaration init -> variable_declaration init + | For.InitExpression expr -> expression expr + in + node + ?comments + "ForStatement" + loc + [ + ("init", option init init_); + ("test", option expression test); + ("update", option expression update); + ("body", statement body); + ] + | (loc, ForIn { ForIn.left; right; body; each; comments }) -> + let left = + match left with + | ForIn.LeftDeclaration left -> variable_declaration left + | ForIn.LeftPattern left -> pattern left + in + node + ?comments + "ForInStatement" + loc + [ + ("left", left); + ("right", expression right); + ("body", statement body); + ("each", bool each); + ] + | (loc, ForOf { ForOf.await; left; right; body; comments }) -> + let left = + match left with + | ForOf.LeftDeclaration left -> variable_declaration left + | ForOf.LeftPattern left -> pattern left + in + node + ?comments + "ForOfStatement" + loc + [ + ("left", left); + ("right", expression right); + ("body", statement body); + ("await", bool await); + ] + | (loc, EnumDeclaration enum) -> enum_declaration (loc, enum) + | (loc, Debugger { Debugger.comments }) -> node ?comments "DebuggerStatement" loc [] + | (loc, ClassDeclaration c) -> class_declaration (loc, c) + | (loc, InterfaceDeclaration i) -> interface_declaration (loc, i) + | (loc, VariableDeclaration var) -> variable_declaration (loc, var) + | (loc, FunctionDeclaration fn) -> function_declaration (loc, fn) + | (loc, ComponentDeclaration c) -> component_declaration (loc, c) + | (loc, DeclareVariable d) -> declare_variable (loc, d) + | (loc, DeclareFunction d) -> declare_function (loc, d) + | (loc, DeclareClass d) -> declare_class (loc, d) + | (loc, DeclareComponent d) -> declare_component (loc, d) + | (loc, DeclareEnum enum) -> declare_enum (loc, enum) + | (loc, DeclareInterface i) -> declare_interface (loc, i) + | (loc, DeclareTypeAlias a) -> declare_type_alias (loc, a) + | (loc, DeclareOpaqueType t) -> opaque_type ~declare:true (loc, t) + | (loc, DeclareModule { DeclareModule.id; body; comments }) -> + let id = + match id with + | DeclareModule.Literal lit -> string_literal lit + | DeclareModule.Identifier id -> identifier id + in + node ?comments "DeclareModule" loc [("id", id); ("body", block body)] + | (loc, DeclareNamespace { DeclareNamespace.id; body; comments }) -> + let (id, global) = + match id with + | DeclareNamespace.Local id -> (identifier id, false) + | DeclareNamespace.Global id -> (identifier id, true) + in + let props = [("id", id); ("body", block body)] in + let props = + if global then + ("global", bool global) :: props + else + props + in + node ?comments "DeclareNamespace" loc props + | ( loc, + DeclareExportDeclaration + { DeclareExportDeclaration.specifiers; declaration; default; source; comments } + ) -> begin + match specifiers with + | Some (ExportNamedDeclaration.ExportBatchSpecifier (_, None)) -> + node + ?comments + "DeclareExportAllDeclaration" + loc + [("source", option string_literal source)] + | _ -> + let declaration = + match declaration with + | Some (DeclareExportDeclaration.Variable v) -> declare_variable v + | Some (DeclareExportDeclaration.Function f) -> declare_function f + | Some (DeclareExportDeclaration.Class c) -> declare_class c + | Some (DeclareExportDeclaration.Component c) -> declare_component c + | Some (DeclareExportDeclaration.DefaultType t) -> _type t + | Some (DeclareExportDeclaration.NamedType t) -> type_alias t + | Some (DeclareExportDeclaration.NamedOpaqueType t) -> opaque_type ~declare:true t + | Some (DeclareExportDeclaration.Interface i) -> interface_declaration i + | Some (DeclareExportDeclaration.Enum enum) -> declare_enum enum + | None -> null + in + node + ?comments + "DeclareExportDeclaration" + loc + [ + ( "default", + bool + (match default with + | Some _ -> true + | None -> false) + ); + ("declaration", declaration); + ("specifiers", export_specifiers specifiers); + ("source", option string_literal source); + ] + end + | (loc, DeclareModuleExports { DeclareModuleExports.annot; comments }) -> + node ?comments "DeclareModuleExports" loc [("typeAnnotation", type_annotation annot)] + | ( loc, + ExportNamedDeclaration + { ExportNamedDeclaration.specifiers; declaration; source; export_kind; comments } + ) -> begin + match specifiers with + | Some (ExportNamedDeclaration.ExportBatchSpecifier (_, exported)) -> + node + ?comments + "ExportAllDeclaration" + loc + [ + ("source", option string_literal source); + ("exported", option identifier exported); + ("exportKind", string (string_of_export_kind export_kind)); + ] + | _ -> + node + ?comments + "ExportNamedDeclaration" + loc + [ + ("declaration", option statement declaration); + ("specifiers", export_specifiers specifiers); + ("source", option string_literal source); + ("exportKind", string (string_of_export_kind export_kind)); + ] + end + | ( loc, + ExportDefaultDeclaration + { + ExportDefaultDeclaration.declaration; + default = _ (* TODO: confirm we shouldn't use this *); + comments; + } + ) -> + let declaration = + match declaration with + | ExportDefaultDeclaration.Declaration stmt -> statement stmt + | ExportDefaultDeclaration.Expression expr -> expression expr + in + node + ?comments + "ExportDefaultDeclaration" + loc + [ + ("declaration", declaration); + ("exportKind", string (string_of_export_kind Statement.ExportValue)); + ] + | ( loc, + ImportDeclaration { ImportDeclaration.specifiers; default; import_kind; source; comments } + ) -> + let specifiers = + match specifiers with + | Some (ImportDeclaration.ImportNamedSpecifiers specifiers) -> + List.map + (fun { ImportDeclaration.local; remote; remote_name_def_loc = _; kind } -> + import_named_specifier local remote kind) + specifiers + | Some (ImportDeclaration.ImportNamespaceSpecifier id) -> [import_namespace_specifier id] + | None -> [] + in + let specifiers = + match default with + | Some default -> import_default_specifier default :: specifiers + | None -> specifiers + in + let import_kind = + match import_kind with + | ImportDeclaration.ImportType -> "type" + | ImportDeclaration.ImportTypeof -> "typeof" + | ImportDeclaration.ImportValue -> "value" + in + node + ?comments + "ImportDeclaration" + loc + [ + ("specifiers", array specifiers); + ("source", string_literal source); + ("importKind", string import_kind); + ] + and expression = + let open Expression in + function + | (loc, This { This.comments }) -> node ?comments "ThisExpression" loc [] + | (loc, Super { Super.comments }) -> node ?comments "Super" loc [] + | (loc, Array { Array.elements; comments }) -> + node + ?comments:(format_internal_comments comments) + "ArrayExpression" + loc + [("elements", array_of_list array_element elements)] + | (loc, Object { Object.properties; comments }) -> + node + ?comments:(format_internal_comments comments) + "ObjectExpression" + loc + [("properties", array_of_list object_property properties)] + | (loc, Function _function) -> function_expression (loc, _function) + | ( loc, + ArrowFunction + { + Function.params = (_, { Function.Params.comments = params_comments; _ }) as params; + async; + effect_ = _; + predicate = predicate_; + tparams; + return; + body; + comments = func_comments; + sig_loc = _; + (* TODO: arrows shouldn't have these: *) + id = _; + generator = _; + } + ) -> + let (body, expression) = + match body with + | Function.BodyBlock b -> (block b, false) + | Function.BodyExpression expr -> (expression expr, true) + in + let comments = + Flow_ast_utils.merge_comments + ~outer:func_comments + ~inner:(format_internal_comments params_comments) + in + node + ?comments + "ArrowFunctionExpression" + loc + [ + ("id", null); + ("params", function_params params); + ("body", body); + ("async", bool async); + ("generator", bool false); + ("predicate", option predicate predicate_); + ("expression", bool expression); + ("returnType", function_return_type return); + ("typeParameters", option type_parameter_declaration tparams); + ] + | (loc, Sequence { Sequence.expressions; comments }) -> + node + ?comments + "SequenceExpression" + loc + [("expressions", array_of_list expression expressions)] + | (loc, Unary { Unary.operator; argument; comments }) -> + Unary.( + (match operator with + | Await -> node ?comments "AwaitExpression" loc [("argument", expression argument)] + | _ -> + let operator = + match operator with + | Minus -> "-" + | Plus -> "+" + | Not -> "!" + | BitNot -> "~" + | Typeof -> "typeof" + | Void -> "void" + | Delete -> "delete" + | Await -> failwith "matched above" + in + node + ?comments + "UnaryExpression" + loc + [ + ("operator", string operator); + ("prefix", bool true); + ("argument", expression argument); + ]) + ) + | (loc, Binary { Binary.left; operator; right; comments }) -> + node + ?comments + "BinaryExpression" + loc + [ + ("operator", string (Flow_ast_utils.string_of_binary_operator operator)); + ("left", expression left); + ("right", expression right); + ] + | (loc, TypeCast { TypeCast.expression = expr; annot; comments }) -> + node + ?comments + "TypeCastExpression" + loc + [("expression", expression expr); ("typeAnnotation", type_annotation annot)] + | (loc, AsExpression { AsExpression.expression = expr; annot = (_, annot); comments }) -> + node + ?comments + "AsExpression" + loc + [("expression", expression expr); ("typeAnnotation", _type annot)] + | (loc, TSSatisfies { TSSatisfies.expression = expr; annot = (_, annot); comments }) -> + node + ?comments + "SatisfiesExpression" + loc + [("expression", expression expr); ("typeAnnotation", _type annot)] + | (loc, AsConstExpression { AsConstExpression.expression = expr; comments }) -> + node ?comments "AsConstExpression" loc [("expression", expression expr)] + | (loc, Assignment { Assignment.left; operator; right; comments }) -> + let operator = + match operator with + | None -> "=" + | Some op -> Flow_ast_utils.string_of_assignment_operator op + in + node + ?comments + "AssignmentExpression" + loc + [("operator", string operator); ("left", pattern left); ("right", expression right)] + | (loc, Update { Update.operator; argument; prefix; comments }) -> + let operator = + match operator with + | Update.Increment -> "++" + | Update.Decrement -> "--" + in + node + ?comments + "UpdateExpression" + loc + [ + ("operator", string operator); ("argument", expression argument); ("prefix", bool prefix); + ] + | (loc, Logical { Logical.left; operator; right; comments }) -> + let operator = + match operator with + | Logical.Or -> "||" + | Logical.And -> "&&" + | Logical.NullishCoalesce -> "??" + in + node + ?comments + "LogicalExpression" + loc + [("operator", string operator); ("left", expression left); ("right", expression right)] + | (loc, Conditional { Conditional.test; consequent; alternate; comments }) -> + node + ?comments + "ConditionalExpression" + loc + [ + ("test", expression test); + ("consequent", expression consequent); + ("alternate", expression alternate); + ] + | (loc, New { New.callee; targs; arguments; comments }) -> + let (arguments, comments) = + match arguments with + | Some ((_, { ArgList.comments = args_comments; _ }) as arguments) -> + ( arg_list arguments, + Flow_ast_utils.merge_comments + ~inner:(format_internal_comments args_comments) + ~outer:comments + ) + | None -> (array [], comments) + in + node + ?comments + "NewExpression" + loc + [ + ("callee", expression callee); + ("typeArguments", option call_type_args targs); + ("arguments", arguments); + ] + | ( loc, + Call + ({ Call.comments; arguments = (_, { ArgList.comments = args_comments; _ }); _ } as call) + ) -> + let comments = + Flow_ast_utils.merge_comments + ~inner:(format_internal_comments args_comments) + ~outer:comments + in + node ?comments "CallExpression" loc (call_node_properties call) + | ( loc, + OptionalCall + { + OptionalCall.call = + { Call.comments; arguments = (_, { ArgList.comments = args_comments; _ }); _ } as + call; + optional; + filtered_out = _; + } + ) -> + let comments = + Flow_ast_utils.merge_comments + ~inner:(format_internal_comments args_comments) + ~outer:comments + in + node + ?comments + "OptionalCallExpression" + loc + (call_node_properties call @ [("optional", bool optional)]) + | (loc, Member ({ Member.comments; _ } as member)) -> + node ?comments "MemberExpression" loc (member_node_properties member) + | ( loc, + OptionalMember + { OptionalMember.member = { Member.comments; _ } as member; optional; filtered_out = _ } + ) -> + node + ?comments + "OptionalMemberExpression" + loc + (member_node_properties member @ [("optional", bool optional)]) + | (loc, Yield { Yield.argument; delegate; comments; result_out = _ }) -> + node + ?comments + "YieldExpression" + loc + [("argument", option expression argument); ("delegate", bool delegate)] + | (_loc, Identifier id) -> identifier id + | (loc, StringLiteral lit) -> string_literal (loc, lit) + | (loc, BooleanLiteral lit) -> boolean_literal (loc, lit) + | (loc, NullLiteral lit) -> null_literal (loc, lit) + | (loc, NumberLiteral lit) -> number_literal (loc, lit) + | (loc, BigIntLiteral lit) -> bigint_literal (loc, lit) + | (loc, RegExpLiteral lit) -> regexp_literal (loc, lit) + | (loc, ModuleRefLiteral lit) -> module_ref_literal (loc, lit) + | (loc, TemplateLiteral lit) -> template_literal (loc, lit) + | (loc, TaggedTemplate tagged) -> tagged_template (loc, tagged) + | (loc, Class c) -> class_expression (loc, c) + | (loc, JSXElement element) -> jsx_element (loc, element) + | (loc, JSXFragment fragment) -> jsx_fragment (loc, fragment) + | (loc, Match { Match.arg; cases; comments; match_keyword_loc = _ }) -> + node + ?comments + "MatchExpression" + loc + [("argument", expression arg); ("cases", array_of_list match_expression_case cases)] + | (loc, MetaProperty { MetaProperty.meta; property; comments }) -> + node + ?comments + "MetaProperty" + loc + [("meta", identifier meta); ("property", identifier property)] + | (loc, Import { Import.argument; comments }) -> + node ?comments "ImportExpression" loc [("source", expression argument)] + and match_expression_case case = match_case "MatchExpressionCase" ~on_case_body:expression case + and match_case + : 'B. string -> on_case_body:('B -> Impl.t) -> (Loc.t, Loc.t, 'B) Match.Case.t -> Impl.t = + fun kind ~on_case_body (loc, { Match.Case.pattern; body; guard; comments }) -> + node + ?comments + kind + loc + [ + ("pattern", match_pattern pattern); + ("body", on_case_body body); + ("guard", option expression guard); + ] + and match_statement_case case = match_case "MatchStatementCase" ~on_case_body:statement case + and match_pattern (loc, pattern) = + let open MatchPattern in + let literal x = node "MatchLiteralPattern" loc [("literal", x)] in + match pattern with + | WildcardPattern comments -> node ?comments "MatchWildcardPattern" loc [] + | StringPattern lit -> literal (string_literal (loc, lit)) + | BooleanPattern lit -> literal (boolean_literal (loc, lit)) + | NullPattern comments -> literal (null_literal (loc, comments)) + | NumberPattern lit -> literal (number_literal (loc, lit)) + | BigIntPattern lit -> literal (bigint_literal (loc, lit)) + | UnaryPattern { UnaryPattern.operator; argument; comments } -> + let operator = + match operator with + | UnaryPattern.Minus -> "-" + | UnaryPattern.Plus -> "+" + in + let argument = + match argument with + | (loc, UnaryPattern.NumberLiteral lit) -> number_literal (loc, lit) + | (loc, UnaryPattern.BigIntLiteral lit) -> bigint_literal (loc, lit) + in + node + ?comments + "MatchUnaryPattern" + loc + [("operator", string operator); ("argument", argument)] + | BindingPattern binding -> match_binding_pattern (loc, binding) + | IdentifierPattern id -> match_identifier_pattern id + | MemberPattern mem -> + let rec member (loc, { MemberPattern.base; property; comments }) = + let member_base = function + | MemberPattern.BaseIdentifier id -> match_identifier_pattern id + | MemberPattern.BaseMember mem -> member mem + in + let member_property = function + | MemberPattern.PropertyString lit -> string_literal lit + | MemberPattern.PropertyNumber lit -> number_literal lit + | MemberPattern.PropertyBigInt lit -> bigint_literal lit + | MemberPattern.PropertyIdentifier id -> identifier id + in + node + ?comments + "MatchMemberPattern" + loc + [("base", member_base base); ("property", member_property property)] + in + member mem + | ObjectPattern { ObjectPattern.properties; rest; comments } -> + let property_key key = + match key with + | ObjectPattern.Property.StringLiteral lit -> string_literal lit + | ObjectPattern.Property.NumberLiteral lit -> number_literal lit + | ObjectPattern.Property.BigIntLiteral lit -> bigint_literal lit + | ObjectPattern.Property.Identifier id -> identifier id + in + let property = function + | ( loc, + ObjectPattern.Property.Valid + { ObjectPattern.Property.key; pattern; shorthand; comments } + ) -> + node + ?comments + "MatchObjectPatternProperty" + loc + [ + ("key", property_key key); + ("pattern", match_pattern pattern); + ("shorthand", bool shorthand); + ] + | (loc, ObjectPattern.Property.InvalidShorthand id) -> + node + "MatchObjectPatternProperty" + loc + [ + ("key", identifier id); + ("pattern", match_identifier_pattern id); + ("shorthand", bool true); + ] + in + + node + ?comments:(format_internal_comments comments) + "MatchObjectPattern" + loc + [ + ("properties", array_of_list property properties); + ("rest", option match_rest_pattern rest); + ] + | ArrayPattern { ArrayPattern.elements; rest; comments } -> + node + ?comments:(format_internal_comments comments) + "MatchArrayPattern" + loc + [ + ( "elements", + array_of_list + (fun { ArrayPattern.Element.pattern; _ } -> match_pattern pattern) + elements + ); + ("rest", option match_rest_pattern rest); + ] + | OrPattern { OrPattern.patterns; comments } -> + node ?comments "MatchOrPattern" loc [("patterns", array_of_list match_pattern patterns)] + | AsPattern { AsPattern.pattern; target; comments } -> + let target = + match target with + | AsPattern.Binding (loc, binding) -> match_binding_pattern (loc, binding) + | AsPattern.Identifier id -> identifier id + in + node ?comments "MatchAsPattern" loc [("pattern", match_pattern pattern); ("target", target)] + and match_identifier_pattern id = + let (loc, _) = id in + node "MatchIdentifierPattern" loc [("id", identifier id)] + and match_binding_pattern (loc, { MatchPattern.BindingPattern.kind; id; comments }) = + let kind = Flow_ast_utils.string_of_variable_kind kind in + node ?comments "MatchBindingPattern" loc [("id", identifier id); ("kind", string kind)] + and match_rest_pattern (loc, { MatchPattern.RestPattern.argument; comments }) = + node ?comments "MatchRestPattern" loc [("argument", option match_binding_pattern argument)] + and function_declaration + ( loc, + { + Function.id; + params = (_, { Function.Params.comments = params_comments; _ }) as params; + async; + generator; + effect_; + predicate = predicate_; + tparams; + return; + body; + comments = func_comments; + sig_loc = _; + } + ) = + let body = + match body with + | Function.BodyBlock b -> b + | Function.BodyExpression _ -> failwith "Unexpected FunctionDeclaration with BodyExpression" + in + let comments = + Flow_ast_utils.merge_comments + ~outer:func_comments + ~inner:(format_internal_comments params_comments) + in + let (node_name, nonhook_attrs) = + if effect_ = Function.Hook then + ("HookDeclaration", []) + else + ( "FunctionDeclaration", + [ + ("async", bool async); + ("generator", bool generator); + ("predicate", option predicate predicate_); + ("expression", bool false); + ] + ) + in + node + ?comments + node_name + loc + ([ + (* estree hasn't come around to the idea that function decls can have + optional ids, but acorn, babel, espree and esprima all have, so let's + do it too. see https://github.com/estree/estree/issues/98 *) + ("id", option identifier id); + ("params", function_params params); + ("body", block body); + ("returnType", function_return_type return); + ("typeParameters", option type_parameter_declaration tparams); + ] + @ nonhook_attrs + ) + and function_expression + ( loc, + { + Function.id; + params = (_, { Function.Params.comments = params_comments; _ }) as params; + async; + generator; + effect_ = _; + predicate = predicate_; + tparams; + return; + body; + comments = func_comments; + sig_loc = _; + } + ) = + let body = + match body with + | Function.BodyBlock b -> b + | Function.BodyExpression _ -> failwith "Unexpected FunctionExpression with BodyExpression" + in + let comments = + Flow_ast_utils.merge_comments + ~outer:func_comments + ~inner:(format_internal_comments params_comments) + in + node + ?comments + "FunctionExpression" + loc + [ + ("id", option identifier id); + ("params", function_params params); + ("body", block body); + ("async", bool async); + ("generator", bool generator); + ("predicate", option predicate predicate_); + ("expression", bool false); + ("returnType", function_return_type return); + ("typeParameters", option type_parameter_declaration tparams); + ] + and identifier (loc, { Identifier.name; comments }) = + node + "Identifier" + ?comments + loc + [("name", string name); ("typeAnnotation", null); ("optional", bool false)] + and private_identifier (loc, { PrivateName.name; comments }) = + node + ?comments + "PrivateIdentifier" + loc + [("name", string name); ("typeAnnotation", null); ("optional", bool false)] + and pattern_identifier + loc { Pattern.Identifier.name = (_, { Identifier.name; comments }); annot; optional } = + node + ?comments + "Identifier" + loc + [ + ("name", string name); + ("typeAnnotation", hint type_annotation annot); + ("optional", bool optional); + ] + and arg_list (_loc, { Expression.ArgList.arguments; comments = _ }) = + (* ESTree does not have a unique node for argument lists, so there's nowhere to + include the loc. *) + array_of_list expression_or_spread arguments + and case (loc, { Statement.Switch.Case.test; consequent; comments }) = + node + ?comments + "SwitchCase" + loc + [("test", option expression test); ("consequent", array_of_list statement consequent)] + and catch (loc, { Statement.Try.CatchClause.param; body; comments }) = + node ?comments "CatchClause" loc [("param", option pattern param); ("body", block body)] + and block (loc, { Statement.Block.body; comments }) = + node + ?comments:(format_internal_comments comments) + "BlockStatement" + loc + [("body", statement_list body)] + and declare_variable (loc, { Statement.DeclareVariable.id; annot; kind; comments }) = + let id_loc = Loc.btwn (fst id) (fst annot) in + let kind = Flow_ast_utils.string_of_variable_kind kind in + node + ?comments + "DeclareVariable" + loc + [ + ( "id", + pattern_identifier + id_loc + { Pattern.Identifier.name = id; annot = Ast.Type.Available annot; optional = false } + ); + ("kind", string kind); + ] + and declare_function + (loc, { Statement.DeclareFunction.id; annot; predicate = predicate_; comments }) = + let id_loc = Loc.btwn (fst id) (fst annot) in + let (name, predicate) = + match annot with + | (_, (_, Type.Function { Type.Function.effect_ = Function.Hook; _ })) -> ("DeclareHook", []) + | _ -> ("DeclareFunction", [("predicate", option predicate predicate_)]) + in + node + ?comments + name + loc + ([ + ( "id", + pattern_identifier + id_loc + { Pattern.Identifier.name = id; annot = Ast.Type.Available annot; optional = false } + ); + ] + @ predicate + ) + and declare_class + (loc, { Statement.DeclareClass.id; tparams; body; extends; implements; mixins; comments }) = + (* TODO: extends shouldn't return an array *) + let extends = + match extends with + | Some extends -> array [interface_extends extends] + | None -> array [] + in + let implements = + match implements with + | Some (_, { Class.Implements.interfaces; comments = _ }) -> + array_of_list class_implements interfaces + | None -> array [] + in + node + ?comments + "DeclareClass" + loc + [ + ("id", identifier id); + ("typeParameters", option type_parameter_declaration tparams); + ("body", object_type ~include_inexact:false body); + ("extends", extends); + ("implements", implements); + ("mixins", array_of_list interface_extends mixins); + ] + and declare_component (loc, component) = + let { + Statement.DeclareComponent.id; + tparams; + params = (_, { Type.Component.Params.comments = params_comments; _ }) as params; + renders; + comments = component_comments; + } = + component + in + let comments = + Flow_ast_utils.merge_comments + ~outer:component_comments + ~inner:(format_internal_comments params_comments) + in + let (_, { Type.Component.Params.params = param_list; rest; comments = _ }) = params in + node + ?comments + "DeclareComponent" + loc + [ + ("id", identifier id); + ("params", component_type_params param_list); + ("rest", option component_type_rest_param rest); + ("params", component_type_params param_list); + ("rendersType", renders_annotation renders); + ("typeParameters", option type_parameter_declaration tparams); + ] + and component_type (loc, component) = + let { + Type.Component.tparams; + params = (_, { Type.Component.Params.comments = params_comments; _ }) as params; + renders; + comments = component_comments; + } = + component + in + let comments = + Flow_ast_utils.merge_comments + ~outer:component_comments + ~inner:(format_internal_comments params_comments) + in + let (_, { Type.Component.Params.params = param_list; rest; comments = _ }) = params in + node + ?comments + "ComponentTypeAnnotation" + loc + [ + ("params", component_type_params param_list); + ("rest", option component_type_rest_param rest); + ("rendersType", renders_annotation renders); + ("typeParameters", option type_parameter_declaration tparams); + ] + and component_type_params params = + let open Type.Component in + let params = + List.map + (fun (loc, { Param.name; annot; optional }) -> + let (_, annot') = annot in + component_type_param ~optional loc (Some name) annot') + params + in + array params + and component_type_rest_param rest = + let open Type.Component in + let (loc, { RestParam.argument; annot; optional; comments }) = rest in + component_type_param + ?comments + ~optional + loc + (Option.map (fun i -> Statement.ComponentDeclaration.Param.Identifier i) argument) + annot + and component_type_param ?comments ~optional loc name annot = + let name' = + match name with + | Some (Statement.ComponentDeclaration.Param.Identifier id) -> option identifier (Some id) + | Some (Statement.ComponentDeclaration.Param.StringLiteral id) -> + option string_literal (Some id) + | None -> option identifier None + in + node + ?comments + "ComponentTypeParameter" + loc + [("name", name'); ("typeAnnotation", _type annot); ("optional", bool optional)] + and declare_enum (loc, { Statement.EnumDeclaration.id; body; comments }) = + node ?comments "DeclareEnum" loc [("id", identifier id); ("body", enum_body body)] + and declare_interface (loc, { Statement.Interface.id; tparams; body; extends; comments }) = + node + ?comments + "DeclareInterface" + loc + [ + ("id", identifier id); + ("typeParameters", option type_parameter_declaration tparams); + ("body", object_type ~include_inexact:false body); + ("extends", array_of_list interface_extends extends); + ] + and string_of_export_kind = function + | Statement.ExportType -> "type" + | Statement.ExportValue -> "value" + and export_specifiers = + let open Statement.ExportNamedDeclaration in + function + | Some (ExportSpecifiers specifiers) -> array_of_list export_specifier specifiers + | Some (ExportBatchSpecifier (loc, Some name)) -> + array [node "ExportNamespaceSpecifier" loc [("exported", identifier name)]] + | Some (ExportBatchSpecifier (_, None)) -> + (* this should've been handled by callers, since this represents an + ExportAllDeclaration, not a specifier. *) + array [] + | None -> array [] + and declare_type_alias (loc, { Statement.TypeAlias.id; tparams; right; comments }) = + node + ?comments + "DeclareTypeAlias" + loc + [ + ("id", identifier id); + ("typeParameters", option type_parameter_declaration tparams); + ("right", _type right); + ] + and type_alias (loc, { Statement.TypeAlias.id; tparams; right; comments }) = + node + ?comments + "TypeAlias" + loc + [ + ("id", identifier id); + ("typeParameters", option type_parameter_declaration tparams); + ("right", _type right); + ] + and opaque_type + ~declare (loc, { Statement.OpaqueType.id; tparams; impltype; supertype; comments }) = + let name = + if declare then + "DeclareOpaqueType" + else + "OpaqueType" + in + node + ?comments + name + loc + [ + ("id", identifier id); + ("typeParameters", option type_parameter_declaration tparams); + ("impltype", option _type impltype); + ("supertype", option _type supertype); + ] + and class_declaration ast = class_helper "ClassDeclaration" ast + and class_expression ast = class_helper "ClassExpression" ast + and class_helper + node_type (loc, { Class.id; extends; body; tparams; implements; class_decorators; comments }) + = + let (super, super_targs, comments) = + match extends with + | Some (_, { Class.Extends.expr; targs; comments = extends_comments }) -> + (Some expr, targs, Flow_ast_utils.merge_comments ~outer:comments ~inner:extends_comments) + | None -> (None, None, comments) + in + let (implements, comments) = + match implements with + | Some (_, { Class.Implements.interfaces; comments = implements_comments }) -> + ( array_of_list class_implements interfaces, + Flow_ast_utils.merge_comments ~outer:comments ~inner:implements_comments + ) + | None -> (array [], comments) + in + node + ?comments + node_type + loc + [ + (* estree hasn't come around to the idea that class decls can have + optional ids, but acorn, babel, espree and esprima all have, so let's + do it too. see https://github.com/estree/estree/issues/98 *) + ("id", option identifier id); + ("body", class_body body); + ("typeParameters", option type_parameter_declaration tparams); + ("superClass", option expression super); + ("superTypeParameters", option type_args super_targs); + ("implements", implements); + ("decorators", array_of_list class_decorator class_decorators); + ] + and class_decorator (loc, { Class.Decorator.expression = expr; comments }) = + node ?comments "Decorator" loc [("expression", expression expr)] + and class_implements (loc, { Class.Implements.Interface.id; targs }) = + node "ClassImplements" loc [("id", identifier id); ("typeParameters", option type_args targs)] + and class_body (loc, { Class.Body.body; comments }) = + node ?comments "ClassBody" loc [("body", array_of_list class_element body)] + and class_element = + Class.Body.( + function + | Method m -> class_method m + | PrivateField p -> class_private_field p + | Property p -> class_property p + ) + and class_method (loc, { Class.Method.key; value; kind; static; decorators; comments }) = + let (key, computed, comments) = + let open Expression.Object.Property in + match key with + | StringLiteral lit -> (string_literal lit, false, comments) + | NumberLiteral lit -> (number_literal lit, false, comments) + | BigIntLiteral lit -> (bigint_literal lit, false, comments) + | Identifier id -> (identifier id, false, comments) + | PrivateName name -> (private_identifier name, false, comments) + | Computed (_, { ComputedKey.expression = expr; comments = computed_comments }) -> + ( expression expr, + true, + Flow_ast_utils.merge_comments ~outer:comments ~inner:computed_comments + ) + in + let kind = + Class.Method.( + match kind with + | Constructor -> "constructor" + | Method -> "method" + | Get -> "get" + | Set -> "set" + ) + in + node + ?comments + "MethodDefinition" + loc + [ + ("key", key); + ("value", function_expression value); + ("kind", string kind); + ("static", bool static); + ("computed", bool computed); + ("decorators", array_of_list class_decorator decorators); + ] + and class_private_field + ( loc, + { + Class.PrivateField.key; + value; + annot; + static; + variance = variance_; + decorators; + comments; + } + ) = + let (value, declare) = + match value with + | Class.Property.Declared -> (None, true) + | Class.Property.Uninitialized -> (None, false) + | Class.Property.Initialized x -> (Some x, false) + in + let props = + [ + ("key", private_identifier key); + ("value", option expression value); + ("typeAnnotation", hint type_annotation annot); + ("computed", bool false); + ("static", bool static); + ("variance", option variance variance_); + ] + @ ( if decorators = [] then + [] + else + [("decorators", array_of_list class_decorator decorators)] + ) + @ + if declare then + [("declare", bool declare)] + else + [] + in + node ?comments "PropertyDefinition" loc props + and class_property + ( loc, + { Class.Property.key; value; annot; static; variance = variance_; decorators; comments } + ) = + let (key, computed, comments) = + match key with + | Expression.Object.Property.StringLiteral lit -> (string_literal lit, false, comments) + | Expression.Object.Property.NumberLiteral lit -> (number_literal lit, false, comments) + | Expression.Object.Property.BigIntLiteral lit -> (bigint_literal lit, false, comments) + | Expression.Object.Property.Identifier id -> (identifier id, false, comments) + | Expression.Object.Property.PrivateName _ -> + failwith "Internal Error: Private name found in class prop" + | Expression.Object.Property.Computed + (_, { ComputedKey.expression = expr; comments = key_comments }) -> + (expression expr, true, Flow_ast_utils.merge_comments ~outer:comments ~inner:key_comments) + in + let (value, declare) = + match value with + | Class.Property.Declared -> (None, true) + | Class.Property.Uninitialized -> (None, false) + | Class.Property.Initialized x -> (Some x, false) + in + let props = + [ + ("key", key); + ("value", option expression value); + ("typeAnnotation", hint type_annotation annot); + ("computed", bool computed); + ("static", bool static); + ("variance", option variance variance_); + ] + @ ( if decorators = [] then + [] + else + [("decorators", array_of_list class_decorator decorators)] + ) + @ + if declare then + [("declare", bool declare)] + else + [] + in + node ?comments "PropertyDefinition" loc props + and component_declaration (loc, component) = + let open Statement.ComponentDeclaration in + let { + id; + tparams; + params = (_, { Params.comments = params_comments; _ }) as params; + body; + renders; + comments = component_comments; + sig_loc = _; + } = + component + in + let comments = + Flow_ast_utils.merge_comments + ~outer:component_comments + ~inner:(format_internal_comments params_comments) + in + node + ?comments + "ComponentDeclaration" + loc + [ + ("body", block body); + ("id", identifier id); + ("params", component_params params); + ("rendersType", renders_annotation renders); + ("typeParameters", option type_parameter_declaration tparams); + ] + and component_params = + let open Statement.ComponentDeclaration.Params in + function + | ( _, + { + params; + rest = Some (rest_loc, { Statement.ComponentDeclaration.RestParam.argument; comments }); + comments = _; + } + ) -> + let rest = node ?comments "RestElement" rest_loc [("argument", pattern argument)] in + let rev_params = List.rev_map component_param params in + let params = List.rev (rest :: rev_params) in + array params + | (_, { params; rest = None; comments = _ }) -> + let params = List.map component_param params in + array params + and component_param param = + let open Statement.ComponentDeclaration.Param in + let (loc, { name; local; default; shorthand }) = param in + let name' = + match name with + | Identifier id -> identifier id + | StringLiteral id -> string_literal id + in + let local' = + match default with + | Some default -> + node "AssignmentPattern" loc [("left", pattern local); ("right", expression default)] + | None -> pattern local + in + node + "ComponentParameter" + loc + [("name", name'); ("local", local'); ("shorthand", bool shorthand)] + and enum_body body = + let open Statement.EnumDeclaration in + match body with + | (loc, BooleanBody { BooleanBody.members; explicit_type; has_unknown_members; comments }) -> + node + ?comments:(format_internal_comments comments) + "EnumBooleanBody" + loc + [ + ( "members", + array_of_list + (fun (loc, { InitializedMember.id; init }) -> + node + "EnumBooleanMember" + loc + [("id", identifier id); ("init", boolean_literal init)]) + members + ); + ("explicitType", bool explicit_type); + ("hasUnknownMembers", bool has_unknown_members); + ] + | (loc, NumberBody { NumberBody.members; explicit_type; has_unknown_members; comments }) -> + node + ?comments:(format_internal_comments comments) + "EnumNumberBody" + loc + [ + ( "members", + array_of_list + (fun (loc, { InitializedMember.id; init }) -> + node "EnumNumberMember" loc [("id", identifier id); ("init", number_literal init)]) + members + ); + ("explicitType", bool explicit_type); + ("hasUnknownMembers", bool has_unknown_members); + ] + | (loc, StringBody { StringBody.members; explicit_type; has_unknown_members; comments }) -> + let members = + match members with + | StringBody.Defaulted defaulted_members -> + List.map + (fun (loc, { DefaultedMember.id }) -> + node "EnumDefaultedMember" loc [("id", identifier id)]) + defaulted_members + | StringBody.Initialized initialized_members -> + List.map + (fun (loc, { InitializedMember.id; init }) -> + node "EnumStringMember" loc [("id", identifier id); ("init", string_literal init)]) + initialized_members + in + node + ?comments:(format_internal_comments comments) + "EnumStringBody" + loc + [ + ("members", array members); + ("explicitType", bool explicit_type); + ("hasUnknownMembers", bool has_unknown_members); + ] + | (loc, SymbolBody { SymbolBody.members; has_unknown_members; comments }) -> + node + ?comments:(format_internal_comments comments) + "EnumSymbolBody" + loc + [ + ( "members", + array_of_list + (fun (loc, { DefaultedMember.id }) -> + node "EnumDefaultedMember" loc [("id", identifier id)]) + members + ); + ("hasUnknownMembers", bool has_unknown_members); + ] + | (loc, BigIntBody { BigIntBody.members; explicit_type; has_unknown_members; comments }) -> + node + ?comments:(format_internal_comments comments) + "EnumBigIntBody" + loc + [ + ( "members", + array_of_list + (fun (loc, { InitializedMember.id; init }) -> + node "EnumBigIntMember" loc [("id", identifier id); ("init", bigint_literal init)]) + members + ); + ("explicitType", bool explicit_type); + ("hasUnknownMembers", bool has_unknown_members); + ] + and enum_declaration (loc, { Statement.EnumDeclaration.id; body; comments }) = + node ?comments "EnumDeclaration" loc [("id", identifier id); ("body", enum_body body)] + and interface_declaration (loc, { Statement.Interface.id; tparams; body; extends; comments }) = + node + ?comments + "InterfaceDeclaration" + loc + [ + ("id", identifier id); + ("typeParameters", option type_parameter_declaration tparams); + ("body", object_type ~include_inexact:false body); + ("extends", array_of_list interface_extends extends); + ] + and interface_extends (loc, { Type.Generic.id; targs; comments }) = + let id = + match id with + | Type.Generic.Identifier.Unqualified id -> identifier id + | Type.Generic.Identifier.Qualified q -> generic_type_qualified_identifier q + in + node ?comments "InterfaceExtends" loc [("id", id); ("typeParameters", option type_args targs)] + and pattern = + Pattern.( + function + | (loc, Object { Object.properties; annot; comments }) -> + node + ?comments:(format_internal_comments comments) + "ObjectPattern" + loc + [ + ("properties", array_of_list object_pattern_property properties); + ("typeAnnotation", hint type_annotation annot); + ] + | (loc, Array { Array.elements; annot; comments }) -> + node + ?comments:(format_internal_comments comments) + "ArrayPattern" + loc + [ + ("elements", array_of_list array_pattern_element elements); + ("typeAnnotation", hint type_annotation annot); + ] + | (loc, Identifier pattern_id) -> pattern_identifier loc pattern_id + | (_loc, Expression expr) -> expression expr + ) + and function_param (loc, { Ast.Function.Param.argument; default }) = + match default with + | Some default -> + node "AssignmentPattern" loc [("left", pattern argument); ("right", expression default)] + | None -> pattern argument + and this_param (loc, { Function.ThisParam.annot; comments }) = + node + ?comments + "Identifier" + loc + [("name", string "this"); ("typeAnnotation", type_annotation annot)] + and function_params = + let open Ast.Function.Params in + function + | ( _, + { + params; + rest = Some (rest_loc, { Function.RestParam.argument; comments }); + comments = _; + this_; + } + ) -> + let rest = node ?comments "RestElement" rest_loc [("argument", pattern argument)] in + let rev_params = List.rev_map function_param params in + let params = List.rev (rest :: rev_params) in + let params = + match this_ with + | Some this -> this_param this :: params + | None -> params + in + array params + | (_, { params; rest = None; this_; comments = _ }) -> + let params = List.map function_param params in + let params = + match this_ with + | Some this -> this_param this :: params + | None -> params + in + array params + and rest_element loc { Pattern.RestElement.argument; comments } = + node ?comments "RestElement" loc [("argument", pattern argument)] + and array_pattern_element = + let open Pattern.Array in + function + | Hole _ -> null + | Element (loc, { Element.argument; default = Some default }) -> + node "AssignmentPattern" loc [("left", pattern argument); ("right", expression default)] + | Element (_loc, { Element.argument; default = None }) -> pattern argument + | RestElement (loc, el) -> rest_element loc el + and function_return_type = function + | Ast.Function.ReturnAnnot.Missing _ -> null + | Ast.Function.ReturnAnnot.TypeGuard (loc, g) -> type_guard_annotation (loc, g) + | Ast.Function.ReturnAnnot.Available t -> type_annotation t + and object_property = + let open Expression.Object in + function + | Property (loc, prop) -> + Property.( + let (key, value, kind, method_, shorthand, comments) = + match prop with + | Init { key; value; shorthand } -> + (key, expression value, "init", false, shorthand, None) + | Method { key; value = (loc, func) } -> + (key, function_expression (loc, func), "init", true, false, None) + | Get { key; value = (loc, func); comments } -> + (key, function_expression (loc, func), "get", false, false, comments) + | Set { key; value = (loc, func); comments } -> + (key, function_expression (loc, func), "set", false, false, comments) + in + let (key, computed, comments) = + match key with + | StringLiteral lit -> (string_literal lit, false, comments) + | NumberLiteral lit -> (number_literal lit, false, comments) + | BigIntLiteral lit -> (bigint_literal lit, false, comments) + | Identifier id -> (identifier id, false, comments) + | PrivateName _ -> failwith "Internal Error: Found private field in object props" + | Computed (_, { ComputedKey.expression = expr; comments = key_comments }) -> + ( expression expr, + true, + Flow_ast_utils.merge_comments ~outer:comments ~inner:key_comments + ) + in + node + ?comments + "Property" + loc + [ + ("key", key); + ("value", value); + ("kind", string kind); + ("method", bool method_); + ("shorthand", bool shorthand); + ("computed", bool computed); + ] + ) + | SpreadProperty (loc, { SpreadProperty.argument; comments }) -> + node ?comments "SpreadElement" loc [("argument", expression argument)] + and object_pattern_property = + let open Pattern.Object in + function + | Property (loc, { Property.key; pattern = patt; default; shorthand }) -> + let (key, computed, comments) = + match key with + | Property.StringLiteral lit -> (string_literal lit, false, None) + | Property.NumberLiteral lit -> (number_literal lit, false, None) + | Property.BigIntLiteral lit -> (bigint_literal lit, false, None) + | Property.Identifier id -> (identifier id, false, None) + | Property.Computed (_, { ComputedKey.expression = expr; comments }) -> + (expression expr, true, comments) + in + let value = + match default with + | Some default -> + let loc = Loc.btwn (fst patt) (fst default) in + node "AssignmentPattern" loc [("left", pattern patt); ("right", expression default)] + | None -> pattern patt + in + node + ?comments + "Property" + loc + [ + ("key", key); + ("value", value); + ("kind", string "init"); + ("method", bool false); + ("shorthand", bool shorthand); + ("computed", bool computed); + ] + | RestElement (loc, el) -> rest_element loc el + and spread_element (loc, { Expression.SpreadElement.argument; comments }) = + node ?comments "SpreadElement" loc [("argument", expression argument)] + and expression_or_spread = + let open Expression in + function + | Expression expr -> expression expr + | Spread spread -> spread_element spread + and array_element = + let open Expression.Array in + function + | Hole _ -> null + | Expression expr -> expression expr + | Spread spread -> spread_element spread + and number_literal (loc, { NumberLiteral.value; raw; comments }) = + node ?comments "Literal" loc [("value", number value); ("raw", string raw)] + and bigint_literal (loc, { BigIntLiteral.value; raw; comments }) = + (* https://github.com/estree/estree/blob/master/es2020.md#bigintliteral + * `bigint` property is the string representation of the `BigInt` value. + * It must contain only decimal digits and not include numeric separators `_` or the suffix `n`. + *) + let bigint = + match value with + | Some value -> Int64.to_string value + | None -> + String.sub raw 0 (String.length raw - 1) |> String.split_on_char '_' |> String.concat "" + in + node ?comments "Literal" loc [("value", null); ("bigint", string bigint); ("raw", string raw)] + and string_literal (loc, { StringLiteral.value; raw; comments }) = + node ?comments "Literal" loc [("value", string value); ("raw", string raw)] + and boolean_literal (loc, { BooleanLiteral.value; comments }) = + let raw = + if value then + "true" + else + "false" + in + node ?comments "Literal" loc [("value", bool value); ("raw", string raw)] + and regexp_literal (loc, { RegExpLiteral.pattern; flags; raw; comments; _ }) = + let value = regexp loc pattern flags in + let regex = obj [("pattern", string pattern); ("flags", string flags)] in + node ?comments "Literal" loc [("value", value); ("raw", string raw); ("regex", regex)] + and null_literal (loc, comments) = + node ?comments "Literal" loc [("value", null); ("raw", string "null")] + and module_ref_literal (loc, { ModuleRefLiteral.value; raw; comments; _ }) = + string_literal (loc, { StringLiteral.value; raw; comments }) + and template_literal (loc, { Expression.TemplateLiteral.quasis; expressions; comments }) = + node + ?comments + "TemplateLiteral" + loc + [ + ("quasis", array_of_list template_element quasis); + ("expressions", array_of_list expression expressions); + ] + and template_element + ( loc, + { + Expression.TemplateLiteral.Element.value = + { Expression.TemplateLiteral.Element.raw; cooked }; + tail; + } + ) = + let value = obj [("raw", string raw); ("cooked", string cooked)] in + node "TemplateElement" loc [("value", value); ("tail", bool tail)] + and tagged_template (loc, { Expression.TaggedTemplate.tag; quasi; comments }) = + node + ?comments + "TaggedTemplateExpression" + loc + [("tag", expression tag); ("quasi", template_literal quasi)] + and variable_declaration (loc, { Statement.VariableDeclaration.kind; declarations; comments }) = + let kind = Flow_ast_utils.string_of_variable_kind kind in + node + ?comments + "VariableDeclaration" + loc + [("declarations", array_of_list variable_declarator declarations); ("kind", string kind)] + and variable_declarator (loc, { Statement.VariableDeclaration.Declarator.id; init }) = + node "VariableDeclarator" loc [("id", pattern id); ("init", option expression init)] + and variance (loc, { Variance.kind; comments }) = + let open Variance in + let kind_str = + match kind with + | Plus -> "plus" + | Minus -> "minus" + | Readonly -> "readonly" + | In -> "in" + | Out -> "out" + | InOut -> "in-out" + in + node ?comments "Variance" loc [("kind", string kind_str)] + and _type (loc, t) = + Type.( + match t with + | Any comments -> any_type loc comments + | Mixed comments -> mixed_type loc comments + | Empty comments -> empty_type loc comments + | Void comments -> void_type loc comments + | Null comments -> null_type loc comments + | Symbol comments -> symbol_type loc comments + | Number comments -> number_type loc comments + | BigInt comments -> bigint_type loc comments + | String comments -> string_type loc comments + | Boolean { raw = _; comments } -> boolean_type loc comments + | Nullable t -> nullable_type loc t + | Function fn -> function_type (loc, fn) + | Component c -> component_type (loc, c) + | Object o -> object_type ~include_inexact:true (loc, o) + | Interface i -> interface_type (loc, i) + | Array t -> array_type loc t + | Conditional t -> conditional_type loc t + | Infer t -> infer_type loc t + | Generic g -> generic_type (loc, g) + | IndexedAccess ia -> indexed_access (loc, ia) + | OptionalIndexedAccess ia -> optional_indexed_access (loc, ia) + | Union t -> union_type (loc, t) + | Intersection t -> intersection_type (loc, t) + | Typeof t -> typeof_type (loc, t) + | Keyof t -> keyof_type (loc, t) + | Renders renders -> render_type loc renders + | ReadOnly t -> read_only_type (loc, t) + | Tuple t -> tuple_type (loc, t) + | StringLiteral s -> string_literal_type (loc, s) + | NumberLiteral n -> number_literal_type (loc, n) + | BigIntLiteral n -> bigint_literal_type (loc, n) + | BooleanLiteral b -> boolean_literal_type (loc, b) + | Exists comments -> exists_type loc comments + | Unknown comments -> unknown_type loc comments + | Never comments -> never_type loc comments + | Undefined comments -> undefined_type loc comments + ) + and any_type loc comments = node ?comments "AnyTypeAnnotation" loc [] + and mixed_type loc comments = node ?comments "MixedTypeAnnotation" loc [] + and empty_type loc comments = node ?comments "EmptyTypeAnnotation" loc [] + and void_type loc comments = node ?comments "VoidTypeAnnotation" loc [] + and null_type loc comments = node ?comments "NullLiteralTypeAnnotation" loc [] + and symbol_type loc comments = node ?comments "SymbolTypeAnnotation" loc [] + and number_type loc comments = node ?comments "NumberTypeAnnotation" loc [] + and bigint_type loc comments = node ?comments "BigIntTypeAnnotation" loc [] + and string_type loc comments = node ?comments "StringTypeAnnotation" loc [] + and boolean_type loc comments = node ?comments "BooleanTypeAnnotation" loc [] + and nullable_type loc { Type.Nullable.argument; comments } = + node ?comments "NullableTypeAnnotation" loc [("typeAnnotation", _type argument)] + and unknown_type loc comments = node ?comments "UnknownTypeAnnotation" loc [] + and never_type loc comments = node ?comments "NeverTypeAnnotation" loc [] + and undefined_type loc comments = node ?comments "UndefinedTypeAnnotation" loc [] + and return_annotation = function + | Ast.Type.Function.TypeAnnotation t -> _type t + | Ast.Type.Function.TypeGuard g -> type_guard g + and type_guard (loc, { Ast.Type.TypeGuard.kind; guard = (x, t); comments }) = + let kind = + let open Ast.Type.TypeGuard in + match kind with + | Default -> null + | Asserts -> string "asserts" + | Implies -> string "implies" + in + node + ?comments:(format_internal_comments comments) + "TypePredicate" + loc + [("parameterName", identifier x); ("typeAnnotation", option _type t); ("kind", kind)] + and function_type + ( loc, + { + Type.Function.params = + (_, { Type.Function.Params.this_; params; rest; comments = params_comments }); + return; + tparams; + effect_; + comments = func_comments; + } + ) = + let comments = + Flow_ast_utils.merge_comments + ~inner:(format_internal_comments params_comments) + ~outer:func_comments + in + let name = + if effect_ = Function.Hook then + "HookTypeAnnotation" + else + "FunctionTypeAnnotation" + in + node + ?comments + name + loc + ([ + ("params", array_of_list function_type_param params); + ("returnType", return_annotation return); + ("rest", option function_type_rest rest); + ("typeParameters", option type_parameter_declaration tparams); + ] + @ + if effect_ = Function.Hook then + [] + else + [("this", option function_type_this_constraint this_)] + ) + and function_type_param ?comments (loc, { Type.Function.Param.name; annot; optional }) = + node + ?comments + "FunctionTypeParam" + loc + [ + ("name", option identifier name); + ("typeAnnotation", _type annot); + ("optional", bool optional); + ] + and function_type_rest (_loc, { Type.Function.RestParam.argument; comments }) = + (* TODO: add a node for the rest param itself, including the `...`, + like we do with RestElement on normal functions. This should be + coordinated with Babel, ast-types, etc. so keeping the status quo for + now. Here's an example: *) + (* node "FunctionTypeRestParam" loc [ + "argument", function_type_param argument; + ] *) + function_type_param ?comments argument + and function_type_this_constraint (loc, { Type.Function.ThisParam.annot = (_, annot); comments }) + = + node + ?comments + "FunctionTypeParam" + loc + [ + ("name", option identifier None); ("typeAnnotation", _type annot); ("optional", bool false); + ] + and object_type ~include_inexact (loc, { Type.Object.properties; exact; inexact; comments }) = + Type.Object.( + let (props, ixs, calls, slots) = + List.fold_left + (fun (props, ixs, calls, slots) -> function + | Property p -> + let prop = object_type_property p in + (prop :: props, ixs, calls, slots) + | SpreadProperty p -> + let prop = object_type_spread_property p in + (prop :: props, ixs, calls, slots) + | Indexer i -> + let ix = object_type_indexer i in + (props, ix :: ixs, calls, slots) + | CallProperty c -> + let call = object_type_call_property c in + (props, ixs, call :: calls, slots) + | InternalSlot s -> + let slot = object_type_internal_slot s in + (props, ixs, calls, slot :: slots) + | MappedType m -> + let mapped_type = object_type_mapped_type m in + (mapped_type :: props, ixs, calls, slots)) + ([], [], [], []) + properties + in + let fields = + [ + ("exact", bool exact); + ("properties", array (List.rev props)); + ("indexers", array (List.rev ixs)); + ("callProperties", array (List.rev calls)); + ("internalSlots", array (List.rev slots)); + ] + in + let fields = + if include_inexact then + ("inexact", bool inexact) :: fields + else + fields + in + node ?comments:(format_internal_comments comments) "ObjectTypeAnnotation" loc fields + ) + and object_type_property + ( loc, + { + Type.Object.Property.key; + value; + optional; + static; + proto; + variance = variance_; + _method; + comments; + } + ) = + let key = + match key with + | Expression.Object.Property.StringLiteral lit -> string_literal lit + | Expression.Object.Property.NumberLiteral lit -> number_literal lit + | Expression.Object.Property.BigIntLiteral lit -> bigint_literal lit + | Expression.Object.Property.Identifier id -> identifier id + | Expression.Object.Property.PrivateName _ -> + failwith "Internal Error: Found private field in object props" + | Expression.Object.Property.Computed _ -> + failwith "There should not be computed object type property keys" + in + let (value, kind) = + match value with + | Type.Object.Property.Init value -> (_type value, "init") + | Type.Object.Property.Get (loc, f) -> (function_type (loc, f), "get") + | Type.Object.Property.Set (loc, f) -> (function_type (loc, f), "set") + in + node + ?comments + "ObjectTypeProperty" + loc + [ + ("key", key); + ("value", value); + ("method", bool _method); + ("optional", bool optional); + ("static", bool static); + ("proto", bool proto); + ("variance", option variance variance_); + ("kind", string kind); + ] + and object_type_spread_property (loc, { Type.Object.SpreadProperty.argument; comments }) = + node ?comments "ObjectTypeSpreadProperty" loc [("argument", _type argument)] + and object_type_indexer + (loc, { Type.Object.Indexer.id; key; value; static; variance = variance_; comments }) = + node + ?comments + "ObjectTypeIndexer" + loc + [ + ("id", option identifier id); + ("key", _type key); + ("value", _type value); + ("static", bool static); + ("variance", option variance variance_); + ] + and object_type_call_property (loc, { Type.Object.CallProperty.value; static; comments }) = + node + ?comments + "ObjectTypeCallProperty" + loc + [("value", function_type value); ("static", bool static)] + and object_type_mapped_type + ( mt_loc, + { + Type.Object.MappedType.key_tparam; + prop_type; + source_type; + variance = variance_; + comments; + optional; + } + ) = + let optional_flag flag = + Type.Object.MappedType.( + match flag with + | PlusOptional -> string "PlusOptional" + | MinusOptional -> string "MinusOptional" + | Optional -> string "Optional" + | NoOptionalFlag -> null + ) + in + node + ?comments + "ObjectTypeMappedTypeProperty" + mt_loc + [ + ("keyTparam", type_param key_tparam); + ("propType", _type prop_type); + ("sourceType", _type source_type); + ("variance", option variance variance_); + ("optional", optional_flag optional); + ] + and object_type_internal_slot + (loc, { Type.Object.InternalSlot.id; optional; static; _method; value; comments }) = + node + ?comments + "ObjectTypeInternalSlot" + loc + [ + ("id", identifier id); + ("optional", bool optional); + ("static", bool static); + ("method", bool _method); + ("value", _type value); + ] + and interface_type (loc, { Type.Interface.extends; body; comments }) = + node + ?comments + "InterfaceTypeAnnotation" + loc + [ + ("extends", array_of_list interface_extends extends); + ("body", object_type ~include_inexact:false body); + ] + and array_type loc { Type.Array.argument; comments } = + node ?comments "ArrayTypeAnnotation" loc [("elementType", _type argument)] + and conditional_type + loc { Type.Conditional.check_type; extends_type; true_type; false_type; comments } = + node + ?comments + "ConditionalTypeAnnotation" + loc + [ + ("checkType", _type check_type); + ("extendsType", _type extends_type); + ("trueType", _type true_type); + ("falseType", _type false_type); + ] + and infer_type loc { Type.Infer.tparam; comments } = + node ?comments "InferTypeAnnotation" loc [("typeParameter", type_param tparam)] + and generic_type_qualified_identifier (loc, { Type.Generic.Identifier.id; qualification }) = + let qualification = + match qualification with + | Type.Generic.Identifier.Unqualified id -> identifier id + | Type.Generic.Identifier.Qualified q -> generic_type_qualified_identifier q + in + node "QualifiedTypeIdentifier" loc [("qualification", qualification); ("id", identifier id)] + and generic_type (loc, { Type.Generic.id; targs; comments }) = + let id = + match id with + | Type.Generic.Identifier.Unqualified id -> identifier id + | Type.Generic.Identifier.Qualified q -> generic_type_qualified_identifier q + in + node + ?comments + "GenericTypeAnnotation" + loc + [("id", id); ("typeParameters", option type_args targs)] + and indexed_access_properties { Type.IndexedAccess._object; index; comments = _ } = + [("objectType", _type _object); ("indexType", _type index)] + and indexed_access (loc, ({ Type.IndexedAccess.comments; _ } as ia)) = + node ?comments "IndexedAccessType" loc (indexed_access_properties ia) + and optional_indexed_access + ( loc, + { + Type.OptionalIndexedAccess.indexed_access = + { Type.IndexedAccess.comments; _ } as indexed_access; + optional; + } + ) = + node + ?comments + "OptionalIndexedAccessType" + loc + (indexed_access_properties indexed_access @ [("optional", bool optional)]) + and union_type (loc, { Type.Union.types = (t0, t1, ts); comments }) = + node ?comments "UnionTypeAnnotation" loc [("types", array_of_list _type (t0 :: t1 :: ts))] + and intersection_type (loc, { Type.Intersection.types = (t0, t1, ts); comments }) = + node + ?comments + "IntersectionTypeAnnotation" + loc + [("types", array_of_list _type (t0 :: t1 :: ts))] + and typeof_type (loc, { Type.Typeof.argument; targs; comments }) = + let targs_field = + match targs with + | None -> [] + | Some targs -> [("typeArguments", type_args targs)] + in + node ?comments "TypeofTypeAnnotation" loc (("argument", typeof_expr argument) :: targs_field) + and typeof_expr id = + match id with + | Type.Typeof.Target.Unqualified id -> identifier id + | Type.Typeof.Target.Qualified q -> typeof_qualifier q + and typeof_qualifier (loc, { Type.Typeof.Target.id; qualification }) = + let qualification = typeof_expr qualification in + node "QualifiedTypeofIdentifier" loc [("qualification", qualification); ("id", identifier id)] + and keyof_type (loc, { Type.Keyof.argument; comments }) = + node ?comments "KeyofTypeAnnotation" loc [("argument", _type argument)] + and renders_annotation = function + | Ast.Type.AvailableRenders (loc, v) -> render_type loc v + | Ast.Type.MissingRenders _ -> null + and render_type loc { Type.Renders.operator_loc = _; comments; variant; argument } = + let operator = + match variant with + | Type.Renders.Normal -> "renders" + | Type.Renders.Maybe -> "renders?" + | Type.Renders.Star -> "renders*" + in + flow_type_operator loc comments operator argument + and flow_type_operator loc comments operator operand = + node + ?comments + "TypeOperator" + loc + [("operator", string operator); ("typeAnnotation", _type operand)] + and read_only_type (loc, { Type.ReadOnly.argument; comments }) = + flow_type_operator loc comments "readonly" argument + and tuple_type (loc, { Type.Tuple.elements; inexact; comments }) = + node + ?comments + "TupleTypeAnnotation" + loc + [ + ( "elementTypes", + array_of_list + (function + | (_, Type.Tuple.UnlabeledElement annot) -> _type annot + | (loc, Type.Tuple.LabeledElement e) -> tuple_labeled_element loc e + | (loc, Type.Tuple.SpreadElement e) -> tuple_spread_element loc e) + elements + ); + ("inexact", bool inexact); + ] + and tuple_labeled_element + ?comments loc { Type.Tuple.LabeledElement.name; annot; variance = variance_; optional } = + node + ?comments + "TupleTypeLabeledElement" + loc + [ + ("label", identifier name); + ("elementType", _type annot); + ("variance", option variance variance_); + ("optional", bool optional); + ] + and tuple_spread_element ?comments loc { Type.Tuple.SpreadElement.name; annot } = + node + ?comments + "TupleTypeSpreadElement" + loc + [("label", option identifier name); ("typeAnnotation", _type annot)] + and string_literal_type (loc, { Ast.StringLiteral.value; raw; comments }) = + node + ?comments + "StringLiteralTypeAnnotation" + loc + [("value", string value); ("raw", string raw)] + and number_literal_type (loc, { Ast.NumberLiteral.value; raw; comments }) = + node + ?comments + "NumberLiteralTypeAnnotation" + loc + [("value", number value); ("raw", string raw)] + and bigint_literal_type (loc, { Ast.BigIntLiteral.raw; comments; _ }) = + node ?comments "BigIntLiteralTypeAnnotation" loc [("value", null); ("raw", string raw)] + and boolean_literal_type (loc, { Ast.BooleanLiteral.value; comments }) = + node + ?comments + "BooleanLiteralTypeAnnotation" + loc + [ + ("value", bool value); + ( "raw", + string + ( if value then + "true" + else + "false" + ) + ); + ] + and exists_type loc comments = node ?comments "ExistsTypeAnnotation" loc [] + and type_annotation (loc, ty) = node "TypeAnnotation" loc [("typeAnnotation", _type ty)] + and type_guard_annotation (loc, (loc1, guard)) = + node "TypeAnnotation" loc [("typeAnnotation", type_guard (loc1, guard))] + and type_parameter_declaration (loc, { Type.TypeParams.params; comments }) = + node + ?comments:(format_internal_comments comments) + "TypeParameterDeclaration" + loc + [("params", array_of_list type_param params)] + and type_param + ( loc, + { + Type.TypeParam.name = (_, { Identifier.name; comments }); + bound; + bound_kind; + variance = tp_var; + default; + const; + } + ) = + node + ?comments + "TypeParameter" + loc + ([ + (* we track the location of the name, but don't expose it here for + backwards-compatibility. TODO: change this? *) + ("name", string name); + ("bound", hint type_annotation bound); + ("const", bool (Option.is_some const)); + ("variance", option variance tp_var); + ("default", option _type default); + ] + @ + match bound_kind with + | Type.TypeParam.Colon -> [] + | Type.TypeParam.Extends -> [("usesExtendsBound", bool true)] + ) + and type_args (loc, { Type.TypeArgs.arguments; comments }) = + node + ?comments:(format_internal_comments comments) + "TypeParameterInstantiation" + loc + [("params", array_of_list _type arguments)] + and call_type_args (loc, { Expression.CallTypeArgs.arguments; comments }) = + node + ?comments:(format_internal_comments comments) + "TypeParameterInstantiation" + loc + [("params", array_of_list call_type_arg arguments)] + and call_type_arg x = + match x with + | Expression.CallTypeArg.Explicit t -> _type t + | Expression.CallTypeArg.Implicit (loc, { Expression.CallTypeArg.Implicit.comments }) -> + generic_type + ( loc, + { + Type.Generic.id = + Type.Generic.Identifier.Unqualified (Flow_ast_utils.ident_of_source (loc, "_")); + targs = None; + comments; + } + ) + and jsx_element + (loc, { JSX.opening_element; closing_element; children = (_loc, children); comments }) = + node + ?comments + "JSXElement" + loc + [ + ("openingElement", jsx_opening opening_element); + ("closingElement", option jsx_closing closing_element); + ("children", array_of_list jsx_child children); + ] + and jsx_fragment + ( loc, + { + JSX.frag_opening_element; + frag_closing_element; + frag_children = (_loc, frag_children); + frag_comments; + } + ) = + node + ?comments:frag_comments + "JSXFragment" + loc + [ + ("openingFragment", jsx_opening_fragment frag_opening_element); + ("children", array_of_list jsx_child frag_children); + ("closingFragment", jsx_closing_fragment frag_closing_element); + ] + and jsx_opening (loc, { JSX.Opening.name; targs; attributes; self_closing }) = + node + "JSXOpeningElement" + loc + ([ + ("name", jsx_name name); + ("attributes", array_of_list jsx_opening_attribute attributes); + ("selfClosing", bool self_closing); + ] + @ + match targs with + | Some targs -> [("typeArguments", call_type_args targs)] + | None -> [] + ) + and jsx_opening_fragment loc = node "JSXOpeningFragment" loc [] + and jsx_opening_attribute = + JSX.Opening.( + function + | Attribute attribute -> jsx_attribute attribute + | SpreadAttribute attribute -> jsx_spread_attribute attribute + ) + and jsx_closing (loc, { JSX.Closing.name }) = + node "JSXClosingElement" loc [("name", jsx_name name)] + and jsx_closing_fragment loc = node "JSXClosingFragment" loc [] + and jsx_child = + JSX.( + function + | (loc, Element element) -> jsx_element (loc, element) + | (loc, Fragment fragment) -> jsx_fragment (loc, fragment) + | (loc, ExpressionContainer expr) -> jsx_expression_container (loc, expr) + | (loc, SpreadChild spread) -> jsx_spread_child (loc, spread) + | (loc, Text str) -> jsx_text (loc, str) + ) + and jsx_name = + JSX.( + function + | Identifier id -> jsx_identifier id + | NamespacedName namespaced_name -> jsx_namespaced_name namespaced_name + | MemberExpression member -> jsx_member_expression member + ) + and jsx_attribute (loc, { JSX.Attribute.name; value }) = + let name = + match name with + | JSX.Attribute.Identifier id -> jsx_identifier id + | JSX.Attribute.NamespacedName namespaced_name -> jsx_namespaced_name namespaced_name + in + node "JSXAttribute" loc [("name", name); ("value", option jsx_attribute_value value)] + and jsx_attribute_value = + JSX.Attribute.( + function + | StringLiteral (loc, value) -> string_literal (loc, value) + | ExpressionContainer (loc, expr) -> jsx_expression_container (loc, expr) + ) + and jsx_spread_attribute (loc, { JSX.SpreadAttribute.argument; comments }) = + node ?comments "JSXSpreadAttribute" loc [("argument", expression argument)] + and jsx_expression_container (loc, { JSX.ExpressionContainer.expression = expr; comments }) = + let expression = + match expr with + | JSX.ExpressionContainer.Expression expr -> expression expr + | JSX.ExpressionContainer.EmptyExpression -> + let empty_loc = + let open Loc in + { + loc with + start = { loc.start with column = loc.start.column + 1 }; + _end = { loc._end with column = loc._end.column - 1 }; + } + in + + node "JSXEmptyExpression" empty_loc [] + in + node + ?comments:(format_internal_comments comments) + "JSXExpressionContainer" + loc + [("expression", expression)] + and jsx_spread_child (loc, { JSX.SpreadChild.expression = expr; comments }) = + node ?comments "JSXSpreadChild" loc [("expression", expression expr)] + and jsx_text (loc, { JSX.Text.value; raw }) = + node "JSXText" loc [("value", string value); ("raw", string raw)] + and jsx_member_expression (loc, { JSX.MemberExpression._object; property }) = + let _object = + match _object with + | JSX.MemberExpression.Identifier id -> jsx_identifier id + | JSX.MemberExpression.MemberExpression member -> jsx_member_expression member + in + node "JSXMemberExpression" loc [("object", _object); ("property", jsx_identifier property)] + and jsx_namespaced_name (loc, { JSX.NamespacedName.namespace; name }) = + node + "JSXNamespacedName" + loc + [("namespace", jsx_identifier namespace); ("name", jsx_identifier name)] + and jsx_identifier (loc, { JSX.Identifier.name; comments }) = + node ?comments "JSXIdentifier" loc [("name", string name)] + and export_specifier + ( loc, + { + Statement.ExportNamedDeclaration.ExportSpecifier.exported; + local; + from_remote = _; + imported_name_def_loc = _; + } + ) = + let exported = + match exported with + | Some exported -> identifier exported + | None -> identifier local + in + node "ExportSpecifier" loc [("local", identifier local); ("exported", exported)] + and import_default_specifier + { Statement.ImportDeclaration.identifier = id; remote_default_name_def_loc = _ } = + node "ImportDefaultSpecifier" (fst id) [("local", identifier id)] + and import_namespace_specifier (loc, id) = + node "ImportNamespaceSpecifier" loc [("local", identifier id)] + and import_named_specifier local_id remote_id kind = + let span_loc = + match local_id with + | Some local_id -> Loc.btwn (fst remote_id) (fst local_id) + | None -> fst remote_id + in + let local_id = + match local_id with + | Some id -> id + | None -> remote_id + in + node + "ImportSpecifier" + span_loc + [ + ("imported", identifier remote_id); + ("local", identifier local_id); + ( "importKind", + match kind with + | Some Statement.ImportDeclaration.ImportType -> string "type" + | Some Statement.ImportDeclaration.ImportTypeof -> string "typeof" + | Some Statement.ImportDeclaration.ImportValue + | None -> + null + ); + ] + and comment_list comments = array_of_list comment comments + and comment (loc, c) = + Comment.( + let (_type, value) = + match c with + | { kind = Line; text = s; _ } -> ("Line", s) + | { kind = Block; text = s; _ } -> ("Block", s) + in + node _type loc [("value", string value)] + ) + and predicate (loc, { Ast.Type.Predicate.kind; comments }) = + let open Ast.Type.Predicate in + let (_type, value) = + match kind with + | Declared e -> ("DeclaredPredicate", [("value", expression e)]) + | Inferred -> ("InferredPredicate", []) + in + node ?comments _type loc value + and call_node_properties { Expression.Call.callee; targs; arguments; comments = _ } = + [ + ("callee", expression callee); + ("typeArguments", option call_type_args targs); + ("arguments", arg_list arguments); + ] + and member_node_properties { Expression.Member._object; property; comments = _ } = + let (property, computed) = + match property with + | Expression.Member.PropertyIdentifier id -> (identifier id, false) + | Expression.Member.PropertyPrivateName name -> (private_identifier name, false) + | Expression.Member.PropertyExpression expr -> (expression expr, true) + in + [("object", expression _object); ("property", property); ("computed", bool computed)] + in + { program; expression } + + let program offset_table = (make_functions offset_table).program + + let expression offset_table = (make_functions offset_table).expression +end diff --git a/compiler/flow_parser/parser/expression_parser.ml b/compiler/flow_parser/parser/expression_parser.ml new file mode 100644 index 00000000000..ec4d89a875b --- /dev/null +++ b/compiler/flow_parser/parser/expression_parser.ml @@ -0,0 +1,1906 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Token +open Parser_env +open Flow_ast +open Parser_common +open Comment_attachment + +module Expression + (Parse : PARSER) + (Type : Parser_common.TYPE) + (Declaration : Parser_common.DECLARATION) + (Pattern_cover : Parser_common.COVER) : Parser_common.EXPRESSION = struct + type op_precedence = + | Left_assoc of int + | Right_assoc of int + + type group_cover = + | Group_expr of (Loc.t, Loc.t) Expression.t + | Group_typecast of (Loc.t, Loc.t) Expression.TypeCast.t + + let is_tighter a b = + let a_prec = + match a with + | Left_assoc x -> x + | Right_assoc x -> x - 1 + in + let b_prec = + match b with + | Left_assoc x -> x + | Right_assoc x -> x + in + a_prec >= b_prec + + let is_assignable_lhs = + let open Expression in + function + | ( _, + MetaProperty + { + MetaProperty.meta = (_, { Identifier.name = "new"; comments = _ }); + property = (_, { Identifier.name = "target"; comments = _ }); + comments = _; + } + ) -> + false + | ( _, + MetaProperty + { + MetaProperty.meta = (_, { Identifier.name = "import"; comments = _ }); + property = (_, { Identifier.name = "meta"; comments = _ }); + comments = _; + } + ) -> + false + (* #sec-static-semantics-static-semantics-isvalidsimpleassignmenttarget *) + | (_, Array _) + | (_, Identifier _) + | (_, Member _) + | (_, MetaProperty _) + | (_, Object _) -> + true + | (_, ArrowFunction _) + | (_, AsConstExpression _) + | (_, AsExpression _) + | (_, Assignment _) + | (_, Binary _) + | (_, Call _) + | (_, Class _) + | (_, Conditional _) + | (_, Function _) + | (_, Import _) + | (_, JSXElement _) + | (_, JSXFragment _) + | (_, StringLiteral _) + | (_, BooleanLiteral _) + | (_, NullLiteral _) + | (_, NumberLiteral _) + | (_, BigIntLiteral _) + | (_, RegExpLiteral _) + | (_, Match _) + | (_, ModuleRefLiteral _) + | (_, Logical _) + | (_, New _) + | (_, OptionalCall _) + | (_, OptionalMember _) + | (_, Sequence _) + | (_, Super _) + | (_, TaggedTemplate _) + | (_, TemplateLiteral _) + | (_, This _) + | (_, TypeCast _) + | (_, TSSatisfies _) + | (_, Unary _) + | (_, Update _) + | (_, Yield _) -> + false + + let as_expression = Pattern_cover.as_expression + + let as_pattern = Pattern_cover.as_pattern + + (* AssignmentExpression : + * [+Yield] YieldExpression + * ConditionalExpression + * LeftHandSideExpression = AssignmentExpression + * LeftHandSideExpression AssignmentOperator AssignmentExpression + * ArrowFunctionFunction + * + * Originally we were parsing this without backtracking, but + * ArrowFunctionExpression got too tricky. Oh well. + *) + let rec assignment_cover = + let assignment_but_not_arrow_function_cover env = + let start_loc = Peek.loc env in + let expr_or_pattern = conditional_cover env in + match assignment_op env with + | Some operator -> + let expr = + with_loc + ~start_loc + (fun env -> + let left = as_pattern env expr_or_pattern in + let right = assignment env in + Expression.(Assignment { Assignment.operator; left; right; comments = None })) + env + in + Cover_expr expr + | _ -> expr_or_pattern + in + let error_callback _ = function + (* Don't rollback on these errors. *) + | Parse_error.StrictReservedWord -> () + (* Everything else causes a rollback *) + | _ -> raise Try.Rollback + (* So we may or may not be parsing the first part of an arrow function + * (the part before the =>). We might end up parsing that whole thing or + * we might end up parsing only part of it and thinking we're done. We + * need to look at the next token to figure out if we really parsed an + * assignment expression or if this is just the beginning of an arrow + * function *) + in + let try_assignment_but_not_arrow_function env = + let env = env |> with_error_callback error_callback in + let ret = assignment_but_not_arrow_function_cover env in + match Peek.token env with + | T_ARROW -> + (* x => 123 *) + raise Try.Rollback + | T_COLON + when match last_token env with + | Some T_RPAREN -> true + | _ -> false -> + (* (x): number => 123 *) + raise Try.Rollback + (* async x => 123 -- and we've already parsed async as an identifier + * expression *) + | _ when Peek.is_identifier env -> begin + match ret with + | Cover_expr (_, Expression.Identifier (_, { Identifier.name = "async"; comments = _ })) + when not (Peek.is_line_terminator env) -> + raise Try.Rollback + | _ -> ret + end + | _ -> ret + in + fun env -> + let is_identifier = + Peek.is_identifier env + && + match Peek.token env with + | T_AWAIT when allow_await env -> false + | T_YIELD when allow_yield env -> false + | _ -> true + in + match (Peek.token env, is_identifier) with + | (T_YIELD, _) when allow_yield env -> Cover_expr (yield env) + | ((T_LPAREN as t), _) + | ((T_LESS_THAN as t), _) + | ((T_THIS as t), _) + | (t, true) -> + (* Ok, we don't know if this is going to be an arrow function or a + * regular assignment expression. Let's first try to parse it as an + * assignment expression. If that fails we'll try an arrow function. + * Unless it begins with `async <` in which case we first try parsing + * it as an arrow function, and then an assignment expression. + *) + let (initial, secondary) = + if t = T_ASYNC && should_parse_types env && Peek.ith_token ~i:1 env = T_LESS_THAN then + (try_arrow_function, try_assignment_but_not_arrow_function) + else + (try_assignment_but_not_arrow_function, try_arrow_function) + in + (match Try.to_parse env initial with + | Try.ParsedSuccessfully expr -> expr + | Try.FailedToParse -> + (match Try.to_parse env secondary with + | Try.ParsedSuccessfully expr -> expr + | Try.FailedToParse -> + (* Well shoot. It doesn't parse cleanly as a normal + * expression or as an arrow_function. Let's treat it as a + * normal assignment expression gone wrong *) + assignment_but_not_arrow_function_cover env)) + | _ -> assignment_but_not_arrow_function_cover env + + and assignment env = as_expression env (assignment_cover env) + + and yield env = + with_loc + (fun env -> + if in_formal_parameters env then error env Parse_error.YieldInFormalParameters; + let leading = Peek.comments env in + let start_loc = Peek.loc env in + Expect.token env T_YIELD; + let end_loc = Peek.loc env in + let (argument, delegate) = + if Peek.is_implicit_semicolon env then + (None, false) + else + let delegate = Eat.maybe env T_MULT in + let has_argument = + match Peek.token env with + | T_SEMICOLON + | T_RBRACKET + | T_RCURLY + | T_RPAREN + | T_COLON + | T_COMMA -> + false + | _ -> true + in + let argument = + if delegate || has_argument then + Some (assignment env) + else + None + in + (argument, delegate) + in + let trailing = + match argument with + | None -> Eat.trailing_comments env + | Some _ -> [] + in + let open Expression in + Yield + Yield. + { + argument; + delegate; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + result_out = Loc.btwn start_loc end_loc; + }) + env + + and is_lhs = + let open Expression in + function + | ( _, + MetaProperty + { + MetaProperty.meta = (_, { Identifier.name = "new"; comments = _ }); + property = (_, { Identifier.name = "target"; comments = _ }); + comments = _; + } + ) -> + false + | ( _, + MetaProperty + { + MetaProperty.meta = (_, { Identifier.name = "import"; comments = _ }); + property = (_, { Identifier.name = "meta"; comments = _ }); + comments = _; + } + ) -> + false + (* #sec-static-semantics-static-semantics-isvalidsimpleassignmenttarget *) + | (_, Identifier _) + | (_, Member _) + | (_, MetaProperty _) -> + true + | (_, Array _) + | (_, ArrowFunction _) + | (_, AsConstExpression _) + | (_, AsExpression _) + | (_, Assignment _) + | (_, Binary _) + | (_, Call _) + | (_, Class _) + | (_, Conditional _) + | (_, Function _) + | (_, Import _) + | (_, JSXElement _) + | (_, JSXFragment _) + | (_, StringLiteral _) + | (_, BooleanLiteral _) + | (_, NullLiteral _) + | (_, NumberLiteral _) + | (_, BigIntLiteral _) + | (_, RegExpLiteral _) + | (_, ModuleRefLiteral _) + | (_, Logical _) + | (_, Match _) + | (_, New _) + | (_, Object _) + | (_, OptionalCall _) + | (_, OptionalMember _) + | (_, Sequence _) + | (_, Super _) + | (_, TaggedTemplate _) + | (_, TemplateLiteral _) + | (_, This _) + | (_, TypeCast _) + | (_, TSSatisfies _) + | (_, Unary _) + | (_, Update _) + | (_, Yield _) -> + false + + and assignment_op env = + let op = + let open Expression.Assignment in + match Peek.token env with + | T_RSHIFT3_ASSIGN -> Some (Some RShift3Assign) + | T_RSHIFT_ASSIGN -> Some (Some RShiftAssign) + | T_LSHIFT_ASSIGN -> Some (Some LShiftAssign) + | T_BIT_XOR_ASSIGN -> Some (Some BitXorAssign) + | T_BIT_OR_ASSIGN -> Some (Some BitOrAssign) + | T_BIT_AND_ASSIGN -> Some (Some BitAndAssign) + | T_MOD_ASSIGN -> Some (Some ModAssign) + | T_DIV_ASSIGN -> Some (Some DivAssign) + | T_MULT_ASSIGN -> Some (Some MultAssign) + | T_EXP_ASSIGN -> Some (Some ExpAssign) + | T_MINUS_ASSIGN -> Some (Some MinusAssign) + | T_PLUS_ASSIGN -> Some (Some PlusAssign) + | T_NULLISH_ASSIGN -> Some (Some NullishAssign) + | T_AND_ASSIGN -> Some (Some AndAssign) + | T_OR_ASSIGN -> Some (Some OrAssign) + | T_ASSIGN -> Some None + | _ -> None + in + if op <> None then Eat.token env; + op + + (* ConditionalExpression : + * LogicalExpression + * LogicalExpression ? AssignmentExpression : AssignmentExpression + *) + and conditional_cover env = + let start_loc = Peek.loc env in + let expr = logical_cover env in + if Peek.token env = T_PLING then ( + Eat.token env; + + (* no_in is ignored for the consequent *) + let env' = env |> with_no_in false in + let consequent = assignment env' in + Expect.token env T_COLON; + let (loc, alternate) = with_loc ~start_loc assignment env in + Cover_expr + ( loc, + let open Expression in + Conditional + { Conditional.test = as_expression env expr; consequent; alternate; comments = None } + ) + ) else + expr + + and conditional env = as_expression env (conditional_cover env) + + (* + * LogicalANDExpression : + * BinaryExpression + * LogicalANDExpression && BitwiseORExpression + * + * LogicalORExpression : + * LogicalANDExpression + * LogicalORExpression || LogicalANDExpression + * LogicalORExpression ?? LogicalANDExpression + * + * LogicalExpression : + * LogicalORExpression + *) + and logical_cover = + let open Expression in + let make_logical env left right operator loc = + let left = as_expression env left in + let right = as_expression env right in + Cover_expr (loc, Logical { Logical.operator; left; right; comments = None }) + in + let rec logical_and env left lloc = + match Peek.token env with + | T_AND -> + Eat.token env; + let (rloc, right) = with_loc binary_cover env in + let loc = Loc.btwn lloc rloc in + let left = make_logical env left right Logical.And loc in + (* `a && b ?? c` is an error, but to recover, try to parse it like `(a && b) ?? c`. *) + let (loc, left) = coalesce ~allowed:false env left loc in + logical_and env left loc + | _ -> (lloc, left) + and logical_or env left lloc = + match Peek.token env with + | T_OR -> + Eat.token env; + let (rloc, right) = with_loc binary_cover env in + let (rloc, right) = logical_and env right rloc in + let loc = Loc.btwn lloc rloc in + let left = make_logical env left right Logical.Or loc in + (* `a || b ?? c` is an error, but to recover, try to parse it like `(a || b) ?? c`. *) + let (loc, left) = coalesce ~allowed:false env left loc in + logical_or env left loc + | _ -> (lloc, left) + and coalesce ~allowed env left lloc = + match Peek.token env with + | T_PLING_PLING -> + if not allowed then error env (Parse_error.NullishCoalescingUnexpectedLogical "??"); + + Expect.token env T_PLING_PLING; + let (rloc, right) = with_loc binary_cover env in + let (rloc, right) = + match Peek.token env with + | (T_AND | T_OR) as t -> + (* `a ?? b || c` is an error. To recover, treat it like `a ?? (b || c)`. *) + error env (Parse_error.NullishCoalescingUnexpectedLogical (Token.value_of_token t)); + let (rloc, right) = logical_and env right rloc in + logical_or env right rloc + | _ -> (rloc, right) + in + let loc = Loc.btwn lloc rloc in + coalesce ~allowed:true env (make_logical env left right Logical.NullishCoalesce loc) loc + | _ -> (lloc, left) + in + fun env -> + let (loc, left) = with_loc binary_cover env in + let (_, left) = + match Peek.token env with + | T_PLING_PLING -> coalesce ~allowed:true env left loc + | _ -> + let (loc, left) = logical_and env left loc in + logical_or env left loc + in + left + + and binary_cover = + let binary_op env = + let ret = + let open Expression.Binary in + match Peek.token env with + (* Most BinaryExpression operators are left associative *) + (* Lowest pri *) + | T_BIT_OR -> Some (BitOr, Left_assoc 2) + | T_BIT_XOR -> Some (Xor, Left_assoc 3) + | T_BIT_AND -> Some (BitAnd, Left_assoc 4) + | T_EQUAL -> Some (Equal, Left_assoc 5) + | T_STRICT_EQUAL -> Some (StrictEqual, Left_assoc 5) + | T_NOT_EQUAL -> Some (NotEqual, Left_assoc 5) + | T_STRICT_NOT_EQUAL -> Some (StrictNotEqual, Left_assoc 5) + | T_LESS_THAN -> Some (LessThan, Left_assoc 6) + | T_LESS_THAN_EQUAL -> Some (LessThanEqual, Left_assoc 6) + | T_GREATER_THAN -> Some (GreaterThan, Left_assoc 6) + | T_GREATER_THAN_EQUAL -> Some (GreaterThanEqual, Left_assoc 6) + | T_IN -> + if no_in env then + None + else + Some (In, Left_assoc 6) + | T_INSTANCEOF -> Some (Instanceof, Left_assoc 6) + | T_LSHIFT -> Some (LShift, Left_assoc 7) + | T_RSHIFT -> Some (RShift, Left_assoc 7) + | T_RSHIFT3 -> Some (RShift3, Left_assoc 7) + | T_PLUS -> Some (Plus, Left_assoc 8) + | T_MINUS -> Some (Minus, Left_assoc 8) + | T_MULT -> Some (Mult, Left_assoc 9) + | T_DIV -> Some (Div, Left_assoc 9) + | T_MOD -> Some (Mod, Left_assoc 9) + | T_EXP -> Some (Exp, Right_assoc 10) + (* Highest priority *) + | _ -> None + in + if ret <> None then Eat.token env; + ret + in + let make_binary left right operator loc = + (loc, Expression.(Binary Binary.{ operator; left; right; comments = None })) + in + let rec add_to_stack right (rop, rpri) rloc = function + | (left, (lop, lpri), lloc) :: rest when is_tighter lpri rpri -> + let loc = Loc.btwn lloc rloc in + let right = make_binary left right lop loc in + add_to_stack right (rop, rpri) loc rest + | stack -> (right, (rop, rpri), rloc) :: stack + in + let rec collapse_stack right rloc = function + | [] -> right + | (left, (lop, _), lloc) :: rest -> + let loc = Loc.btwn lloc rloc in + collapse_stack (make_binary left right lop loc) loc rest + in + let rec helper env stack = + let (expr_loc, (is_unary, expr)) = + with_loc + (fun env -> + let is_unary = peek_unary_op env <> None in + let expr = unary_cover (env |> with_no_in false) in + (is_unary, expr)) + env + in + let next = Peek.token env in + ( if next = T_LESS_THAN then + match expr with + | Cover_expr (_, Expression.JSXElement _) -> error env Parse_error.AdjacentJSXElements + | _ -> () + ); + let (stack, expr) = + let rec loop stack expr = + match Peek.token env with + | T_IDENTIFIER { raw = ("as" | "satisfies") as keyword; _ } when should_parse_types env -> + Eat.token env; + let expr = as_expression env expr in + let (stack, expr) = + match stack with + | (left, (lop, lpri), lloc) :: rest when is_tighter lpri (Left_assoc 6) -> + let expr_loc = Loc.btwn lloc expr_loc in + let expr = make_binary left expr lop expr_loc in + (rest, expr) + | _ -> (stack, expr) + in + let (expr_loc, _) = expr in + let expr = + if keyword = "satisfies" then + let ((annot_loc, _) as annot) = Type._type env in + let loc = Loc.btwn expr_loc annot_loc in + Cover_expr + ( loc, + Expression.TSSatisfies + { + Expression.TSSatisfies.expression = expr; + annot = (annot_loc, annot); + comments = None; + } + ) + else if Peek.token env = T_CONST then ( + let loc = Loc.btwn expr_loc (Peek.loc env) in + Eat.token env; + Cover_expr + ( loc, + Expression.AsConstExpression + { Expression.AsConstExpression.expression = expr; comments = None } + ) + ) else + let ((annot_loc, _) as annot) = Type._type env in + let loc = Loc.btwn expr_loc annot_loc in + Cover_expr + ( loc, + Expression.AsExpression + { + Expression.AsExpression.expression = expr; + annot = (annot_loc, annot); + comments = None; + } + ) + in + loop stack expr + | _ -> (stack, expr) + in + loop stack expr + in + + match (stack, binary_op env) with + | ([], None) -> expr + | (_, None) -> + let expr = as_expression env expr in + Cover_expr (collapse_stack expr expr_loc stack) + | (_, Some (rop, rpri)) -> + if is_unary && rop = Expression.Binary.Exp then + error_at env (expr_loc, Parse_error.InvalidLHSInExponentiation); + let expr = as_expression env expr in + helper env (add_to_stack expr (rop, rpri) expr_loc stack) + in + (fun env -> helper env []) + + and peek_unary_op env = + let open Expression.Unary in + match Peek.token env with + | T_NOT -> Some Not + | T_BIT_NOT -> Some BitNot + | T_PLUS -> Some Plus + | T_MINUS -> Some Minus + | T_TYPEOF -> Some Typeof + | T_VOID -> Some Void + | T_DELETE -> Some Delete + (* If we are in a unary expression context, and within an async function, + * assume that a use of "await" is intended as a keyword, not an ordinary + * identifier. This is a little bit inconsistent, since it can be used as + * an identifier in other contexts (such as a variable name), but it's how + * Babel does it. *) + | T_AWAIT when allow_await env -> + if in_formal_parameters env then error env Parse_error.AwaitInAsyncFormalParameters; + Some Await + | _ -> None + + and unary_cover env = + let start_loc = Peek.loc env in + let leading = Peek.comments env in + let op = peek_unary_op env in + match op with + | None -> + let op = + let open Expression.Update in + match Peek.token env with + | T_INCR -> Some Increment + | T_DECR -> Some Decrement + | _ -> None + in + (match op with + | None -> postfix_cover env + | Some operator -> + Eat.token env; + let (loc, argument) = with_loc ~start_loc unary env in + if not (is_lhs argument) then error_at env (fst argument, Parse_error.InvalidLHSInAssignment); + (match argument with + | (_, Expression.Identifier (_, { Identifier.name; comments = _ })) when is_restricted name + -> + strict_error env Parse_error.StrictLHSPrefix + | _ -> ()); + Cover_expr + ( loc, + Expression.( + Update + { + Update.operator; + prefix = true; + argument; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + )) + | Some operator -> + Eat.token env; + let (loc, argument) = with_loc ~start_loc unary env in + let open Expression in + (match (operator, argument) with + | (Unary.Delete, (_, Identifier _)) -> strict_error_at env (loc, Parse_error.StrictDelete) + | (Unary.Delete, (_, Member member)) -> begin + match member.Ast.Expression.Member.property with + | Ast.Expression.Member.PropertyPrivateName _ -> + error_at env (loc, Parse_error.PrivateDelete) + | _ -> () + end + | _ -> ()); + Cover_expr + ( loc, + let open Expression in + Unary { Unary.operator; argument; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + + and unary env = as_expression env (unary_cover env) + + and postfix_cover env = + let argument = left_hand_side_cover env in + (* No line terminator allowed before operator *) + if Peek.is_line_terminator env then + argument + else + let op = + let open Expression.Update in + match Peek.token env with + | T_INCR -> Some Increment + | T_DECR -> Some Decrement + | _ -> None + in + match op with + | None -> argument + | Some operator -> + let argument = as_expression env argument in + if not (is_lhs argument) then error_at env (fst argument, Parse_error.InvalidLHSInAssignment); + (match argument with + | (_, Expression.Identifier (_, { Identifier.name; comments = _ })) when is_restricted name + -> + strict_error env Parse_error.StrictLHSPostfix + | _ -> ()); + let end_loc = Peek.loc env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let loc = Loc.btwn (fst argument) end_loc in + Cover_expr + ( loc, + Expression.( + Update + { + Update.operator; + prefix = false; + argument; + comments = Flow_ast_utils.mk_comments_opt ~trailing (); + } + ) + ) + + and left_hand_side_cover env = + let start_loc = Peek.loc env in + let allow_new = not (no_new env) in + let env = with_no_new false env in + let expr = + match Peek.token env with + | T_NEW when allow_new -> Cover_expr (new_expression env) + | T_IMPORT -> Cover_expr (import env) + | T_SUPER -> Cover_expr (super env) + | _ when Peek.is_function env -> Cover_expr (_function env) + | _ -> primary_cover env + in + call_cover env start_loc expr + + and left_hand_side env = as_expression env (left_hand_side_cover env) + + and super env = + let (allowed, call_allowed) = + match allow_super env with + | No_super -> (false, false) + | Super_prop -> (true, false) + | Super_prop_or_call -> (true, true) + in + let loc = Peek.loc env in + let leading = Peek.comments env in + Expect.token env T_SUPER; + let trailing = Eat.trailing_comments env in + let super = + ( loc, + Expression.Super + { Expression.Super.comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) + in + match Peek.token env with + | T_PERIOD + | T_LBRACKET -> + let super = + if not allowed then ( + error_at env (loc, Parse_error.UnexpectedSuper); + (loc, Expression.Identifier (Flow_ast_utils.ident_of_source (loc, "super"))) + ) else + super + in + call env loc super + | T_LPAREN -> + let super = + if not call_allowed then ( + error_at env (loc, Parse_error.UnexpectedSuperCall); + (loc, Expression.Identifier (Flow_ast_utils.ident_of_source (loc, "super"))) + ) else + super + in + call env loc super + | _ -> + if not allowed then + error_at env (loc, Parse_error.UnexpectedSuper) + else + error_unexpected ~expected:"either a call or access of `super`" env; + super + + and import env = + with_loc + (fun env -> + let leading = Peek.comments env in + let start_loc = Peek.loc env in + Expect.token env T_IMPORT; + if Eat.maybe env T_PERIOD then ( + (* import.meta *) + let import_ident = Flow_ast_utils.ident_of_source (start_loc, "import") in + let meta_loc = Peek.loc env in + Expect.identifier env "meta"; + let meta_ident = Flow_ast_utils.ident_of_source (meta_loc, "meta") in + let trailing = Eat.trailing_comments env in + Expression.MetaProperty + { + Expression.MetaProperty.meta = import_ident; + property = meta_ident; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) else + let leading_arg = Peek.comments env in + Expect.token env T_LPAREN; + let argument = add_comments (assignment (with_no_in false env)) ~leading:leading_arg in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + Expression.Import + { + Expression.Import.argument; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + + and call_cover ?(allow_optional_chain = true) ?(in_optional_chain = false) env start_loc left = + let left = member_cover ~allow_optional_chain ~in_optional_chain env start_loc left in + let optional = + match last_token env with + | Some T_PLING_PERIOD -> true + | _ -> false + in + let left_to_callee env = + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing (as_expression env left) (fun remover left -> remover#expression left) + in + let arguments ?targs env callee = + let (args_loc, arguments) = arguments env in + let loc = Loc.btwn start_loc args_loc in + let call = + { Expression.Call.callee; targs; arguments = (args_loc, arguments); comments = None } + in + let call = + if optional || in_optional_chain then + let open Expression in + OptionalCall { OptionalCall.call; optional; filtered_out = loc } + else + Expression.Call call + in + let in_optional_chain = in_optional_chain || optional in + call_cover ~allow_optional_chain ~in_optional_chain env start_loc (Cover_expr (loc, call)) + in + if no_call env then + left + else + match Peek.token env with + | T_LPAREN -> arguments env (left_to_callee env) + | T_LSHIFT + | T_LESS_THAN + when should_parse_types env -> + (* If we are parsing types, then f(e) is a function call with a + type application. If we aren't, it's a nested binary expression. *) + let error_callback _ _ = raise Try.Rollback in + let env = env |> with_error_callback error_callback in + (* Parameterized call syntax is ambiguous, so we fall back to + standard parsing if it fails. *) + Try.or_else env ~fallback:left (fun env -> + let callee = left_to_callee env in + let targs = call_type_args env in + arguments ?targs env callee + ) + | _ -> left + + and call ?(allow_optional_chain = true) env start_loc left = + as_expression env (call_cover ~allow_optional_chain env start_loc (Cover_expr left)) + + and new_expression env = + with_loc + (fun env -> + let start_loc = Peek.loc env in + let leading = Peek.comments env in + Expect.token env T_NEW; + + if in_function env && Peek.token env = T_PERIOD then ( + let trailing = Eat.trailing_comments env in + Eat.token env; + let meta = + Flow_ast_utils.ident_of_source + (start_loc, "new") + ?comments:(Flow_ast_utils.mk_comments_opt ~leading ~trailing ()) + in + match Peek.token env with + | T_IDENTIFIER { raw = "target"; _ } -> + let property = Parse.identifier env in + Expression.(MetaProperty MetaProperty.{ meta; property; comments = None }) + | _ -> + error_unexpected ~expected:"the identifier `target`" env; + Eat.token env; + + (* skip unknown identifier *) + Expression.Identifier meta + (* return `new` identifier *) + ) else + let callee_loc = Peek.loc env in + let expr = + match Peek.token env with + | T_NEW -> new_expression env + | T_SUPER -> super (env |> with_no_call true) + | _ when Peek.is_function env -> _function env + | _ -> primary env + in + let callee = + member ~allow_optional_chain:false (env |> with_no_call true) callee_loc expr + in + (* You can do something like + * new raw`42` + *) + let callee = + let callee = + match Peek.token env with + | T_TEMPLATE_PART part -> tagged_template env callee_loc callee part + | _ -> callee + in + (* Remove trailing comments if the callee is followed by args or type args *) + if Peek.token env = T_LPAREN || (should_parse_types env && Peek.token env = T_LESS_THAN) + then + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing callee (fun remover callee -> remover#expression callee) + else + callee + in + let targs = + (* If we are parsing types, then new C(e) is a constructor with a + type application. If we aren't, it's a nested binary expression. *) + if should_parse_types env then + (* Parameterized call syntax is ambiguous, so we fall back to + standard parsing if it fails. *) + let error_callback _ _ = raise Try.Rollback in + let env = env |> with_error_callback error_callback in + Try.or_else env ~fallback:None call_type_args + else + None + in + let arguments = + match Peek.token env with + | T_LPAREN -> Some (arguments env) + | _ -> None + in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Expression.(New New.{ callee; targs; arguments; comments })) + env + + and call_type_args = + let args = + let rec args_helper env acc = + match Peek.token env with + | T_EOF + | T_GREATER_THAN -> + List.rev acc + | _ -> + let t = + match Peek.token env with + | T_IDENTIFIER { value = "_"; _ } -> + let loc = Peek.loc env in + let leading = Peek.comments env in + Expect.identifier env "_"; + let trailing = Eat.trailing_comments env in + Expression.CallTypeArg.Implicit + ( loc, + { + Expression.CallTypeArg.Implicit.comments = + Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + | _ -> Expression.CallTypeArg.Explicit (Type._type env) + in + let acc = t :: acc in + if Peek.token env <> T_GREATER_THAN then Expect.token env T_COMMA; + args_helper env acc + in + fun env -> + let leading = Peek.comments env in + Expect.token env T_LESS_THAN; + let arguments = args_helper env [] in + let internal = Peek.comments env in + Expect.token env T_GREATER_THAN; + let trailing = + if Peek.token env = T_LPAREN then + let { trailing; _ } = trailing_and_remover env in + trailing + else + Eat.trailing_comments env + in + { + Expression.CallTypeArgs.arguments; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + } + in + fun env -> + Eat.push_lex_mode env Lex_mode.TYPE; + let node = + if Peek.token env = T_LESS_THAN then + Some (with_loc args env) + else + None + in + Eat.pop_lex_mode env; + node + + and arguments = + let spread_element env = + let leading = Peek.comments env in + Expect.token env T_ELLIPSIS; + let argument = assignment env in + Expression.SpreadElement.{ argument; comments = Flow_ast_utils.mk_comments_opt ~leading () } + in + let argument env = + match Peek.token env with + | T_ELLIPSIS -> Expression.Spread (with_loc spread_element env) + | _ -> Expression.Expression (assignment env) + in + let rec arguments' env acc = + match Peek.token env with + | T_EOF + | T_RPAREN -> + List.rev acc + | _ -> + let acc = argument env :: acc in + if Peek.token env <> T_RPAREN then Expect.token env T_COMMA; + arguments' env acc + in + fun env -> + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LPAREN; + let args = arguments' env [] in + let internal = Peek.comments env in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + { + Expression.ArgList.arguments = args; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }) + env + + and member_cover = + let dynamic + ?(allow_optional_chain = true) + ?(in_optional_chain = false) + ?(optional = false) + env + start_loc + left = + let expr = Parse.expression (env |> with_no_call false) in + let last_loc = Peek.loc env in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + let loc = Loc.btwn start_loc last_loc in + let member = + { + Expression.Member._object = as_expression env left; + property = Expression.Member.PropertyExpression expr; + comments = Flow_ast_utils.mk_comments_opt ~trailing (); + } + in + + let member = + if in_optional_chain then + let open Expression in + OptionalMember { OptionalMember.member; optional; filtered_out = loc } + else + Expression.Member member + in + call_cover ~allow_optional_chain ~in_optional_chain env start_loc (Cover_expr (loc, member)) + in + let static + ?(allow_optional_chain = true) + ?(in_optional_chain = false) + ?(optional = false) + env + start_loc + left = + let open Expression.Member in + let (id_loc, property) = + match Peek.token env with + | T_POUND -> + let ((id_loc, { Ast.PrivateName.name; _ }) as id) = private_identifier env in + add_used_private env name id_loc; + (id_loc, PropertyPrivateName id) + | _ -> + let ((id_loc, _) as id) = identifier_name env in + (id_loc, PropertyIdentifier id) + in + let loc = Loc.btwn start_loc id_loc in + (* super.PrivateName is a syntax error *) + begin + match (left, property) with + | (Cover_expr (_, Ast.Expression.Super _), PropertyPrivateName _) -> + error_at env (loc, Parse_error.SuperPrivate) + | _ -> () + end; + let member = + Expression.Member.{ _object = as_expression env left; property; comments = None } + in + let member = + if in_optional_chain then + let open Expression in + OptionalMember { OptionalMember.member; optional; filtered_out = loc } + else + Expression.Member member + in + call_cover ~allow_optional_chain ~in_optional_chain env start_loc (Cover_expr (loc, member)) + in + fun ?(allow_optional_chain = true) ?(in_optional_chain = false) env start_loc left -> + match Peek.token env with + | T_PLING_PERIOD -> + if not allow_optional_chain then error env Parse_error.OptionalChainNew; + + Expect.token env T_PLING_PERIOD; + begin + match Peek.token env with + | T_TEMPLATE_PART _ -> + error env Parse_error.OptionalChainTemplate; + left + | T_LPAREN -> left + | T_LESS_THAN when should_parse_types env -> left + | T_LBRACKET -> + Eat.token env; + dynamic ~allow_optional_chain ~in_optional_chain:true ~optional:true env start_loc left + | _ -> + static ~allow_optional_chain ~in_optional_chain:true ~optional:true env start_loc left + end + | T_LBRACKET -> + Eat.token env; + dynamic ~allow_optional_chain ~in_optional_chain env start_loc left + | T_PERIOD -> + Eat.token env; + static ~allow_optional_chain ~in_optional_chain env start_loc left + | T_TEMPLATE_PART part -> + if in_optional_chain then error env Parse_error.OptionalChainTemplate; + let expr = tagged_template env start_loc (as_expression env left) part in + call_cover ~allow_optional_chain:true env start_loc (Cover_expr expr) + | _ -> left + + and member ?(allow_optional_chain = true) env start_loc left = + as_expression env (member_cover ~allow_optional_chain env start_loc (Cover_expr left)) + + and _function env = + with_loc + (fun env -> + let (async, leading_async) = Declaration.async env in + let (sig_loc, (id, params, generator, predicate, return, tparams, leading)) = + with_loc + (fun env -> + let leading_function = Peek.comments env in + Expect.token env T_FUNCTION; + let (generator, leading_generator) = Declaration.generator env in + let leading = List.concat [leading_async; leading_function; leading_generator] in + (* `await` is a keyword in async functions: + - proposal-async-iteration/#prod-AsyncGeneratorExpression + - #prod-AsyncFunctionExpression *) + let await = async in + (* `yield` is a keyword in generator functions: + - proposal-async-iteration/#prod-AsyncGeneratorExpression + - #prod-GeneratorExpression *) + let yield = generator in + let (id, tparams) = + if Peek.token env = T_LPAREN then + (None, None) + else + let id = + match Peek.token env with + | T_LESS_THAN -> None + | _ -> + let env = env |> with_allow_await await |> with_allow_yield yield in + let id = + id_remove_trailing + env + (Parse.identifier ~restricted_error:Parse_error.StrictFunctionName env) + in + Some id + in + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.FunctionTP + (Type.type_params env) + in + (id, tparams) + in + (* #sec-function-definitions-static-semantics-early-errors *) + let env = env |> with_allow_super No_super in + let params = + (* await is a keyword if *this* is an async function, OR if it's already + a keyword in the current scope (e.g. if this is a non-async function + nested in an async function). *) + let await = await || allow_await env in + let params = Declaration.function_params ~await ~yield env in + if Peek.token env = T_COLON then + params + else + function_params_remove_trailing env params + in + let (return, predicate) = Type.function_return_annotation_and_predicate_opt env in + let (return, predicate) = + match predicate with + | None -> (return_annotation_remove_trailing env return, predicate) + | Some _ -> (return, predicate_remove_trailing env predicate) + in + (id, params, generator, predicate, return, tparams, leading)) + env + in + let simple_params = is_simple_parameter_list params in + let (body, contains_use_strict) = + Declaration.function_body env ~async ~generator ~expression:true ~simple_params + in + Declaration.strict_function_post_check env ~contains_use_strict id params; + Expression.Function + { + Function.id; + params; + body; + generator; + effect_ = Function.Arbitrary; + async; + predicate; + return; + tparams; + sig_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + + and number env kind raw = + let value = + match kind with + | LEGACY_OCTAL -> + strict_error env Parse_error.StrictOctalLiteral; + begin + try Int64.to_float (Int64.of_string ("0o" ^ raw)) with + | Failure _ -> failwith ("Invalid legacy octal " ^ raw) + end + | LEGACY_NON_OCTAL -> + strict_error env Parse_error.StrictNonOctalLiteral; + begin + try float_of_string raw with + | Failure _ -> failwith ("Invalid number " ^ raw) + end + | BINARY + | OCTAL -> begin + try Int64.to_float (Int64.of_string raw) with + | Failure _ -> failwith ("Invalid binary/octal " ^ raw) + end + | NORMAL -> begin + try float_of_string raw with + | Failure _ -> failwith ("Invalid number " ^ raw) + end + in + Expect.token env (T_NUMBER { kind; raw }); + value + + and bigint_strip_n raw = + let size = String.length raw in + let str = + if size != 0 && raw.[size - 1] == 'n' then + String.sub raw 0 (size - 1) + else + raw + in + str + + and bigint env kind raw = + let postraw = bigint_strip_n raw in + let value = Int64.of_string_opt postraw in + Expect.token env (T_BIGINT { kind; raw }); + value + + and primary_cover env = + let loc = Peek.loc env in + let leading = Peek.comments env in + match Peek.token env with + | T_THIS -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Cover_expr + ( loc, + Expression.This + { Expression.This.comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) + | T_NUMBER { kind; raw } -> + let value = number env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Cover_expr (loc, Expression.NumberLiteral { Ast.NumberLiteral.value; raw; comments }) + | T_BIGINT { kind; raw } -> + let value = bigint env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Cover_expr (loc, Expression.BigIntLiteral { Ast.BigIntLiteral.value; raw; comments }) + | T_STRING (loc, value, raw, octal) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + Eat.token env; + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + let expr = + let opts = parse_options env in + match (opts.module_ref_prefix, opts.module_ref_prefix_LEGACY_INTEROP) with + | (Some prefix, _) when String.starts_with ~prefix value -> + let prefix_len = String.length prefix in + Expression.ModuleRefLiteral + { + Ast.ModuleRefLiteral.value; + require_loc = loc; + def_loc_opt = None; + prefix_len; + legacy_interop = false; + raw; + comments; + } + | (_, Some prefix) when String.starts_with ~prefix value -> + let prefix_len = String.length prefix in + Expression.ModuleRefLiteral + { + Ast.ModuleRefLiteral.value; + require_loc = loc; + def_loc_opt = None; + prefix_len; + legacy_interop = true; + raw; + comments; + } + | _ -> Expression.StringLiteral { Ast.StringLiteral.value; raw; comments } + in + Cover_expr (loc, expr) + | (T_TRUE | T_FALSE) as token -> + Eat.token env; + let value = token = T_TRUE in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Cover_expr (loc, Expression.BooleanLiteral { Ast.BooleanLiteral.value; comments }) + | T_NULL -> + Eat.token env; + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Cover_expr (loc, Expression.NullLiteral comments) + | T_LPAREN -> Cover_expr (group env) + | T_LCURLY -> + let (loc, obj, errs) = Parse.object_initializer env in + Cover_patt ((loc, Expression.Object obj), errs) + | T_LBRACKET -> + let (loc, (arr, errs)) = with_loc array_initializer env in + Cover_patt ((loc, Expression.Array arr), errs) + | T_DIV + | T_DIV_ASSIGN -> + Cover_expr (regexp env) + | T_LESS_THAN -> + let (loc, expression) = + match Parse.jsx_element_or_fragment env with + | (loc, `Element e) -> (loc, Expression.JSXElement e) + | (loc, `Fragment f) -> (loc, Expression.JSXFragment f) + in + Cover_expr (loc, expression) + | T_TEMPLATE_PART part -> + let (loc, template) = template_literal env part in + Cover_expr (loc, Expression.TemplateLiteral template) + | T_CLASS -> Cover_expr (Parse.class_expression env) + (* `match (` *) + | T_MATCH + when (parse_options env).pattern_matching + && (not (Peek.ith_is_line_terminator ~i:1 env)) + && Peek.ith_token ~i:1 env = T_LPAREN -> + let leading = Peek.comments env in + let match_keyword_loc = Peek.loc env in + (* Consume `match` as an identifier, in case it's a call expression. *) + let id = Parse.identifier env in + (* Allows trailing comma. *) + let args = arguments env in + (* `match () {` *) + if (not (Peek.is_line_terminator env)) && Peek.token env = T_LCURLY then + let arg = Parser_common.reparse_arguments_as_match_argument env args in + Cover_expr (match_expression ~match_keyword_loc ~leading ~arg env) + else + (* It's actually a call expression of the form `match(...)` *) + let callee = (match_keyword_loc, Expression.Identifier id) in + let (args_loc, _) = args in + let loc = Loc.btwn match_keyword_loc args_loc in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + let call = + Expression.Call { Expression.Call.callee; targs = None; arguments = args; comments } + in + (* Could have a chained call after this. *) + call_cover + ~allow_optional_chain:true + ~in_optional_chain:false + env + match_keyword_loc + (Cover_expr (loc, call)) + | T_IDENTIFIER { raw = "abstract"; _ } when Peek.ith_token ~i:1 env = T_CLASS -> + Cover_expr (Parse.class_expression env) + | _ when Peek.is_identifier env -> + let id = Parse.identifier env in + Cover_expr (fst id, Expression.Identifier id) + | t -> + error_unexpected env; + + (* Let's get rid of the bad token *) + begin + match t with + | T_ERROR _ -> Eat.token env + | _ -> () + end; + + (* Really no idea how to recover from this. I suppose a null + * expression is as good as anything *) + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing:[] () in + Cover_expr (loc, Expression.NullLiteral comments) + + and primary env = as_expression env (primary_cover env) + + and match_expression env ~match_keyword_loc ~leading ~arg = + let case env = + let leading = Peek.comments env in + let pattern = Parse.match_pattern env in + let guard = + if Eat.maybe env T_IF then ( + Expect.token env T_LPAREN; + let test = Parse.expression env in + Expect.token env T_RPAREN; + Some test + ) else + None + in + (* Continue parsing colon until hermes-parser is also updated. *) + if not @@ Eat.maybe env T_COLON then Expect.token env T_ARROW; + let body = assignment env in + (match Peek.token env with + | T_EOF + | T_RCURLY -> + () + | _ -> Expect.token env T_COMMA); + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + { Match.Case.pattern; body; guard; comments } + in + let rec case_list env acc = + match Peek.token env with + | T_EOF + | T_RCURLY -> + List.rev acc + | _ -> case_list env (with_loc case env :: acc) + in + with_loc + ~start_loc:match_keyword_loc + (fun env -> + Expect.token env T_LCURLY; + let cases = case_list env [] in + Expect.token env T_RCURLY; + let trailing = Eat.trailing_comments env in + Expression.Match + { + Match.arg; + cases; + match_keyword_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + + and template_literal = + let rec template_parts env quasis expressions = + let expr = Parse.expression env in + let expressions = expr :: expressions in + match Peek.token env with + | T_RCURLY -> + Eat.push_lex_mode env Lex_mode.TEMPLATE; + let (loc, part, is_tail) = + match Peek.token env with + | T_TEMPLATE_PART (loc, cooked, raw, _, tail) -> + let open Ast.Expression.TemplateLiteral in + Eat.token env; + (loc, { Element.value = { Element.cooked; raw }; tail }, tail) + | _ -> assert false + in + Eat.pop_lex_mode env; + let quasis = (loc, part) :: quasis in + if is_tail then + (loc, List.rev quasis, List.rev expressions) + else + template_parts env quasis expressions + | _ -> + (* Malformed template *) + error_unexpected ~expected:"a template literal part" env; + let imaginary_quasi = + ( fst expr, + { + Expression.TemplateLiteral.Element.value = + { Expression.TemplateLiteral.Element.raw = ""; cooked = "" }; + tail = true; + } + ) + in + (fst expr, List.rev (imaginary_quasi :: quasis), List.rev expressions) + in + fun env ((start_loc, cooked, raw, _, is_tail) as part) -> + let leading = Peek.comments env in + Expect.token env (T_TEMPLATE_PART part); + let (end_loc, quasis, expressions) = + let head = + ( start_loc, + { + Ast.Expression.TemplateLiteral.Element.value = + { Ast.Expression.TemplateLiteral.Element.cooked; raw }; + tail = is_tail; + } + ) + in + + if is_tail then + (start_loc, [head], []) + else + template_parts env [head] [] + in + let trailing = Eat.trailing_comments env in + let loc = Loc.btwn start_loc end_loc in + ( loc, + { + Expression.TemplateLiteral.quasis; + expressions; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + and tagged_template env start_loc tag part = + let tag = expression_remove_trailing env tag in + let quasi = template_literal env part in + ( Loc.btwn start_loc (fst quasi), + Expression.(TaggedTemplate TaggedTemplate.{ tag; quasi; comments = None }) + ) + + and group env = + let leading = Peek.comments env in + let (loc, cover) = + with_loc + (fun env -> + Expect.token env T_LPAREN; + let expr_start_loc = Peek.loc env in + let expression = assignment env in + let ret = + match Peek.token env with + | T_COLON -> + let annot = Type.annotation env in + Group_typecast Expression.TypeCast.{ expression; annot; comments = None } + | T_COMMA -> Group_expr (sequence env ~start_loc:expr_start_loc [expression]) + | _ -> Group_expr expression + in + Expect.token env T_RPAREN; + ret) + env + in + let trailing = Eat.trailing_comments env in + let ret = + match cover with + | Group_expr expr -> expr + | Group_typecast cast -> (loc, Expression.TypeCast cast) + in + add_comments ret ~leading ~trailing + + and add_comments ?(leading = []) ?(trailing = []) (loc, expression) = + let merge_comments inner = + Flow_ast_utils.merge_comments + ~inner + ~outer:(Flow_ast_utils.mk_comments_opt ~leading ~trailing ()) + in + let merge_comments_with_internal inner = + Flow_ast_utils.merge_comments_with_internal + ~inner + ~outer:(Flow_ast_utils.mk_comments_opt ~leading ~trailing ()) + in + let open Expression in + ( loc, + match expression with + | Array ({ Array.comments; _ } as e) -> + Array { e with Array.comments = merge_comments_with_internal comments } + | ArrowFunction ({ Function.comments; _ } as e) -> + ArrowFunction { e with Function.comments = merge_comments comments } + | AsExpression ({ AsExpression.comments; _ } as e) -> + AsExpression { e with AsExpression.comments = merge_comments comments } + | AsConstExpression ({ AsConstExpression.comments; _ } as e) -> + AsConstExpression { e with AsConstExpression.comments = merge_comments comments } + | Assignment ({ Assignment.comments; _ } as e) -> + Assignment { e with Assignment.comments = merge_comments comments } + | Binary ({ Binary.comments; _ } as e) -> + Binary { e with Binary.comments = merge_comments comments } + | Call ({ Call.comments; _ } as e) -> Call { e with Call.comments = merge_comments comments } + | Class ({ Class.comments; _ } as e) -> + Class { e with Class.comments = merge_comments comments } + | Conditional ({ Conditional.comments; _ } as e) -> + Conditional { e with Conditional.comments = merge_comments comments } + | Function ({ Function.comments; _ } as e) -> + Function { e with Function.comments = merge_comments comments } + | Identifier (loc, ({ Identifier.comments; _ } as e)) -> + Identifier (loc, { e with Identifier.comments = merge_comments comments }) + | Import ({ Import.comments; _ } as e) -> + Import { e with Import.comments = merge_comments comments } + | JSXElement ({ JSX.comments; _ } as e) -> + JSXElement { e with JSX.comments = merge_comments comments } + | JSXFragment ({ JSX.frag_comments; _ } as e) -> + JSXFragment { e with JSX.frag_comments = merge_comments frag_comments } + | StringLiteral ({ StringLiteral.comments; _ } as e) -> + StringLiteral { e with StringLiteral.comments = merge_comments comments } + | BooleanLiteral ({ BooleanLiteral.comments; _ } as e) -> + BooleanLiteral { e with BooleanLiteral.comments = merge_comments comments } + | NullLiteral comments -> NullLiteral (merge_comments comments) + | NumberLiteral ({ NumberLiteral.comments; _ } as e) -> + NumberLiteral { e with NumberLiteral.comments = merge_comments comments } + | BigIntLiteral ({ BigIntLiteral.comments; _ } as e) -> + BigIntLiteral { e with BigIntLiteral.comments = merge_comments comments } + | RegExpLiteral ({ RegExpLiteral.comments; _ } as e) -> + RegExpLiteral { e with RegExpLiteral.comments = merge_comments comments } + | Match ({ Match.comments; _ } as e) -> + Match { e with Match.comments = merge_comments comments } + | ModuleRefLiteral ({ ModuleRefLiteral.comments; _ } as e) -> + ModuleRefLiteral { e with ModuleRefLiteral.comments = merge_comments comments } + | Logical ({ Logical.comments; _ } as e) -> + Logical { e with Logical.comments = merge_comments comments } + | Member ({ Member.comments; _ } as e) -> + Member { e with Member.comments = merge_comments comments } + | MetaProperty ({ MetaProperty.comments; _ } as e) -> + MetaProperty { e with MetaProperty.comments = merge_comments comments } + | New ({ New.comments; _ } as e) -> New { e with New.comments = merge_comments comments } + | Object ({ Object.comments; _ } as e) -> + Object { e with Object.comments = merge_comments_with_internal comments } + | OptionalCall ({ OptionalCall.call = { Call.comments; _ } as call; _ } as optional_call) -> + OptionalCall + { + optional_call with + OptionalCall.call = { call with Call.comments = merge_comments comments }; + } + | OptionalMember + ({ OptionalMember.member = { Member.comments; _ } as member; _ } as optional_member) -> + OptionalMember + { + optional_member with + OptionalMember.member = { member with Member.comments = merge_comments comments }; + } + | Sequence ({ Sequence.comments; _ } as e) -> + Sequence { e with Sequence.comments = merge_comments comments } + | Super { Super.comments; _ } -> Super { Super.comments = merge_comments comments } + | TaggedTemplate ({ TaggedTemplate.comments; _ } as e) -> + TaggedTemplate { e with TaggedTemplate.comments = merge_comments comments } + | TemplateLiteral ({ TemplateLiteral.comments; _ } as e) -> + TemplateLiteral { e with TemplateLiteral.comments = merge_comments comments } + | This { This.comments; _ } -> This { This.comments = merge_comments comments } + | TSSatisfies ({ TSSatisfies.comments; _ } as e) -> + TSSatisfies { e with TSSatisfies.comments = merge_comments comments } + | TypeCast ({ TypeCast.comments; _ } as e) -> + TypeCast { e with TypeCast.comments = merge_comments comments } + | Unary ({ Unary.comments; _ } as e) -> + Unary { e with Unary.comments = merge_comments comments } + | Update ({ Update.comments; _ } as e) -> + Update { e with Update.comments = merge_comments comments } + | Yield ({ Yield.comments; _ } as e) -> + Yield { e with Yield.comments = merge_comments comments } + ) + + and array_initializer = + let rec elements env (acc, errs) = + match Peek.token env with + | T_EOF + | T_RBRACKET -> + (List.rev acc, Pattern_cover.rev_errors errs) + | T_COMMA -> + let loc = Peek.loc env in + Eat.token env; + elements env (Expression.Array.Hole loc :: acc, errs) + | T_ELLIPSIS -> + let leading = Peek.comments env in + let (loc, (argument, new_errs)) = + with_loc + (fun env -> + Eat.token env; + match assignment_cover env with + | Cover_expr argument -> (argument, Pattern_cover.empty_errors) + | Cover_patt (argument, new_errs) -> (argument, new_errs)) + env + in + let elem = + Expression.( + Array.Spread + ( loc, + SpreadElement.{ argument; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + ) + in + let is_last = Peek.token env = T_RBRACKET in + (* if this array is interpreted as a pattern, the spread becomes an AssignmentRestElement + which must be the last element. We can easily error about additional elements since + they will be in the element list, but a trailing elision, like `[...x,]`, is not part + of the AST. so, keep track of the error so we can raise it if this is a pattern. *) + let new_errs = + if (not is_last) && Peek.ith_token ~i:1 env = T_RBRACKET then + let if_patt = (loc, Parse_error.ElementAfterRestElement) :: new_errs.if_patt in + { new_errs with if_patt } + else + new_errs + in + if not is_last then Expect.token env T_COMMA; + let acc = elem :: acc in + let errs = Pattern_cover.rev_append_errors new_errs errs in + elements env (acc, errs) + | _ -> + let (elem, new_errs) = + match assignment_cover env with + | Cover_expr elem -> (elem, Pattern_cover.empty_errors) + | Cover_patt (elem, new_errs) -> (elem, new_errs) + in + if Peek.token env <> T_RBRACKET then Expect.token env T_COMMA; + let acc = Expression.Array.Expression elem :: acc in + let errs = Pattern_cover.rev_append_errors new_errs errs in + elements env (acc, errs) + in + fun env -> + let leading = Peek.comments env in + Expect.token env T_LBRACKET; + let (elems, errs) = elements env ([], Pattern_cover.empty_errors) in + let internal = Peek.comments env in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + ( { + Ast.Expression.Array.elements = elems; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }, + errs + ) + + and regexp env = + Eat.push_lex_mode env Lex_mode.REGEXP; + let loc = Peek.loc env in + let leading = Peek.comments env in + let tkn = Peek.token env in + let (raw, pattern, raw_flags, trailing) = + match tkn with + | T_REGEXP (_, pattern, flags) -> + Eat.token env; + let trailing = Eat.trailing_comments env in + let raw = "/" ^ pattern ^ "/" ^ flags in + (raw, pattern, flags, trailing) + | _ -> + error_unexpected ~expected:"a regular expression" env; + ("", "", "", []) + in + Eat.pop_lex_mode env; + let filtered_flags = Buffer.create (String.length raw_flags) in + String.iter + (function + | ('d' | 'g' | 'i' | 'm' | 's' | 'u' | 'y' | 'v') as c -> Buffer.add_char filtered_flags c + | _ -> ()) + raw_flags; + let flags = Buffer.contents filtered_flags in + if flags <> raw_flags then error env (Parse_error.InvalidRegExpFlags raw_flags); + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, Expression.RegExpLiteral { Ast.RegExpLiteral.pattern; flags; raw; comments }) + + and try_arrow_function = + (* Certain errors (almost all errors) cause a rollback *) + let error_callback _ = + Parse_error.( + function + (* Don't rollback on these errors. *) + | StrictParamDupe + | StrictParamName + | StrictReservedWord + | ParameterAfterRestParameter + | NewlineBeforeArrow + | AwaitAsIdentifierReference + | AwaitInAsyncFormalParameters + | YieldInFormalParameters + | ThisParamBannedInArrowFunctions -> + () + (* Everything else causes a rollback *) + | _ -> raise Try.Rollback + ) + in + let concise_function_body env = + match Peek.token env with + | T_LCURLY -> + let (body_block, contains_use_strict) = Parse.function_block_body env ~expression:true in + (Function.BodyBlock body_block, contains_use_strict) + | _ -> + let expr = Parse.assignment env in + (Function.BodyExpression expr, false) + in + fun env -> + let env = env |> with_error_callback error_callback in + let start_loc = Peek.loc env in + (* a T_ASYNC could either be a parameter name or it could be indicating + * that it's an async function *) + let (async, leading) = + if Peek.ith_token ~i:1 env <> T_ARROW then + Declaration.async env + else + (false, []) + in + + (* await is a keyword if this is an async function, or if we're in one already. *) + let await = async || allow_await env in + let env = with_allow_await await env in + + let yield = allow_yield env in + + let (sig_loc, (tparams, params, return, predicate)) = + with_loc + (fun env -> + let tparams = + type_params_remove_trailing env ~kind:Flow_ast_mapper.FunctionTP (Type.type_params env) + in + (* Disallow all fancy features for identifier => body *) + if Peek.is_identifier env && tparams = None then + let ((loc, _) as name) = + Parse.identifier ~restricted_error:Parse_error.StrictParamName env + in + let param = + ( loc, + { + Ast.Function.Param.argument = + ( loc, + Pattern.Identifier + { + Pattern.Identifier.name; + annot = Ast.Type.Missing (Peek.loc_skip_lookahead env); + optional = false; + } + ); + default = None; + } + ) + in + ( tparams, + ( loc, + { + Ast.Function.Params.params = [param]; + rest = None; + comments = None; + this_ = None; + } + ), + Ast.Function.ReturnAnnot.Missing Loc.{ loc with start = loc._end }, + None + ) + else + let params = Declaration.function_params ~await ~yield env in + + (* https://tc39.es/ecma262/#prod-ArrowFormalParameters *) + Declaration.check_unique_formal_parameters env params; + + (* There's an ambiguity if you use a function type as the return + * type for an arrow function. So we disallow anonymous function + * types in arrow function return types unless the function type is + * enclosed in parens *) + let (return, predicate) = + env + |> with_no_anon_function_type true + |> Type.function_return_annotation_and_predicate_opt + in + (tparams, params, return, predicate)) + env + in + (* It's hard to tell if an invalid expression was intended to be an + * arrow function before we see the =>. If there are no params, that + * implies "()" which is only ever found in arrow params. Similarly, + * rest params indicate arrow functions. Therefore, if we see a rest + * param or an empty param list then we can disable the rollback and + * instead generate errors as if we were parsing an arrow function *) + let env = + match params with + | (_, { Ast.Function.Params.params = _; rest = Some _; this_ = None; comments = _ }) + | (_, { Ast.Function.Params.params = []; rest = _; this_ = None; comments = _ }) -> + without_error_callback env + | _ -> env + in + + (* Disallow this param annotations in arrow functions *) + let params = + match params with + | (loc, ({ Ast.Function.Params.this_ = Some (this_loc, _); _ } as params)) -> + error_at env (this_loc, Parse_error.ThisParamBannedInArrowFunctions); + (loc, { params with Ast.Function.Params.this_ = None }) + | _ -> params + in + let simple_params = is_simple_parameter_list params in + + if Peek.is_line_terminator env && Peek.token env = T_ARROW then + error env Parse_error.NewlineBeforeArrow; + Expect.token env T_ARROW; + + (* Now we know for sure this is an arrow function *) + let env = without_error_callback env in + (* arrow functions can't be generators *) + let env = enter_function env ~async ~generator:false ~simple_params in + let (end_loc, (body, contains_use_strict)) = with_loc concise_function_body env in + Declaration.strict_function_post_check env ~contains_use_strict None params; + let loc = Loc.btwn start_loc end_loc in + Cover_expr + ( loc, + let open Expression in + ArrowFunction + { + Function.id = None; + params; + body; + async; + generator = false; + (* arrow functions cannot be generators *) + effect_ = Function.Arbitrary; + predicate; + return; + tparams; + sig_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + + and sequence = + let rec helper acc env = + match Peek.token env with + | T_COMMA -> + Eat.token env; + let expr = assignment env in + helper (expr :: acc) env + | _ -> + let expressions = List.rev acc in + Expression.(Sequence Sequence.{ expressions; comments = None }) + in + (fun env ~start_loc acc -> with_loc ~start_loc (helper acc) env) +end diff --git a/compiler/flow_parser/parser/expression_parser.mli b/compiler/flow_parser/parser/expression_parser.mli new file mode 100644 index 00000000000..331c7ab6032 --- /dev/null +++ b/compiler/flow_parser/parser/expression_parser.mli @@ -0,0 +1,12 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Expression + (_ : Parser_common.PARSER) + (_ : Parser_common.TYPE) + (_ : Parser_common.DECLARATION) + (_ : Parser_common.COVER) : Parser_common.EXPRESSION diff --git a/compiler/flow_parser/parser/file_key.ml b/compiler/flow_parser/parser/file_key.ml new file mode 100644 index 00000000000..fb51ba41177 --- /dev/null +++ b/compiler/flow_parser/parser/file_key.ml @@ -0,0 +1,78 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type t = + | LibFile of string + | SourceFile of string + | JsonFile of string + (* A resource that might get required, like .css, .jpg, etc. We don't parse + these, just check that they exist *) + | ResourceFile of string +[@@deriving show, eq] + +let to_string = function + | LibFile x + | SourceFile x + | JsonFile x + | ResourceFile x -> + x + +let to_path = function + | LibFile x + | SourceFile x + | JsonFile x + | ResourceFile x -> + Ok x + +let compare = + (* libs, then source and json files at the same priority since JSON files are + * basically source files. We don't actually read resource files so they come + * last *) + let order_of_filename = function + | LibFile _ -> 1 + | SourceFile _ -> 2 + | JsonFile _ -> 2 + | ResourceFile _ -> 3 + in + fun a b -> + let k = order_of_filename a - order_of_filename b in + if k <> 0 then + k + else + String.compare (to_string a) (to_string b) + +let compare_opt a b = + match (a, b) with + | (Some _, None) -> -1 + | (None, Some _) -> 1 + | (None, None) -> 0 + | (Some a, Some b) -> compare a b + +let is_lib_file = function + | LibFile _ -> true + | SourceFile _ -> false + | JsonFile _ -> false + | ResourceFile _ -> false + +let map f = function + | LibFile filename -> LibFile (f filename) + | SourceFile filename -> SourceFile (f filename) + | JsonFile filename -> JsonFile (f filename) + | ResourceFile filename -> ResourceFile (f filename) + +let exists f = function + | LibFile filename + | SourceFile filename + | JsonFile filename + | ResourceFile filename -> + f filename + +let check_suffix filename suffix = exists (fun fn -> Filename.check_suffix fn suffix) filename + +let chop_suffix filename suffix = map (fun fn -> Filename.chop_suffix fn suffix) filename + +let with_suffix filename suffix = map (fun fn -> fn ^ suffix) filename diff --git a/compiler/flow_parser/parser/flow_ast.ml b/compiler/flow_parser/parser/flow_ast.ml new file mode 100644 index 00000000000..373e4d85e53 --- /dev/null +++ b/compiler/flow_parser/parser/flow_ast.ml @@ -0,0 +1,2423 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +[%%gen +module rec Syntax : sig + type ('M, 'internal) t = { + leading: 'M Comment.t list; + trailing: 'M Comment.t list; + internal: 'internal; + } + [@@deriving show] +end = + Syntax + +and Identifier : sig + type ('M, 'T) t = 'T * 'M t' + + and 'M t' = { + name: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + Identifier + +and PrivateName : sig + type 'M t = 'M * 'M t' + + and 'M t' = { + name: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + PrivateName + +and StringLiteral : sig + type 'M t = { + value: string; + raw: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + StringLiteral + +and NumberLiteral : sig + type 'M t = { + value: float; + raw: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + NumberLiteral + +and BigIntLiteral : sig + type 'M t = { + (* This will be None if we couldn't parse `raw`. That could be if the number is out of range or invalid (like a float) *) + value: int64 option; + raw: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + BigIntLiteral + +and BooleanLiteral : sig + type 'M t = { + value: bool; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + BooleanLiteral + +and RegExpLiteral : sig + type 'M t = { + pattern: string; + flags: string; + raw: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + RegExpLiteral + +and ModuleRefLiteral : sig + type ('M, 'T) t = { + value: string; + require_loc: 'M; + def_loc_opt: 'M option; + prefix_len: int; + legacy_interop: bool; + raw: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + ModuleRefLiteral + +and Variance : sig + type 'M t = 'M * 'M t' + + and kind = + | Plus + | Minus + | Readonly + | In + | Out + | InOut + + and 'M t' = { + kind: kind; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + Variance + +and ComputedKey : sig + type ('M, 'T) t = 'M * ('M, 'T) ComputedKey.t' + + and ('M, 'T) t' = { + expression: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + ComputedKey + +and Variable : sig + type kind = + | Var + | Let + | Const + [@@deriving show] +end = + Variable + +and Type : sig + module Conditional : sig + type ('M, 'T) t = { + check_type: ('M, 'T) Type.t; + extends_type: ('M, 'T) Type.t; + true_type: ('M, 'T) Type.t; + false_type: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Infer : sig + type ('M, 'T) t = { + tparam: ('M, 'T) Type.TypeParam.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Function : sig + module Param : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + name: ('M, 'T) Identifier.t option; + annot: ('M, 'T) Type.t; + optional: bool; + } + [@@deriving show] + end + + module RestParam : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Param.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module ThisParam : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + annot: ('M, 'T) Type.annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Params : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + this_: ('M, 'T) ThisParam.t option; + params: ('M, 'T) Param.t list; + rest: ('M, 'T) RestParam.t option; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + tparams: ('M, 'T) Type.TypeParams.t option; + params: ('M, 'T) Params.t; + return: ('M, 'T) return_annotation; + comments: ('M, unit) Syntax.t option; + effect_: Function.effect_; + } + + and ('M, 'T) return_annotation = + | TypeAnnotation of ('M, 'T) Type.t + | TypeGuard of ('M, 'T) Type.TypeGuard.t + [@@deriving show] + end + + module Component : sig + module Param : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + name: ('M, 'T) Statement.ComponentDeclaration.Param.param_name; + annot: ('M, 'T) Type.annotation; + optional: bool; + } + [@@deriving show] + end + + module RestParam : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Identifier.t option; + annot: ('M, 'T) Type.t; + optional: bool; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Params : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + params: ('M, 'T) Param.t list; + rest: ('M, 'T) RestParam.t option; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + tparams: ('M, 'T) Type.TypeParams.t option; + params: ('M, 'T) Params.t; + renders: ('M, 'T) Type.component_renders_annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Generic : sig + module Identifier : sig + type ('M, 'T) t = + | Unqualified of ('M, 'T) Identifier.t + | Qualified of ('M, 'T) qualified + + and ('M, 'T) qualified = 'M * ('M, 'T) qualified' + + and ('M, 'T) qualified' = { + qualification: ('M, 'T) t; + id: ('M, 'T) Identifier.t; + } + [@@deriving show] + end + + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + targs: ('M, 'T) Type.TypeArgs.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module IndexedAccess : sig + type ('M, 'T) t = { + _object: ('M, 'T) Type.t; + index: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module OptionalIndexedAccess : sig + type ('M, 'T) t = { + indexed_access: ('M, 'T) IndexedAccess.t; + optional: bool; + } + [@@deriving show] + end + + module Object : sig + module Property : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + key: ('M, 'T) Expression.Object.Property.key; + value: ('M, 'T) value; + optional: bool; + static: bool; + proto: bool; + _method: bool; + variance: 'M Variance.t option; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) value = + | Init of ('M, 'T) Type.t + | Get of ('M * ('M, 'T) Function.t) + | Set of ('M * ('M, 'T) Function.t) + [@@deriving show] + end + + module SpreadProperty : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Indexer : sig + type ('M, 'T) t' = { + id: ('M, 'M) Identifier.t option; + key: ('M, 'T) Type.t; + value: ('M, 'T) Type.t; + static: bool; + variance: 'M Variance.t option; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) t = 'M * ('M, 'T) t' [@@deriving show] + end + + module MappedType : sig + (* PlusOptional = +?, MinusOptional = -?, Optional = ?, NoOptionalFlag = blank *) + type optional_flag = + | PlusOptional + | MinusOptional + | Optional + | NoOptionalFlag + [@@deriving show] + + type ('M, 'T) t' = { + key_tparam: ('M, 'T) Type.TypeParam.t; + prop_type: ('M, 'T) Type.t; + source_type: ('M, 'T) Type.t; + variance: 'M Variance.t option; + optional: optional_flag; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) t = 'M * ('M, 'T) t' [@@deriving show] + end + + module CallProperty : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + value: 'M * ('M, 'T) Function.t; + static: bool; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module InternalSlot : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + id: ('M, 'M) Identifier.t; + value: ('M, 'T) Type.t; + optional: bool; + static: bool; + _method: bool; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + exact: bool; + (* Inexact indicates the presence of ... in the object. It is more + * easily understood if exact is read as "explicitly exact" and "inexact" + * is read as "explicitly inexact". + * + * This confusion will go away when we get rid of the exact flag in favor + * of inexact as part of the work to make object types exact by default. + * *) + inexact: bool; + properties: ('M, 'T) property list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + + and ('M, 'T) property = + | Property of ('M, 'T) Property.t + | SpreadProperty of ('M, 'T) SpreadProperty.t + | Indexer of ('M, 'T) Indexer.t + | CallProperty of ('M, 'T) CallProperty.t + | InternalSlot of ('M, 'T) InternalSlot.t + | MappedType of ('M, 'T) MappedType.t + [@@deriving show] + end + + module Interface : sig + type ('M, 'T) t = { + body: 'M * ('M, 'T) Object.t; + extends: ('M * ('M, 'T) Generic.t) list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Nullable : sig + type ('M, 'T) t = { + argument: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Typeof : sig + module Target : sig + type ('M, 'T) t = + | Unqualified of ('M, 'T) Identifier.t + | Qualified of ('M, 'T) qualified + + and ('M, 'T) qualified' = { + qualification: ('M, 'T) t; + id: ('M, 'T) Identifier.t; + } + + and ('M, 'T) qualified = 'T * ('M, 'T) qualified' [@@deriving show] + end + + type ('M, 'T) t = { + argument: ('M, 'T) Target.t; + targs: ('M, 'T) Type.TypeArgs.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Keyof : sig + type ('M, 'T) t = { + argument: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Renders : sig + type variant = + | Normal + | Maybe + | Star + [@@deriving show] + + type ('M, 'T) t = { + operator_loc: 'M; + argument: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + variant: variant; + } + [@@deriving show] + end + + module ReadOnly : sig + type ('M, 'T) t = { + argument: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Tuple : sig + module LabeledElement : sig + type ('M, 'T) t = { + name: ('M, 'T) Identifier.t; + annot: ('M, 'T) Type.t; + variance: 'M Variance.t option; + optional: bool; + } + [@@deriving show] + end + + module SpreadElement : sig + type ('M, 'T) t = { + name: ('M, 'T) Identifier.t option; + annot: ('M, 'T) Type.t; + } + [@@deriving show] + end + + type ('M, 'T) element = 'M * ('M, 'T) element' [@@deriving show] + + and ('M, 'T) element' = + | UnlabeledElement of ('M, 'T) Type.t + | LabeledElement of ('M, 'T) LabeledElement.t + | SpreadElement of ('M, 'T) SpreadElement.t + [@@deriving show] + + and ('M, 'T) t = { + elements: ('M, 'T) element list; + inexact: bool; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Array : sig + type ('M, 'T) t = { + argument: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Union : sig + type ('M, 'T) t = { + types: ('M, 'T) Type.t * ('M, 'T) Type.t * ('M, 'T) Type.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Intersection : sig + type ('M, 'T) t = { + types: ('M, 'T) Type.t * ('M, 'T) Type.t * ('M, 'T) Type.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = 'T * ('M, 'T) t' + + (* Yes, we could add a little complexity here to show that Any and Void + * should never be declared nullable, but that check can happen later *) + and ('M, 'T) t' = + | Any of ('M, unit) Syntax.t option + | Mixed of ('M, unit) Syntax.t option + | Empty of ('M, unit) Syntax.t option + | Void of ('M, unit) Syntax.t option + | Null of ('M, unit) Syntax.t option + | Number of ('M, unit) Syntax.t option + | BigInt of ('M, unit) Syntax.t option + | String of ('M, unit) Syntax.t option + | Boolean of { + raw: [ `Boolean | `Bool ]; + comments: ('M, unit) Syntax.t option; + } + | Symbol of ('M, unit) Syntax.t option + | Exists of ('M, unit) Syntax.t option + | Nullable of ('M, 'T) Nullable.t + | Function of ('M, 'T) Function.t + | Component of ('M, 'T) Component.t + | Object of ('M, 'T) Object.t + | Interface of ('M, 'T) Interface.t + | Array of ('M, 'T) Array.t + | Conditional of ('M, 'T) Conditional.t + | Infer of ('M, 'T) Infer.t + | Generic of ('M, 'T) Generic.t + | IndexedAccess of ('M, 'T) IndexedAccess.t + | OptionalIndexedAccess of ('M, 'T) OptionalIndexedAccess.t + | Union of ('M, 'T) Union.t + | Intersection of ('M, 'T) Intersection.t + | Typeof of ('M, 'T) Typeof.t + | Keyof of ('M, 'T) Keyof.t + | Renders of ('M, 'T) Renders.t + | ReadOnly of ('M, 'T) ReadOnly.t + | Tuple of ('M, 'T) Tuple.t + | StringLiteral of 'M StringLiteral.t + | NumberLiteral of 'M NumberLiteral.t + | BigIntLiteral of 'M BigIntLiteral.t + | BooleanLiteral of 'M BooleanLiteral.t + | Unknown of ('M, unit) Syntax.t option + | Never of ('M, unit) Syntax.t option + | Undefined of ('M, unit) Syntax.t option + + (* Type.annotation is a concrete syntax node with a location that starts at + * the colon and ends after the type. For example, "var a: number", the + * identifier a would have a property annot which contains a + * Type.annotation with a location from column 6-14 *) + and ('M, 'T) annotation = 'M * ('M, 'T) t + + (* Same convention about the colon holds for type guards. *) + and ('M, 'T) type_guard_annotation = 'M * ('M, 'T) Type.TypeGuard.t + + and ('M, 'T) annotation_or_hint = + | Missing of 'T + | Available of ('M, 'T) Type.annotation + [@@deriving show] + + and ('M, 'T) component_renders_annotation = + | MissingRenders of 'T + | AvailableRenders of 'M * ('M, 'T) Type.Renders.t + [@@deriving show] + + module TypeParam : sig + type bound_kind = + | Colon + | Extends + [@@deriving show] + + module ConstModifier : sig + type 'M t = 'M * ('M, unit) Syntax.t option [@@deriving show] + end + + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + name: ('M, 'M) Identifier.t; + bound: ('M, 'T) Type.annotation_or_hint; + bound_kind: bound_kind; + variance: 'M Variance.t option; + default: ('M, 'T) Type.t option; + const: 'M ConstModifier.t option; + } + [@@deriving show] + end + + module TypeParams : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + params: ('M, 'T) TypeParam.t list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module TypeArgs : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + arguments: ('M, 'T) Type.t list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module Predicate : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + kind: ('M, 'T) kind; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) kind = + | Declared of ('M, 'T) Expression.t + | Inferred + [@@deriving show] + end + + module TypeGuard : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and kind = + | Default + | Asserts + | Implies + + and ('M, 'T) t' = { + kind: kind; + guard: ('M, 'M) Identifier.t * ('M, 'T) Type.t option; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end +end = + Type + +and Statement : sig + module Block : sig + type ('M, 'T) t = { + body: ('M, 'T) Statement.t list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module If : sig + module Alternate : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + body: ('M, 'T) Statement.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + test: ('M, 'T) Expression.t; + consequent: ('M, 'T) Statement.t; + alternate: ('M, 'T) Alternate.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Labeled : sig + type ('M, 'T) t = { + label: ('M, 'M) Identifier.t; + body: ('M, 'T) Statement.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Break : sig + type 'M t = { + label: ('M, 'M) Identifier.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Continue : sig + type 'M t = { + label: ('M, 'M) Identifier.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Debugger : sig + type 'M t = { comments: ('M, unit) Syntax.t option } [@@deriving show] + end + + module With : sig + type ('M, 'T) t = { + _object: ('M, 'T) Expression.t; + body: ('M, 'T) Statement.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module TypeAlias : sig + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + tparams: ('M, 'T) Type.TypeParams.t option; + right: ('M, 'T) Type.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module OpaqueType : sig + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + tparams: ('M, 'T) Type.TypeParams.t option; + impltype: ('M, 'T) Type.t option; + supertype: ('M, 'T) Type.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) match_statement = ('M, 'T, ('M, 'T) Statement.t) Match.t [@@deriving show] + + module Switch : sig + module Case : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + test: ('M, 'T) Expression.t option; + consequent: ('M, 'T) Statement.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + discriminant: ('M, 'T) Expression.t; + cases: ('M, 'T) Case.t list; + comments: ('M, unit) Syntax.t option; + exhaustive_out: 'T; + } + [@@deriving show] + end + + module Return : sig + type ('M, 'T) t = { + argument: ('M, 'T) Expression.t option; + comments: ('M, unit) Syntax.t option; + return_out: 'T; + } + [@@deriving show] + end + + module Throw : sig + type ('M, 'T) t = { + argument: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Try : sig + module CatchClause : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + param: ('M, 'T) Pattern.t option; + body: 'M * ('M, 'T) Block.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + block: 'M * ('M, 'T) Block.t; + handler: ('M, 'T) CatchClause.t option; + finalizer: ('M * ('M, 'T) Block.t) option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module VariableDeclaration : sig + module Declarator : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + id: ('M, 'T) Pattern.t; + init: ('M, 'T) Expression.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + declarations: ('M, 'T) Declarator.t list; + kind: Variable.kind; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module While : sig + type ('M, 'T) t = { + test: ('M, 'T) Expression.t; + body: ('M, 'T) Statement.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DoWhile : sig + type ('M, 'T) t = { + body: ('M, 'T) Statement.t; + test: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module For : sig + type ('M, 'T) t = { + init: ('M, 'T) init option; + test: ('M, 'T) Expression.t option; + update: ('M, 'T) Expression.t option; + body: ('M, 'T) Statement.t; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) init = + | InitDeclaration of ('M * ('M, 'T) VariableDeclaration.t) + | InitExpression of ('M, 'T) Expression.t + [@@deriving show] + end + + module ForIn : sig + type ('M, 'T) t = { + left: ('M, 'T) left; + right: ('M, 'T) Expression.t; + body: ('M, 'T) Statement.t; + each: bool; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) left = + | LeftDeclaration of ('M * ('M, 'T) VariableDeclaration.t) + | LeftPattern of ('M, 'T) Pattern.t + [@@deriving show] + end + + module ForOf : sig + type ('M, 'T) t = { + left: ('M, 'T) left; + right: ('M, 'T) Expression.t; + body: ('M, 'T) Statement.t; + await: bool; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) left = + | LeftDeclaration of ('M * ('M, 'T) VariableDeclaration.t) + | LeftPattern of ('M, 'T) Pattern.t + [@@deriving show] + end + + module EnumDeclaration : sig + module DefaultedMember : sig + type 'M t = 'M * 'M t' + + and 'M t' = { id: ('M, 'M) Identifier.t } [@@deriving show] + end + + module InitializedMember : sig + type ('I, 'M) t = 'M * ('I, 'M) t' + + and ('I, 'M) t' = { + id: ('M, 'M) Identifier.t; + init: 'M * 'I; + } + [@@deriving show] + end + + module BooleanBody : sig + type 'M t = { + members: ('M BooleanLiteral.t, 'M) InitializedMember.t list; + explicit_type: bool; + has_unknown_members: bool; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module NumberBody : sig + type 'M t = { + members: ('M NumberLiteral.t, 'M) InitializedMember.t list; + explicit_type: bool; + has_unknown_members: bool; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module StringBody : sig + type 'M t = { + members: ('M StringLiteral.t, 'M) members; + explicit_type: bool; + has_unknown_members: bool; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + + and ('I, 'M) members = + | Defaulted of 'M DefaultedMember.t list + | Initialized of ('I, 'M) InitializedMember.t list + [@@deriving show] + end + + module SymbolBody : sig + type 'M t = { + members: 'M DefaultedMember.t list; + has_unknown_members: bool; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module BigIntBody : sig + type 'M t = { + members: ('M BigIntLiteral.t, 'M) InitializedMember.t list; + explicit_type: bool; + has_unknown_members: bool; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + body: 'M body; + comments: ('M, unit) Syntax.t option; + } + + and 'M body = 'M * 'M body' + + and 'M body' = + | BooleanBody of 'M BooleanBody.t + | NumberBody of 'M NumberBody.t + | StringBody of 'M StringBody.t + | SymbolBody of 'M SymbolBody.t + | BigIntBody of 'M BigIntBody.t + [@@deriving show] + end + + module ComponentDeclaration : sig + module RestParam : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Pattern.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Param : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + (* Name should only be an Identifier or StringLiteral. However, we allow parsing + it as an option to have better error messages. *) + name: ('M, 'T) param_name; + local: ('M, 'T) Pattern.t; + default: ('M, 'T) Expression.t option; + shorthand: bool; + } + + and ('M, 'T) param_name = + | Identifier of ('M, 'T) Identifier.t + | StringLiteral of ('M * 'M StringLiteral.t) + [@@deriving show] + end + + module Params : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + params: ('M, 'T) Param.t list; + rest: ('M, 'T) RestParam.t option; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + tparams: ('M, 'T) Type.TypeParams.t option; + params: ('M, 'T) Params.t; + renders: ('M, 'T) Type.component_renders_annotation; + body: 'M * ('M, 'T) Statement.Block.t; + comments: ('M, unit) Syntax.t option; + (* Location of the signature portion of a component, e.g. + * component Foo(): void {} + * ^^^^^^^^^^^^^^^^^^^^ + *) + sig_loc: 'M; + } + [@@deriving show] + end + + module Interface : sig + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + tparams: ('M, 'T) Type.TypeParams.t option; + extends: ('M * ('M, 'T) Type.Generic.t) list; + body: 'M * ('M, 'T) Type.Object.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DeclareClass : sig + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + tparams: ('M, 'T) Type.TypeParams.t option; + body: 'M * ('M, 'T) Type.Object.t; + extends: ('M * ('M, 'T) Type.Generic.t) option; + mixins: ('M * ('M, 'T) Type.Generic.t) list; + implements: ('M, 'T) Class.Implements.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DeclareComponent : sig + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + tparams: ('M, 'T) Type.TypeParams.t option; + params: ('M, 'T) Type.Component.Params.t; + renders: ('M, 'T) Type.component_renders_annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DeclareVariable : sig + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + annot: ('M, 'T) Type.annotation; + kind: Variable.kind; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DeclareFunction : sig + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t; + annot: ('M, 'T) Type.annotation; + predicate: ('M, 'T) Type.Predicate.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DeclareModule : sig + type ('M, 'T) id = + | Identifier of ('M, 'T) Identifier.t + | Literal of ('T * 'M StringLiteral.t) + + and ('M, 'T) t = { + id: ('M, 'T) id; + body: 'M * ('M, 'T) Block.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DeclareModuleExports : sig + type ('M, 'T) t = { + annot: ('M, 'T) Type.annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module DeclareNamespace : sig + type ('M, 'T) id = + | Global of ('M, 'M) Identifier.t + | Local of ('M, 'T) Identifier.t + [@@deriving show] + + type ('M, 'T) t = { + id: ('M, 'T) id; + body: 'M * ('M, 'T) Block.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module ExportNamedDeclaration : sig + module ExportSpecifier : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + local: ('M, 'T) Identifier.t; + exported: ('M, 'T) Identifier.t option; + from_remote: bool; + (* Imported name's definition location. It will be populated only in typed AST for `export {foo} from '...'`. *) + imported_name_def_loc: 'M option; + } + [@@deriving show] + end + + module ExportBatchSpecifier : sig + type ('M, 'T) t = 'M * ('M, 'T) Identifier.t option [@@deriving show] + end + + type ('M, 'T) t = { + declaration: ('M, 'T) Statement.t option; + specifiers: ('M, 'T) specifier option; + source: ('T * 'M StringLiteral.t) option; + export_kind: Statement.export_kind; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) specifier = + | ExportSpecifiers of ('M, 'T) ExportSpecifier.t list + | ExportBatchSpecifier of ('M, 'T) ExportBatchSpecifier.t + [@@deriving show] + end + + module ExportDefaultDeclaration : sig + type ('M, 'T) t = { + default: 'T; + declaration: ('M, 'T) declaration; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) declaration = + | Declaration of ('M, 'T) Statement.t + | Expression of ('M, 'T) Expression.t + [@@deriving show] + end + + module DeclareExportDeclaration : sig + type ('M, 'T) declaration = + (* declare export var *) + | Variable of ('M * ('M, 'T) DeclareVariable.t) + (* declare export function *) + | Function of ('M * ('M, 'T) DeclareFunction.t) + (* declare export class *) + | Class of ('M * ('M, 'T) DeclareClass.t) + (* declare export component *) + | Component of ('M * ('M, 'T) DeclareComponent.t) + (* declare export default [type] + * this corresponds to things like + * export default 1+1; *) + | DefaultType of ('M, 'T) Type.t + (* declare export type *) + | NamedType of ('M * ('M, 'T) TypeAlias.t) + (* declare export opaque type *) + | NamedOpaqueType of ('M * ('M, 'T) OpaqueType.t) + (* declare export interface *) + | Interface of ('M * ('M, 'T) Interface.t) + (* declare export enum *) + | Enum of ('M * ('M, 'T) EnumDeclaration.t) + + and ('M, 'T) t = { + default: 'M option; + declaration: ('M, 'T) declaration option; + specifiers: ('M, 'T) ExportNamedDeclaration.specifier option; + source: ('T * 'M StringLiteral.t) option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module ImportDeclaration : sig + type import_kind = + | ImportType + | ImportTypeof + | ImportValue + + and ('M, 'T) specifier = + | ImportNamedSpecifiers of ('M, 'T) named_specifier list + | ImportNamespaceSpecifier of ('M * ('M, 'T) Identifier.t) + + and ('M, 'T) named_specifier = { + kind: import_kind option; + local: ('M, 'T) Identifier.t option; + remote: ('M, 'T) Identifier.t; + (* Remote name's definition location. It will be populated only in typed AST. *) + remote_name_def_loc: 'M option; + } + + and ('M, 'T) default_identifier = { + identifier: ('M, 'T) Identifier.t; + (* Remote name's definition location. It will be populated only in typed AST. *) + remote_default_name_def_loc: 'M option; + } + + and ('M, 'T) t = { + import_kind: import_kind; + source: 'T * 'M StringLiteral.t; + default: ('M, 'T) default_identifier option; + specifiers: ('M, 'T) specifier option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Expression : sig + type ('M, 'T) t = { + expression: ('M, 'T) Expression.t; + directive: string option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Empty : sig + type 'M t = { comments: ('M, unit) Syntax.t option } [@@deriving show] + end + + type export_kind = + | ExportType + | ExportValue + + and ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = + | Block of ('M, 'T) Block.t + | Break of 'M Break.t + | ClassDeclaration of ('M, 'T) Class.t + | ComponentDeclaration of ('M, 'T) ComponentDeclaration.t + | Continue of 'M Continue.t + | Debugger of 'M Debugger.t + | DeclareClass of ('M, 'T) DeclareClass.t + | DeclareComponent of ('M, 'T) DeclareComponent.t + | DeclareEnum of ('M, 'T) EnumDeclaration.t + | DeclareExportDeclaration of ('M, 'T) DeclareExportDeclaration.t + | DeclareFunction of ('M, 'T) DeclareFunction.t + | DeclareInterface of ('M, 'T) Interface.t + | DeclareModule of ('M, 'T) DeclareModule.t + | DeclareModuleExports of ('M, 'T) DeclareModuleExports.t + | DeclareNamespace of ('M, 'T) DeclareNamespace.t + | DeclareTypeAlias of ('M, 'T) TypeAlias.t + | DeclareOpaqueType of ('M, 'T) OpaqueType.t + | DeclareVariable of ('M, 'T) DeclareVariable.t + | DoWhile of ('M, 'T) DoWhile.t + | Empty of 'M Empty.t + | EnumDeclaration of ('M, 'T) EnumDeclaration.t + | ExportDefaultDeclaration of ('M, 'T) ExportDefaultDeclaration.t + | ExportNamedDeclaration of ('M, 'T) ExportNamedDeclaration.t + | Expression of ('M, 'T) Expression.t + | For of ('M, 'T) For.t + | ForIn of ('M, 'T) ForIn.t + | ForOf of ('M, 'T) ForOf.t + | FunctionDeclaration of ('M, 'T) Function.t + | If of ('M, 'T) If.t + | ImportDeclaration of ('M, 'T) ImportDeclaration.t + | InterfaceDeclaration of ('M, 'T) Interface.t + | Labeled of ('M, 'T) Labeled.t + | Match of ('M, 'T) match_statement + | Return of ('M, 'T) Return.t + | Switch of ('M, 'T) Switch.t + | Throw of ('M, 'T) Throw.t + | Try of ('M, 'T) Try.t + | TypeAlias of ('M, 'T) TypeAlias.t + | OpaqueType of ('M, 'T) OpaqueType.t + | VariableDeclaration of ('M, 'T) VariableDeclaration.t + | While of ('M, 'T) While.t + | With of ('M, 'T) With.t + [@@deriving show] +end = + Statement + +and Expression : sig + module CallTypeArg : sig + module Implicit : sig + type ('M, 'T) t = 'T * 'M t' + + and 'M t' = { comments: ('M, unit) Syntax.t option } [@@deriving show] + end + + type ('M, 'T) t = + | Explicit of ('M, 'T) Type.t + | Implicit of ('M, 'T) Implicit.t + [@@deriving show] + end + + module CallTypeArgs : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + arguments: ('M, 'T) CallTypeArg.t list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module SpreadElement : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Array : sig + type ('M, 'T) element = + | Expression of ('M, 'T) Expression.t + | Spread of ('M, 'T) SpreadElement.t + | Hole of 'M + [@@deriving show] + + type ('M, 'T) t = { + elements: ('M, 'T) element list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module TemplateLiteral : sig + module Element : sig + type value = { + raw: string; + cooked: string; + } + + and 'M t = 'M * t' + + and t' = { + value: value; + tail: bool; + } + [@@deriving show] + end + + type ('M, 'T) t = { + quasis: 'M Element.t list; + expressions: ('M, 'T) Expression.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module TaggedTemplate : sig + type ('M, 'T) t = { + tag: ('M, 'T) Expression.t; + quasi: 'M * ('M, 'T) TemplateLiteral.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Object : sig + module Property : sig + type ('M, 'T) key = + | StringLiteral of ('T * 'M StringLiteral.t) + | NumberLiteral of ('T * 'M NumberLiteral.t) + | BigIntLiteral of ('T * 'M BigIntLiteral.t) + | Identifier of ('M, 'T) Identifier.t + | PrivateName of 'M PrivateName.t + | Computed of ('M, 'T) ComputedKey.t + + and ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = + | Init of { + key: ('M, 'T) key; + value: ('M, 'T) Expression.t; + shorthand: bool; + } + | Method of { + key: ('M, 'T) key; + value: 'M * ('M, 'T) Function.t; + } + | Get of { + key: ('M, 'T) key; + value: 'M * ('M, 'T) Function.t; + comments: ('M, unit) Syntax.t option; + } + | Set of { + key: ('M, 'T) key; + value: 'M * ('M, 'T) Function.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module SpreadProperty : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) property = + | Property of ('M, 'T) Property.t + | SpreadProperty of ('M, 'T) SpreadProperty.t + + and ('M, 'T) t = { + properties: ('M, 'T) property list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module Sequence : sig + type ('M, 'T) t = { + expressions: ('M, 'T) Expression.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Unary : sig + type operator = + | Minus + | Plus + | Not + | BitNot + | Typeof + | Void + | Delete + | Await + + and ('M, 'T) t = { + operator: operator; + argument: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Binary : sig + type operator = + | Equal + | NotEqual + | StrictEqual + | StrictNotEqual + | LessThan + | LessThanEqual + | GreaterThan + | GreaterThanEqual + | LShift + | RShift + | RShift3 + | Plus + | Minus + | Mult + | Exp + | Div + | Mod + | BitOr + | Xor + | BitAnd + | In + | Instanceof + + and ('M, 'T) t = { + operator: operator; + left: ('M, 'T) Expression.t; + right: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Assignment : sig + type operator = + | PlusAssign + | MinusAssign + | MultAssign + | ExpAssign + | DivAssign + | ModAssign + | LShiftAssign + | RShiftAssign + | RShift3Assign + | BitOrAssign + | BitXorAssign + | BitAndAssign + | NullishAssign + | AndAssign + | OrAssign + + and ('M, 'T) t = { + operator: operator option; + left: ('M, 'T) Pattern.t; + right: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Update : sig + type operator = + | Increment + | Decrement + + and ('M, 'T) t = { + operator: operator; + argument: ('M, 'T) Expression.t; + prefix: bool; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Logical : sig + type operator = + | Or + | And + | NullishCoalesce + + and ('M, 'T) t = { + operator: operator; + left: ('M, 'T) Expression.t; + right: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Conditional : sig + type ('M, 'T) t = { + test: ('M, 'T) Expression.t; + consequent: ('M, 'T) Expression.t; + alternate: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) expression_or_spread = + | Expression of ('M, 'T) Expression.t + | Spread of ('M, 'T) SpreadElement.t + [@@deriving show] + + module ArgList : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + arguments: ('M, 'T) expression_or_spread list; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module New : sig + type ('M, 'T) t = { + callee: ('M, 'T) Expression.t; + targs: ('M, 'T) Expression.CallTypeArgs.t option; + arguments: ('M, 'T) ArgList.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Call : sig + type ('M, 'T) t = { + callee: ('M, 'T) Expression.t; + targs: ('M, 'T) Expression.CallTypeArgs.t option; + arguments: ('M, 'T) ArgList.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module OptionalCall : sig + type ('M, 'T) t = { + call: ('M, 'T) Call.t; + filtered_out: 'T; + optional: bool; + } + [@@deriving show] + end + + module Member : sig + type ('M, 'T) property = + | PropertyIdentifier of ('M, 'T) Identifier.t + | PropertyPrivateName of 'M PrivateName.t + | PropertyExpression of ('M, 'T) Expression.t + + and ('M, 'T) t = { + _object: ('M, 'T) Expression.t; + property: ('M, 'T) property; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module OptionalMember : sig + type ('M, 'T) t = { + member: ('M, 'T) Member.t; + filtered_out: 'T; + optional: bool; + } + [@@deriving show] + end + + module Yield : sig + type ('M, 'T) t = { + argument: ('M, 'T) Expression.t option; + comments: ('M, unit) Syntax.t option; + delegate: bool; + result_out: 'T; + } + [@@deriving show] + end + + module TypeCast : sig + type ('M, 'T) t = { + expression: ('M, 'T) Expression.t; + annot: ('M, 'T) Type.annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module AsExpression : sig + type ('M, 'T) t = { + expression: ('M, 'T) Expression.t; + annot: ('M, 'T) Type.annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module AsConstExpression : sig + type ('M, 'T) t = { + expression: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module TSSatisfies : sig + type ('M, 'T) t = { + expression: ('M, 'T) Expression.t; + annot: ('M, 'T) Type.annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module MetaProperty : sig + type 'M t = { + meta: ('M, 'M) Identifier.t; + property: ('M, 'M) Identifier.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module This : sig + type 'M t = { comments: ('M, unit) Syntax.t option } [@@deriving show] + end + + module Super : sig + type 'M t = { comments: ('M, unit) Syntax.t option } [@@deriving show] + end + + module Import : sig + type ('M, 'T) t = { + argument: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) match_expression = ('M, 'T, ('M, 'T) Expression.t) Match.t [@@deriving show] + + type ('M, 'T) t = 'T * ('M, 'T) t' + + and ('M, 'T) t' = + | Array of ('M, 'T) Array.t + | ArrowFunction of ('M, 'T) Function.t + | AsConstExpression of ('M, 'T) AsConstExpression.t + | AsExpression of ('M, 'T) AsExpression.t + | Assignment of ('M, 'T) Assignment.t + | Binary of ('M, 'T) Binary.t + | Call of ('M, 'T) Call.t + | Class of ('M, 'T) Class.t + | Conditional of ('M, 'T) Conditional.t + | Function of ('M, 'T) Function.t + | Identifier of ('M, 'T) Identifier.t + | Import of ('M, 'T) Import.t + | JSXElement of ('M, 'T) JSX.element + | JSXFragment of ('M, 'T) JSX.fragment + | StringLiteral of 'M StringLiteral.t + | BooleanLiteral of 'M BooleanLiteral.t + | NullLiteral of ('M, unit) Syntax.t option + | NumberLiteral of 'M NumberLiteral.t + | BigIntLiteral of 'M BigIntLiteral.t + | RegExpLiteral of 'M RegExpLiteral.t + | ModuleRefLiteral of ('M, 'T) ModuleRefLiteral.t + | Logical of ('M, 'T) Logical.t + | Match of ('M, 'T) match_expression + | Member of ('M, 'T) Member.t + | MetaProperty of 'M MetaProperty.t + | New of ('M, 'T) New.t + | Object of ('M, 'T) Object.t + | OptionalCall of ('M, 'T) OptionalCall.t + | OptionalMember of ('M, 'T) OptionalMember.t + | Sequence of ('M, 'T) Sequence.t + | Super of 'M Super.t + | TaggedTemplate of ('M, 'T) TaggedTemplate.t + | TemplateLiteral of ('M, 'T) TemplateLiteral.t + | This of 'M This.t + | TypeCast of ('M, 'T) TypeCast.t + | TSSatisfies of ('M, 'T) TSSatisfies.t + | Unary of ('M, 'T) Unary.t + | Update of ('M, 'T) Update.t + | Yield of ('M, 'T) Yield.t + [@@deriving show] +end = + Expression + +and JSX : sig + module Identifier : sig + type ('M, 'T) t = 'T * 'M t' + + and 'M t' = { + name: string; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module NamespacedName : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + namespace: ('M, 'T) Identifier.t; + name: ('M, 'T) Identifier.t; + } + [@@deriving show] + end + + module ExpressionContainer : sig + type ('M, 'T) t = { + expression: ('M, 'T) expression; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + + and ('M, 'T) expression = + | Expression of ('M, 'T) Expression.t + | EmptyExpression + [@@deriving show] + end + + module Text : sig + type t = { + value: string; + raw: string; + } + [@@deriving show] + end + + module Attribute : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) name = + | Identifier of ('M, 'T) Identifier.t + | NamespacedName of ('M, 'T) NamespacedName.t + + and ('M, 'T) value = + | StringLiteral of ('T * 'M StringLiteral.t) + | ExpressionContainer of ('T * ('M, 'T) ExpressionContainer.t) + + and ('M, 'T) t' = { + name: ('M, 'T) name; + value: ('M, 'T) value option; + } + [@@deriving show] + end + + module SpreadAttribute : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module MemberExpression : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) _object = + | Identifier of ('M, 'T) Identifier.t + | MemberExpression of ('M, 'T) t + + and ('M, 'T) t' = { + _object: ('M, 'T) _object; + property: ('M, 'T) Identifier.t; + } + [@@deriving show] + end + + type ('M, 'T) name = + | Identifier of ('M, 'T) Identifier.t + | NamespacedName of ('M, 'T) NamespacedName.t + | MemberExpression of ('M, 'T) MemberExpression.t + [@@deriving show] + + module Opening : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) attribute = + | Attribute of ('M, 'T) Attribute.t + | SpreadAttribute of ('M, 'T) SpreadAttribute.t + + and ('M, 'T) t' = { + name: ('M, 'T) name; + targs: ('M, 'T) Expression.CallTypeArgs.t option; + self_closing: bool; + attributes: ('M, 'T) attribute list; + } + [@@deriving show] + end + + module Closing : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { name: ('M, 'T) name } [@@deriving show] + end + + module SpreadChild : sig + type ('M, 'T) t = { + expression: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) child = 'T * ('M, 'T) child' + + and ('M, 'T) child' = + | Element of ('M, 'T) element + | Fragment of ('M, 'T) fragment + | ExpressionContainer of ('M, 'T) ExpressionContainer.t + | SpreadChild of ('M, 'T) SpreadChild.t + | Text of Text.t + + and ('M, 'T) element = { + opening_element: ('M, 'T) Opening.t; + closing_element: ('M, 'T) Closing.t option; + children: 'M * ('M, 'T) child list; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) fragment = { + frag_opening_element: 'M; + frag_closing_element: 'M; + frag_children: 'M * ('M, 'T) child list; + frag_comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + JSX + +and Match : sig + module Case : sig + type ('M, 'T, 'B) t = 'M * ('M, 'T, 'B) t' + + and ('M, 'T, 'B) t' = { + pattern: ('M, 'T) MatchPattern.t; + body: 'B; + guard: ('M, 'T) Expression.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T, 'B) t = { + arg: ('M, 'T) Expression.t; + cases: ('M, 'T, 'B) Case.t list; + (* The type here is used to store the resulting type after the patterns + refine the arg type. *) + match_keyword_loc: 'T; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + Match + +and MatchPattern : sig + module UnaryPattern : sig + type operator = + | Plus + | Minus + + and 'M argument = + | NumberLiteral of 'M NumberLiteral.t + | BigIntLiteral of 'M BigIntLiteral.t + + and 'M t = { + operator: operator; + argument: 'M * 'M argument; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module MemberPattern : sig + type ('M, 'T) base = + | BaseIdentifier of ('M, 'T) Identifier.t + | BaseMember of ('M, 'T) t + + and ('M, 'T) property = + | PropertyString of ('M * 'M StringLiteral.t) + | PropertyNumber of ('M * 'M NumberLiteral.t) + | PropertyBigInt of ('M * 'M BigIntLiteral.t) + | PropertyIdentifier of ('M, 'T) Identifier.t + + and ('M, 'T) t = 'T * ('M, 'T) t' + + and ('M, 'T) t' = { + base: ('M, 'T) base; + property: ('M, 'T) property; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module BindingPattern : sig + type ('M, 'T) t = { + kind: Variable.kind; + id: ('M, 'T) Identifier.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module RestPattern : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M * ('M, 'T) BindingPattern.t) option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module ObjectPattern : sig + module Property : sig + type ('M, 'T) key = + | StringLiteral of ('M * 'M StringLiteral.t) + | NumberLiteral of ('M * 'M NumberLiteral.t) + | BigIntLiteral of ('M * 'M BigIntLiteral.t) + | Identifier of ('M, 'T) Identifier.t + + and ('M, 'T) property = { + key: ('M, 'T) key; + pattern: ('M, 'T) MatchPattern.t; + shorthand: bool; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = + | Valid of ('M, 'T) property + (* Invalid code parsed so we can error with quick-fix. *) + | InvalidShorthand of ('M, 'M) Identifier.t + [@@deriving show] + end + + type ('M, 'T) t = { + properties: ('M, 'T) Property.t list; + rest: ('M, 'T) RestPattern.t option; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module ArrayPattern : sig + module Element : sig + type ('M, 'T) t = { + index: 'M; + pattern: ('M, 'T) MatchPattern.t; + } + [@@deriving show] + end + + type ('M, 'T) t = { + elements: ('M, 'T) Element.t list; + rest: ('M, 'T) RestPattern.t option; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module OrPattern : sig + type ('M, 'T) t = { + patterns: ('M, 'T) MatchPattern.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module AsPattern : sig + type ('M, 'T) target = + | Identifier of ('M, 'T) Identifier.t + | Binding of 'M * ('M, 'T) BindingPattern.t + + and ('M, 'T) t = { + pattern: ('M, 'T) MatchPattern.t; + target: ('M, 'T) target; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = + | WildcardPattern of ('M, unit) Syntax.t option + | NumberPattern of 'M NumberLiteral.t + | BigIntPattern of 'M BigIntLiteral.t + | StringPattern of 'M StringLiteral.t + | BooleanPattern of 'M BooleanLiteral.t + | NullPattern of ('M, unit) Syntax.t option + | UnaryPattern of 'M UnaryPattern.t + | BindingPattern of ('M, 'T) BindingPattern.t + | IdentifierPattern of ('M, 'T) Identifier.t + | MemberPattern of ('M, 'T) MemberPattern.t + | ObjectPattern of ('M, 'T) ObjectPattern.t + | ArrayPattern of ('M, 'T) ArrayPattern.t + | OrPattern of ('M, 'T) OrPattern.t + | AsPattern of ('M, 'T) AsPattern.t + [@@deriving show] +end = + MatchPattern + +and Pattern : sig + module RestElement : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Pattern.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Object : sig + module Property : sig + type ('M, 'T) key = + | StringLiteral of ('M * 'M StringLiteral.t) + | NumberLiteral of ('M * 'M NumberLiteral.t) + | BigIntLiteral of ('M * 'M BigIntLiteral.t) + | Identifier of ('M, 'T) Identifier.t + | Computed of ('M, 'T) ComputedKey.t + + and ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + key: ('M, 'T) key; + pattern: ('M, 'T) Pattern.t; + default: ('M, 'T) Expression.t option; + shorthand: bool; + } + [@@deriving show] + end + + type ('M, 'T) property = + | Property of ('M, 'T) Property.t + | RestElement of ('M, 'T) RestElement.t + + and ('M, 'T) t = { + properties: ('M, 'T) property list; + annot: ('M, 'T) Type.annotation_or_hint; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module Array : sig + module Element : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Pattern.t; + default: ('M, 'T) Expression.t option; + } + [@@deriving show] + end + + type ('M, 'T) element = + | Element of ('M, 'T) Element.t + | RestElement of ('M, 'T) RestElement.t + | Hole of 'M + + and ('M, 'T) t = { + elements: ('M, 'T) element list; + annot: ('M, 'T) Type.annotation_or_hint; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module Identifier : sig + type ('M, 'T) t = { + name: ('M, 'T) Identifier.t; + annot: ('M, 'T) Type.annotation_or_hint; + optional: bool; + } + [@@deriving show] + end + + type ('M, 'T) t = 'T * ('M, 'T) t' + + and ('M, 'T) t' = + | Object of ('M, 'T) Object.t + | Array of ('M, 'T) Array.t + | Identifier of ('M, 'T) Identifier.t + | Expression of ('M, 'T) Expression.t + [@@deriving show] +end = + Pattern + +and Comment : sig + type 'M t = 'M * t' + + and kind = + | Block + | Line + + and t' = { + kind: kind; + text: string; + on_newline: bool; + } + [@@deriving show] +end = + Comment + +and Class : sig + module Method : sig + type ('M, 'T) t = 'T * ('M, 'T) t' + + and kind = + | Constructor + | Method + | Get + | Set + + and ('M, 'T) t' = { + kind: kind; + key: ('M, 'T) Expression.Object.Property.key; + value: 'M * ('M, 'T) Function.t; + static: bool; + decorators: ('M, 'T) Class.Decorator.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Property : sig + type ('M, 'T) t = 'T * ('M, 'T) t' + + and ('M, 'T) t' = { + key: ('M, 'T) Expression.Object.Property.key; + value: ('M, 'T) value; + annot: ('M, 'T) Type.annotation_or_hint; + static: bool; + variance: 'M Variance.t option; + decorators: ('M, 'T) Class.Decorator.t list; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) value = + | Declared + | Uninitialized + | Initialized of ('M, 'T) Expression.t + [@@deriving show] + end + + module PrivateField : sig + type ('M, 'T) t = 'T * ('M, 'T) t' + + and ('M, 'T) t' = { + key: 'M PrivateName.t; + value: ('M, 'T) Class.Property.value; + annot: ('M, 'T) Type.annotation_or_hint; + static: bool; + variance: 'M Variance.t option; + decorators: ('M, 'T) Class.Decorator.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Extends : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + expr: ('M, 'T) Expression.t; + targs: ('M, 'T) Type.TypeArgs.t option; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Implements : sig + module Interface : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + id: ('M, 'T) Identifier.t; + targs: ('M, 'T) Type.TypeArgs.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + interfaces: ('M, 'T) Interface.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Body : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + body: ('M, 'T) element list; + comments: ('M, unit) Syntax.t option; + } + + and ('M, 'T) element = + | Method of ('M, 'T) Method.t + | Property of ('M, 'T) Property.t + | PrivateField of ('M, 'T) PrivateField.t + [@@deriving show] + end + + module Decorator : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + expression: ('M, 'T) Expression.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t option; + body: ('M, 'T) Class.Body.t; + tparams: ('M, 'T) Type.TypeParams.t option; + extends: ('M, 'T) Extends.t option; + implements: ('M, 'T) Implements.t option; + class_decorators: ('M, 'T) Decorator.t list; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] +end = + Class + +and Function : sig + module RestParam : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Pattern.t; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Param : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + argument: ('M, 'T) Pattern.t; + default: ('M, 'T) Expression.t option; + } + [@@deriving show] + end + + module ThisParam : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + annot: ('M, 'T) Type.annotation; + comments: ('M, unit) Syntax.t option; + } + [@@deriving show] + end + + module Params : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + this_: ('M, 'T) ThisParam.t option; + params: ('M, 'T) Param.t list; + rest: ('M, 'T) RestParam.t option; + comments: ('M, 'M Comment.t list) Syntax.t option; + } + [@@deriving show] + end + + module ReturnAnnot : sig + type ('M, 'T) t = + | Missing of 'T + | Available of ('M, 'T) Type.annotation + | TypeGuard of ('M, 'T) Type.type_guard_annotation + [@@deriving show] + end + + type effect_ = + | Hook + | Arbitrary + | Idempotent + | Parametric of int + [@@deriving show] + + type ('M, 'T) t = { + id: ('M, 'T) Identifier.t option; + params: ('M, 'T) Params.t; + body: ('M, 'T) body; + async: bool; + generator: bool; + effect_: effect_; + predicate: ('M, 'T) Type.Predicate.t option; + return: ('M, 'T) ReturnAnnot.t; + tparams: ('M, 'T) Type.TypeParams.t option; + comments: ('M, unit) Syntax.t option; + (* Location of the signature portion of a function, e.g. + * function foo(): void {} + * ^^^^^^^^^^^^^^^^^^^^ + *) + sig_loc: 'M; + } + + and ('M, 'T) body = + | BodyBlock of ('M * ('M, 'T) Statement.Block.t) + | BodyExpression of ('M, 'T) Expression.t + [@@deriving show] +end = + Function + +and Program : sig + type ('M, 'T) t = 'M * ('M, 'T) t' + + and ('M, 'T) t' = { + statements: ('M, 'T) Statement.t list; + interpreter: ('M * string) option; (** interpreter directive / shebang *) + comments: ('M, unit) Syntax.t option; + all_comments: 'M Comment.t list; + } + [@@deriving show] +end = + Program] diff --git a/compiler/flow_parser/parser/flow_ast_mapper.ml b/compiler/flow_parser/parser/flow_ast_mapper.ml new file mode 100644 index 00000000000..9fd711268cd --- /dev/null +++ b/compiler/flow_parser/parser/flow_ast_mapper.ml @@ -0,0 +1,3475 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Ast = Flow_ast + +let map_opt : 'node. ('node -> 'node) -> 'node option -> 'node option = + fun map opt -> + match opt with + | Some item -> + let item' = map item in + if item == item' then + opt + else + Some item' + | None -> opt + +let id_loc : 'node 'a. ('loc -> 'node -> 'node) -> 'loc -> 'node -> 'a -> ('node -> 'a) -> 'a = + fun map loc item same diff -> + let item' = map loc item in + if item == item' then + same + else + diff item' + +let id : 'node 'a. ('node -> 'node) -> 'node -> 'a -> ('node -> 'a) -> 'a = + fun map item same diff -> + let item' = map item in + if item == item' then + same + else + diff item' + +let map_loc : 'node. ('loc -> 'node -> 'node) -> 'loc * 'node -> 'loc * 'node = + fun map same -> + let (loc, item) = same in + id_loc map loc item same (fun diff -> (loc, diff)) + +let map_loc_opt : 'node. ('loc -> 'node -> 'node) -> ('loc * 'node) option -> ('loc * 'node) option + = + fun map same -> + map_opt + (fun same -> + let (loc, item) = same in + id_loc map loc item same (fun diff -> (loc, diff))) + same + +let map_list map lst = + let (rev_lst, changed) = + List.fold_left + (fun (lst', changed) item -> + let item' = map item in + (item' :: lst', changed || item' != item)) + ([], false) + lst + in + if changed then + List.rev rev_lst + else + lst + +let map_list_multiple map lst = + let (rev_lst, changed) = + List.fold_left + (fun (lst', changed) item -> + match map item with + | [] -> (lst', true) + | [item'] -> (item' :: lst', changed || item != item') + | items' -> (List.rev_append items' lst', true)) + ([], false) + lst + in + if changed then + List.rev rev_lst + else + lst + +type type_params_context = + | ClassTP + | FunctionTP + | DeclareFunctionTP + | DeclareClassTP + | DeclareComponentTP + | TypeAliasTP + | InterfaceTP + | OpaqueTypeTP + | ComponentDeclarationTP + | ComponentTypeTP + | FunctionTypeTP + | InferTP + | ObjectMappedTypeTP + +class ['loc] mapper = + object (this) + method program (program : ('loc, 'loc) Ast.Program.t) = + let open Ast.Program in + let (loc, { statements; interpreter; comments; all_comments }) = program in + let statements' = this#toplevel_statement_list statements in + let comments' = this#syntax_opt comments in + let all_comments' = map_list this#comment all_comments in + if statements == statements' && comments == comments' && all_comments == all_comments' then + program + else + ( loc, + { + statements = statements'; + interpreter; + comments = comments'; + all_comments = all_comments'; + } + ) + + method statement (stmt : ('loc, 'loc) Ast.Statement.t) = + let open Ast.Statement in + match stmt with + | (loc, Block block) -> id_loc this#block loc block stmt (fun block -> (loc, Block block)) + | (loc, Break break) -> id_loc this#break loc break stmt (fun break -> (loc, Break break)) + | (loc, ClassDeclaration cls) -> + id_loc this#class_declaration loc cls stmt (fun cls -> (loc, ClassDeclaration cls)) + | (loc, ComponentDeclaration component) -> + id_loc this#component_declaration loc component stmt (fun component -> + (loc, ComponentDeclaration component) + ) + | (loc, Continue cont) -> id_loc this#continue loc cont stmt (fun cont -> (loc, Continue cont)) + | (loc, Debugger dbg) -> id_loc this#debugger loc dbg stmt (fun dbg -> (loc, Debugger dbg)) + | (loc, DeclareClass stuff) -> + id_loc this#declare_class loc stuff stmt (fun stuff -> (loc, DeclareClass stuff)) + | (loc, DeclareComponent stuff) -> + id_loc this#declare_component loc stuff stmt (fun stuff -> (loc, DeclareComponent stuff)) + | (loc, DeclareEnum enum) -> + id_loc this#declare_enum loc enum stmt (fun enum -> (loc, DeclareEnum enum)) + | (loc, DeclareExportDeclaration decl) -> + id_loc this#declare_export_declaration loc decl stmt (fun decl -> + (loc, DeclareExportDeclaration decl) + ) + | (loc, DeclareFunction stuff) -> + id_loc this#declare_function loc stuff stmt (fun stuff -> (loc, DeclareFunction stuff)) + | (loc, DeclareInterface stuff) -> + id_loc this#declare_interface loc stuff stmt (fun stuff -> (loc, DeclareInterface stuff)) + | (loc, DeclareModule m) -> + id_loc this#declare_module loc m stmt (fun m -> (loc, DeclareModule m)) + | (loc, DeclareModuleExports annot) -> + id_loc this#declare_module_exports loc annot stmt (fun annot -> + (loc, DeclareModuleExports annot) + ) + | (loc, DeclareNamespace n) -> + id_loc this#declare_namespace loc n stmt (fun n -> (loc, DeclareNamespace n)) + | (loc, DeclareOpaqueType otype) -> + id_loc this#opaque_type loc otype stmt (fun otype -> (loc, DeclareOpaqueType otype)) + | (loc, DeclareTypeAlias stuff) -> + id_loc this#declare_type_alias loc stuff stmt (fun stuff -> (loc, DeclareTypeAlias stuff)) + | (loc, DeclareVariable stuff) -> + id_loc this#declare_variable loc stuff stmt (fun stuff -> (loc, DeclareVariable stuff)) + | (loc, DoWhile stuff) -> + id_loc this#do_while loc stuff stmt (fun stuff -> (loc, DoWhile stuff)) + | (loc, Empty empty) -> id_loc this#empty loc empty stmt (fun empty -> (loc, Empty empty)) + | (loc, EnumDeclaration enum) -> + id_loc this#enum_declaration loc enum stmt (fun enum -> (loc, EnumDeclaration enum)) + | (loc, ExportDefaultDeclaration decl) -> + id_loc this#export_default_declaration loc decl stmt (fun decl -> + (loc, ExportDefaultDeclaration decl) + ) + | (loc, ExportNamedDeclaration decl) -> + id_loc this#export_named_declaration loc decl stmt (fun decl -> + (loc, ExportNamedDeclaration decl) + ) + | (loc, Expression expr) -> + id_loc this#expression_statement loc expr stmt (fun expr -> (loc, Expression expr)) + | (loc, For for_stmt) -> + id_loc this#for_statement loc for_stmt stmt (fun for_stmt -> (loc, For for_stmt)) + | (loc, ForIn stuff) -> + id_loc this#for_in_statement loc stuff stmt (fun stuff -> (loc, ForIn stuff)) + | (loc, ForOf stuff) -> + id_loc this#for_of_statement loc stuff stmt (fun stuff -> (loc, ForOf stuff)) + | (loc, FunctionDeclaration func) -> + id_loc this#function_declaration loc func stmt (fun func -> (loc, FunctionDeclaration func)) + | (loc, If if_stmt) -> + id_loc this#if_statement loc if_stmt stmt (fun if_stmt -> (loc, If if_stmt)) + | (loc, ImportDeclaration decl) -> + id_loc this#import_declaration loc decl stmt (fun decl -> (loc, ImportDeclaration decl)) + | (loc, InterfaceDeclaration stuff) -> + id_loc this#interface_declaration loc stuff stmt (fun stuff -> + (loc, InterfaceDeclaration stuff) + ) + | (loc, Labeled label) -> + id_loc this#labeled_statement loc label stmt (fun label -> (loc, Labeled label)) + | (loc, Match x) -> id_loc this#match_statement loc x stmt (fun x -> (loc, Match x)) + | (loc, OpaqueType otype) -> + id_loc this#opaque_type loc otype stmt (fun otype -> (loc, OpaqueType otype)) + | (loc, Return ret) -> id_loc this#return loc ret stmt (fun ret -> (loc, Return ret)) + | (loc, Switch switch) -> + id_loc this#switch loc switch stmt (fun switch -> (loc, Switch switch)) + | (loc, Throw throw) -> id_loc this#throw loc throw stmt (fun throw -> (loc, Throw throw)) + | (loc, Try try_stmt) -> + id_loc this#try_catch loc try_stmt stmt (fun try_stmt -> (loc, Try try_stmt)) + | (loc, VariableDeclaration decl) -> + id_loc this#variable_declaration loc decl stmt (fun decl -> (loc, VariableDeclaration decl)) + | (loc, While stuff) -> id_loc this#while_ loc stuff stmt (fun stuff -> (loc, While stuff)) + | (loc, With stuff) -> id_loc this#with_ loc stuff stmt (fun stuff -> (loc, With stuff)) + | (loc, TypeAlias stuff) -> + id_loc this#type_alias loc stuff stmt (fun stuff -> (loc, TypeAlias stuff)) + + method comment (c : 'loc Ast.Comment.t) = c + + method syntax_opt + : 'internal. ('loc, 'internal) Ast.Syntax.t option -> ('loc, 'internal) Ast.Syntax.t option + = + map_opt this#syntax + + method syntax : 'internal. ('loc, 'internal) Ast.Syntax.t -> ('loc, 'internal) Ast.Syntax.t = + fun attached -> + let open Ast.Syntax in + let { leading; trailing; internal } = attached in + let leading' = map_list this#comment leading in + let trailing' = map_list this#comment trailing in + if leading == leading' && trailing == trailing' then + attached + else + { leading = leading'; trailing = trailing'; internal } + + method expression (expr : ('loc, 'loc) Ast.Expression.t) = + let open Ast.Expression in + match expr with + | (loc, Array x) -> id_loc this#array loc x expr (fun x -> (loc, Array x)) + | (loc, ArrowFunction x) -> + id_loc this#arrow_function loc x expr (fun x -> (loc, ArrowFunction x)) + | (loc, AsConstExpression x) -> + id_loc this#as_const_expression loc x expr (fun x -> (loc, AsConstExpression x)) + | (loc, AsExpression x) -> + id_loc this#as_expression loc x expr (fun x -> (loc, AsExpression x)) + | (loc, Assignment x) -> id_loc this#assignment loc x expr (fun x -> (loc, Assignment x)) + | (loc, Binary x) -> id_loc this#binary loc x expr (fun x -> (loc, Binary x)) + | (loc, Call x) -> id_loc this#call loc x expr (fun x -> (loc, Call x)) + | (loc, Class x) -> id_loc this#class_expression loc x expr (fun x -> (loc, Class x)) + | (loc, Conditional x) -> id_loc this#conditional loc x expr (fun x -> (loc, Conditional x)) + | (loc, Function x) -> id_loc this#function_expression loc x expr (fun x -> (loc, Function x)) + | (loc, Identifier x) -> id this#identifier x expr (fun x -> (loc, Identifier x)) + | (loc, Import x) -> id (this#import loc) x expr (fun x -> (loc, Import x)) + | (loc, JSXElement x) -> id_loc this#jsx_element loc x expr (fun x -> (loc, JSXElement x)) + | (loc, JSXFragment x) -> id_loc this#jsx_fragment loc x expr (fun x -> (loc, JSXFragment x)) + | (loc, StringLiteral x) -> + id_loc this#string_literal loc x expr (fun x -> (loc, StringLiteral x)) + | (loc, BooleanLiteral x) -> + id_loc this#boolean_literal loc x expr (fun x -> (loc, BooleanLiteral x)) + | (loc, NullLiteral x) -> id_loc this#null_literal loc x expr (fun x -> (loc, NullLiteral x)) + | (loc, NumberLiteral x) -> + id_loc this#number_literal loc x expr (fun x -> (loc, NumberLiteral x)) + | (loc, BigIntLiteral x) -> + id_loc this#bigint_literal loc x expr (fun x -> (loc, BigIntLiteral x)) + | (loc, RegExpLiteral x) -> + id_loc this#regexp_literal loc x expr (fun x -> (loc, RegExpLiteral x)) + | (loc, ModuleRefLiteral x) -> + id_loc this#module_ref_literal loc x expr (fun x -> (loc, ModuleRefLiteral x)) + | (loc, Logical x) -> id_loc this#logical loc x expr (fun x -> (loc, Logical x)) + | (loc, Match x) -> id_loc this#match_expression loc x expr (fun x -> (loc, Match x)) + | (loc, Member x) -> id_loc this#member loc x expr (fun x -> (loc, Member x)) + | (loc, MetaProperty x) -> + id_loc this#meta_property loc x expr (fun x -> (loc, MetaProperty x)) + | (loc, New x) -> id_loc this#new_ loc x expr (fun x -> (loc, New x)) + | (loc, Object x) -> id_loc this#object_ loc x expr (fun x -> (loc, Object x)) + | (loc, OptionalCall x) -> id (this#optional_call loc) x expr (fun x -> (loc, OptionalCall x)) + | (loc, OptionalMember x) -> + id_loc this#optional_member loc x expr (fun x -> (loc, OptionalMember x)) + | (loc, Sequence x) -> id_loc this#sequence loc x expr (fun x -> (loc, Sequence x)) + | (loc, Super x) -> id_loc this#super_expression loc x expr (fun x -> (loc, Super x)) + | (loc, TaggedTemplate x) -> + id_loc this#tagged_template loc x expr (fun x -> (loc, TaggedTemplate x)) + | (loc, TemplateLiteral x) -> + id_loc this#template_literal loc x expr (fun x -> (loc, TemplateLiteral x)) + | (loc, This x) -> id_loc this#this_expression loc x expr (fun x -> (loc, This x)) + | (loc, TypeCast x) -> id_loc this#type_cast loc x expr (fun x -> (loc, TypeCast x)) + | (loc, TSSatisfies x) -> id_loc this#ts_satisfies loc x expr (fun x -> (loc, TSSatisfies x)) + | (loc, Unary x) -> id_loc this#unary_expression loc x expr (fun x -> (loc, Unary x)) + | (loc, Update x) -> id_loc this#update_expression loc x expr (fun x -> (loc, Update x)) + | (loc, Yield x) -> id_loc this#yield loc x expr (fun x -> (loc, Yield x)) + + method array _loc (expr : ('loc, 'loc) Ast.Expression.Array.t) = + let open Ast.Expression in + let { Array.elements; comments } = expr in + let elements' = map_list this#array_element elements in + let comments' = this#syntax_opt comments in + if elements == elements' && comments == comments' then + expr + else + { Array.elements = elements'; comments = comments' } + + method array_element element = + let open Ast.Expression.Array in + match element with + | Expression expr -> id this#expression expr element (fun expr -> Expression expr) + | Spread spread -> id this#spread_element spread element (fun spread -> Spread spread) + | Hole _ -> element + + method arrow_function loc (expr : ('loc, 'loc) Ast.Function.t) = this#function_ loc expr + + method as_const_expression _loc (expr : ('loc, 'loc) Ast.Expression.AsConstExpression.t) = + let open Ast.Expression.AsConstExpression in + let { expression; comments } = expr in + let expression' = this#expression expression in + let comments' = this#syntax_opt comments in + if expression' == expression && comments' == comments then + expr + else + { expression = expression'; comments = comments' } + + method as_expression _loc (expr : ('loc, 'loc) Ast.Expression.AsExpression.t) = + let open Ast.Expression.AsExpression in + let { expression; annot; comments } = expr in + let expression' = this#expression expression in + let annot' = this#type_annotation annot in + let comments' = this#syntax_opt comments in + if expression' == expression && annot' == annot && comments' == comments then + expr + else + { expression = expression'; annot = annot'; comments = comments' } + + method assignment _loc (expr : ('loc, 'loc) Ast.Expression.Assignment.t) = + let open Ast.Expression.Assignment in + let { operator = _; left; right; comments } = expr in + let left' = this#assignment_pattern left in + let right' = this#expression right in + let comments' = this#syntax_opt comments in + if left == left' && right == right' && comments == comments' then + expr + else + { expr with left = left'; right = right'; comments = comments' } + + method binary _loc (expr : ('loc, 'loc) Ast.Expression.Binary.t) = + let open Ast.Expression.Binary in + let { operator = _; left; right; comments } = expr in + let left' = this#expression left in + let right' = this#expression right in + let comments' = this#syntax_opt comments in + if left == left' && right == right' && comments == comments' then + expr + else + { expr with left = left'; right = right'; comments = comments' } + + method block _loc (stmt : ('loc, 'loc) Ast.Statement.Block.t) = + let open Ast.Statement.Block in + let { body; comments } = stmt in + let body' = this#statement_list body in + let comments' = this#syntax_opt comments in + if body == body' && comments == comments' then + stmt + else + { body = body'; comments = comments' } + + method break _loc (break : 'loc Ast.Statement.Break.t) = + let open Ast.Statement.Break in + let { label; comments } = break in + let label' = map_opt this#label_identifier label in + let comments' = this#syntax_opt comments in + if label == label' && comments == comments' then + break + else + { label = label'; comments = comments' } + + method call _loc (expr : ('loc, 'loc) Ast.Expression.Call.t) = + let open Ast.Expression.Call in + let { callee; targs; arguments; comments } = expr in + let callee' = this#expression callee in + let targs' = map_opt this#call_type_args targs in + let arguments' = this#arg_list arguments in + let comments' = this#syntax_opt comments in + if callee == callee' && targs == targs' && arguments == arguments' && comments == comments' + then + expr + else + { callee = callee'; targs = targs'; arguments = arguments'; comments = comments' } + + method arg_list (arg_list : ('loc, 'loc) Ast.Expression.ArgList.t) = + let open Ast.Expression.ArgList in + let (loc, { arguments; comments }) = arg_list in + let arguments' = map_list this#expression_or_spread arguments in + let comments' = this#syntax_opt comments in + if arguments == arguments' && comments == comments' then + arg_list + else + (loc, { arguments = arguments'; comments = comments' }) + + method optional_call loc (expr : ('loc, 'loc) Ast.Expression.OptionalCall.t) = + let open Ast.Expression.OptionalCall in + let { call; optional = _; filtered_out = _ } = expr in + let call' = this#call loc call in + if call == call' then + expr + else + { expr with call = call' } + + method call_type_args (targs : ('loc, 'loc) Ast.Expression.CallTypeArgs.t) = + let open Ast.Expression.CallTypeArgs in + let (loc, { arguments; comments }) = targs in + let arguments' = map_list this#call_type_arg arguments in + let comments' = this#syntax_opt comments in + if arguments == arguments' && comments == comments' then + targs + else + (loc, { arguments = arguments'; comments = comments' }) + + method call_type_arg t = + let open Ast.Expression.CallTypeArg in + match t with + | Explicit x -> + let x' = this#type_ x in + if x' == x then + t + else + Explicit x' + | Implicit (loc, { Implicit.comments }) -> + let comments' = this#syntax_opt comments in + if comments == comments' then + t + else + Implicit (loc, { Implicit.comments = comments' }) + + method catch_body (body : 'loc * ('loc, 'loc) Ast.Statement.Block.t) = map_loc this#block body + + method catch_clause _loc (clause : ('loc, 'loc) Ast.Statement.Try.CatchClause.t') = + let open Ast.Statement.Try.CatchClause in + let { param; body; comments } = clause in + let param' = map_opt this#catch_clause_pattern param in + let body' = this#catch_body body in + let comments' = this#syntax_opt comments in + if param == param' && body == body' && comments == comments' then + clause + else + { param = param'; body = body'; comments = comments' } + + method class_declaration loc (cls : ('loc, 'loc) Ast.Class.t) = this#class_ loc cls + + method class_expression loc (cls : ('loc, 'loc) Ast.Class.t) = this#class_ loc cls + + method class_ _loc (cls : ('loc, 'loc) Ast.Class.t) = + let open Ast.Class in + let { id; body; tparams; extends; implements; class_decorators; comments } = cls in + let id' = map_opt this#class_identifier id in + let tparams' = map_opt (this#type_params ~kind:ClassTP) tparams in + let body' = this#class_body body in + let extends' = map_opt (map_loc this#class_extends) extends in + let implements' = map_opt this#class_implements implements in + let class_decorators' = map_list this#class_decorator class_decorators in + let comments' = this#syntax_opt comments in + if + id == id' + && body == body' + && extends == extends' + && implements == implements' + && class_decorators == class_decorators' + && comments == comments' + && tparams == tparams' + then + cls + else + { + id = id'; + body = body'; + extends = extends'; + implements = implements'; + class_decorators = class_decorators'; + comments = comments'; + tparams = tparams'; + } + + method class_extends _loc (extends : ('loc, 'loc) Ast.Class.Extends.t') = + let open Ast.Class.Extends in + let { expr; targs; comments } = extends in + let expr' = this#expression expr in + let targs' = map_opt this#type_args targs in + let comments' = this#syntax_opt comments in + if expr == expr' && targs == targs' && comments == comments' then + extends + else + { expr = expr'; targs = targs'; comments = comments' } + + method class_identifier (ident : ('loc, 'loc) Ast.Identifier.t) = + this#pattern_identifier ~kind:Ast.Variable.Let ident + + method class_body (cls_body : ('loc, 'loc) Ast.Class.Body.t) = + let open Ast.Class.Body in + let (loc, { body; comments }) = cls_body in + let body' = map_list this#class_element body in + let comments' = this#syntax_opt comments in + if body == body' && comments == comments' then + cls_body + else + (loc, { body = body'; comments = comments' }) + + method class_decorator (dec : ('loc, 'loc) Ast.Class.Decorator.t) = + let open Ast.Class.Decorator in + let (loc, { expression; comments }) = dec in + let expression' = this#expression expression in + let comments' = this#syntax_opt comments in + if expression == expression' && comments == comments' then + dec + else + (loc, { expression = expression'; comments = comments' }) + + method class_element (elem : ('loc, 'loc) Ast.Class.Body.element) = + let open Ast.Class.Body in + match elem with + | Method (loc, meth) -> id_loc this#class_method loc meth elem (fun meth -> Method (loc, meth)) + | Property (loc, prop) -> + id_loc this#class_property loc prop elem (fun prop -> Property (loc, prop)) + | PrivateField (loc, field) -> + id_loc this#class_private_field loc field elem (fun field -> PrivateField (loc, field)) + + method class_implements (implements : ('loc, 'loc) Ast.Class.Implements.t) = + let open Ast.Class.Implements in + let (loc, { interfaces; comments }) = implements in + let interfaces' = map_list this#class_implements_interface interfaces in + let comments' = this#syntax_opt comments in + if interfaces == interfaces' && comments == comments' then + implements + else + (loc, { interfaces = interfaces'; comments = comments' }) + + method class_implements_interface (interface : ('loc, 'loc) Ast.Class.Implements.Interface.t) = + let open Ast.Class.Implements.Interface in + let (loc, { id; targs }) = interface in + let id' = this#type_identifier_reference id in + let targs' = map_opt this#type_args targs in + if id == id' && targs == targs' then + interface + else + (loc, { id = id'; targs = targs' }) + + method class_method _loc (meth : ('loc, 'loc) Ast.Class.Method.t') = + let open Ast.Class.Method in + let { kind = _; key; value; static = _; decorators; comments } = meth in + let key' = this#object_key key in + let value' = map_loc this#function_expression_or_method value in + let decorators' = map_list this#class_decorator decorators in + let comments' = this#syntax_opt comments in + if key == key' && value == value' && decorators == decorators' && comments == comments' then + meth + else + { meth with key = key'; value = value'; decorators = decorators'; comments = comments' } + + method class_property _loc (prop : ('loc, 'loc) Ast.Class.Property.t') = + let open Ast.Class.Property in + let { key; value; annot; static = _; variance; decorators; comments } = prop in + let key' = this#object_key key in + let value' = this#class_property_value value in + let annot' = this#type_annotation_hint annot in + let variance' = this#variance_opt variance in + let decorators' = map_list this#class_decorator decorators in + let comments' = this#syntax_opt comments in + if + key == key' + && value == value' + && annot' == annot + && variance' == variance + && decorators' == decorators + && comments' == comments + then + prop + else + { + prop with + key = key'; + value = value'; + annot = annot'; + variance = variance'; + decorators = decorators'; + comments = comments'; + } + + method class_property_value (value : ('loc, 'loc) Ast.Class.Property.value) = + let open Ast.Class.Property in + match value with + | Declared -> value + | Uninitialized -> value + | Initialized x -> + let x' = this#expression x in + if x == x' then + value + else + Initialized x' + + method class_private_field _loc (prop : ('loc, 'loc) Ast.Class.PrivateField.t') = + let open Ast.Class.PrivateField in + let { key; value; annot; static = _; variance; decorators; comments } = prop in + let key' = this#private_name key in + let value' = this#class_property_value value in + let annot' = this#type_annotation_hint annot in + let variance' = this#variance_opt variance in + let decorators' = map_list this#class_decorator decorators in + let comments' = this#syntax_opt comments in + if + key == key' + && value == value' + && annot' == annot + && variance' == variance + && decorators' == decorators + && comments' == comments + then + prop + else + { + prop with + key = key'; + value = value'; + annot = annot'; + variance = variance'; + decorators = decorators'; + comments = comments'; + } + + method default_opt (default : ('loc, 'loc) Ast.Expression.t option) = + map_opt this#expression default + + method component_declaration _loc (component : ('loc, 'loc) Ast.Statement.ComponentDeclaration.t) + = + let open Ast.Statement.ComponentDeclaration in + let { id = ident; tparams; params; body; renders; comments; sig_loc } = component in + let ident' = this#component_identifier ident in + let tparams' = map_opt (this#type_params ~kind:ComponentDeclarationTP) tparams in + let params' = this#component_params params in + let body' = this#component_body body in + let renders' = this#component_renders_annotation renders in + let comments' = this#syntax_opt comments in + if + ident == ident' + && tparams == tparams' + && params == params' + && body == body' + && renders == renders' + && comments == comments' + then + component + else + { + id = ident'; + tparams = tparams'; + params = params'; + body = body'; + renders = renders'; + comments = comments'; + sig_loc; + } + + method component_identifier (ident : ('loc, 'loc) Ast.Identifier.t) = + this#pattern_identifier ~kind:Ast.Variable.Var ident + + method component_params (params : ('loc, 'loc) Ast.Statement.ComponentDeclaration.Params.t) = + let open Ast.Statement.ComponentDeclaration in + let (loc, { Params.params = params_list; rest; comments }) = params in + let params_list' = map_list this#component_param params_list in + let rest' = map_opt this#component_rest_param rest in + let comments' = this#syntax_opt comments in + if params_list == params_list' && rest == rest' && comments == comments' then + params + else + (loc, { Params.params = params_list'; rest = rest'; comments = comments' }) + + method component_param (param : ('loc, 'loc) Ast.Statement.ComponentDeclaration.Param.t) = + let open Ast.Statement.ComponentDeclaration.Param in + let (loc, { name; local; default; shorthand }) = param in + let name' = this#component_param_name name in + let local' = this#component_param_pattern local in + let default' = this#default_opt default in + if name == name' && local == local' && default == default' then + param + else + (loc, { name = name'; local = local'; default = default'; shorthand }) + + method component_param_name + (param_name : ('loc, 'loc) Ast.Statement.ComponentDeclaration.Param.param_name) = + let open Ast.Statement.ComponentDeclaration.Param in + match param_name with + | Identifier ident -> Identifier (this#identifier ident) + | StringLiteral (str_loc, str) -> StringLiteral (str_loc, this#string_literal str_loc str) + + method component_param_pattern (expr : ('loc, 'loc) Ast.Pattern.t) = + this#binding_pattern ~kind:Ast.Variable.Let expr + + method component_rest_param (expr : ('loc, 'loc) Ast.Statement.ComponentDeclaration.RestParam.t) + = + let open Ast.Statement.ComponentDeclaration.RestParam in + let (loc, { argument; comments }) = expr in + let argument' = this#component_param_pattern argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + expr + else + (loc, { argument = argument'; comments = comments' }) + + method component_body (body : 'loc * ('loc, 'loc) Ast.Statement.Block.t) = + let (loc, block) = body in + id_loc this#block loc block body (fun block -> (loc, block)) + + method conditional _loc (expr : ('loc, 'loc) Ast.Expression.Conditional.t) = + let open Ast.Expression.Conditional in + let { test; consequent; alternate; comments } = expr in + let test' = this#predicate_expression test in + let consequent' = this#expression consequent in + let alternate' = this#expression alternate in + let comments' = this#syntax_opt comments in + if + test == test' + && consequent == consequent' + && alternate == alternate' + && comments == comments' + then + expr + else + { test = test'; consequent = consequent'; alternate = alternate'; comments = comments' } + + method continue _loc (cont : 'loc Ast.Statement.Continue.t) = + let open Ast.Statement.Continue in + let { label; comments } = cont in + let label' = map_opt this#label_identifier label in + let comments' = this#syntax_opt comments in + if label == label' && comments == comments' then + cont + else + { label = label'; comments = comments' } + + method debugger _loc (dbg : 'loc Ast.Statement.Debugger.t) = + let open Ast.Statement.Debugger in + let { comments } = dbg in + let comments' = this#syntax_opt comments in + if comments == comments' then + dbg + else + { comments = comments' } + + method declare_class _loc (decl : ('loc, 'loc) Ast.Statement.DeclareClass.t) = + let open Ast.Statement.DeclareClass in + let { id = ident; tparams; body; extends; mixins; implements; comments } = decl in + let id' = this#class_identifier ident in + let tparams' = map_opt (this#type_params ~kind:DeclareClassTP) tparams in + let body' = map_loc this#object_type body in + let extends' = map_opt (map_loc this#generic_type) extends in + let mixins' = map_list (map_loc this#generic_type) mixins in + let implements' = map_opt this#class_implements implements in + let comments' = this#syntax_opt comments in + if + id' == ident + && tparams' == tparams + && body' == body + && extends' == extends + && mixins' == mixins + && implements' == implements + && comments' == comments + then + decl + else + { + id = id'; + tparams = tparams'; + body = body'; + extends = extends'; + mixins = mixins'; + implements = implements'; + comments = comments'; + } + + method declare_component _loc (decl : ('loc, 'loc) Ast.Statement.DeclareComponent.t) = + let open Ast.Statement.DeclareComponent in + let { id = ident; tparams; params; renders; comments } = decl in + let ident' = this#component_identifier ident in + let tparams' = map_opt (this#type_params ~kind:DeclareComponentTP) tparams in + let params' = this#component_type_params params in + let renders' = this#component_renders_annotation renders in + let comments' = this#syntax_opt comments in + if + ident == ident' + && tparams == tparams' + && params == params' + && renders == renders' + && comments == comments' + then + decl + else + { + id = ident'; + tparams = tparams'; + params = params'; + renders = renders'; + comments = comments'; + } + + method component_type _loc (t : ('loc, 'loc) Ast.Type.Component.t) = + let open Ast.Type.Component in + let { tparams; params; renders; comments } = t in + let tparams' = map_opt (this#type_params ~kind:ComponentTypeTP) tparams in + let params' = this#component_type_params params in + let renders' = this#component_renders_annotation renders in + let comments' = this#syntax_opt comments in + if tparams == tparams' && params == params' && renders == renders' && comments == comments' + then + t + else + { tparams = tparams'; params = params'; renders = renders'; comments = comments' } + + method component_type_params (params : ('loc, 'loc) Ast.Type.Component.Params.t) = + let open Ast.Type.Component in + let (loc, { Params.params = params_list; rest; comments }) = params in + let params_list' = map_list this#component_type_param params_list in + let rest' = map_opt this#component_type_rest_param rest in + let comments' = this#syntax_opt comments in + if params_list == params_list' && rest == rest' && comments == comments' then + params + else + (loc, { Params.params = params_list'; rest = rest'; comments = comments' }) + + method component_type_param (param : ('loc, 'loc) Ast.Type.Component.Param.t) = + let open Ast.Type.Component.Param in + let (loc, { name; annot; optional }) = param in + let name' = this#component_param_name name in + let annot' = this#type_annotation annot in + if name == name' && annot == annot' then + param + else + (loc, { name = name'; annot = annot'; optional }) + + method component_type_rest_param (expr : ('loc, 'loc) Ast.Type.Component.RestParam.t) = + let open Ast.Type.Component.RestParam in + let (loc, { argument; annot; optional; comments }) = expr in + let argument' = map_opt this#identifier argument in + let annot' = this#type_ annot in + let comments' = this#syntax_opt comments in + if argument == argument' && annot == annot' && comments == comments' then + expr + else + (loc, { argument = argument'; annot = annot'; comments = comments'; optional }) + + method declare_enum loc (enum : ('loc, 'loc) Ast.Statement.EnumDeclaration.t) = + this#enum_declaration loc enum + + method declare_export_declaration + _loc (decl : ('loc, 'loc) Ast.Statement.DeclareExportDeclaration.t) = + let open Ast.Statement.DeclareExportDeclaration in + let { default; source; specifiers; declaration; comments } = decl in + let source' = map_loc_opt this#export_source source in + let specifiers' = map_opt this#export_named_specifier specifiers in + let declaration' = map_opt this#declare_export_declaration_decl declaration in + let comments' = this#syntax_opt comments in + if + source == source' + && specifiers == specifiers' + && declaration == declaration' + && comments == comments' + then + decl + else + { + default; + source = source'; + specifiers = specifiers'; + declaration = declaration'; + comments = comments'; + } + + method declare_export_declaration_decl + (decl : ('loc, 'loc) Ast.Statement.DeclareExportDeclaration.declaration) = + let open Ast.Statement.DeclareExportDeclaration in + match decl with + | Variable (loc, dv) -> + let dv' = this#declare_variable loc dv in + if dv' == dv then + decl + else + Variable (loc, dv') + | Function (loc, df) -> + let df' = this#declare_function loc df in + if df' == df then + decl + else + Function (loc, df') + | Class (loc, dc) -> + let dc' = this#declare_class loc dc in + if dc' == dc then + decl + else + Class (loc, dc') + | Component (loc, dc) -> + let dc' = this#declare_component loc dc in + if dc' == dc then + decl + else + Component (loc, dc') + | DefaultType t -> + let t' = this#type_ t in + if t' == t then + decl + else + DefaultType t' + | NamedType (loc, ta) -> + let ta' = this#type_alias loc ta in + if ta' == ta then + decl + else + NamedType (loc, ta') + | NamedOpaqueType (loc, ot) -> + let ot' = this#opaque_type loc ot in + if ot' == ot then + decl + else + NamedOpaqueType (loc, ot') + | Interface (loc, i) -> + let i' = this#interface loc i in + if i' == i then + decl + else + Interface (loc, i') + | Enum (loc, enum) -> + let enum' = this#enum_declaration loc enum in + if enum' == enum then + decl + else + Enum (loc, enum') + + method declare_function _loc (decl : ('loc, 'loc) Ast.Statement.DeclareFunction.t) = + let open Ast.Statement.DeclareFunction in + let { id = ident; annot; predicate; comments } = decl in + let id' = this#function_identifier ident in + let annot' = this#type_annotation annot in + let predicate' = map_opt this#predicate predicate in + let comments' = this#syntax_opt comments in + if id' == ident && annot' == annot && predicate' == predicate && comments' == comments then + decl + else + { id = id'; annot = annot'; predicate = predicate'; comments = comments' } + + method declare_interface loc (decl : ('loc, 'loc) Ast.Statement.Interface.t) = + this#interface loc decl + + method declare_module _loc (m : ('loc, 'loc) Ast.Statement.DeclareModule.t) = + let open Ast.Statement.DeclareModule in + let { id; body; comments } = m in + let body' = map_loc this#block body in + let comments' = this#syntax_opt comments in + if body' == body && comments == comments' then + m + else + { id; body = body'; comments = comments' } + + method declare_module_exports _loc (exports : ('loc, 'loc) Ast.Statement.DeclareModuleExports.t) + = + let open Ast.Statement.DeclareModuleExports in + let { annot; comments } = exports in + let annot' = this#type_annotation annot in + let comments' = this#syntax_opt comments in + if annot == annot' && comments == comments' then + exports + else + { annot = annot'; comments = comments' } + + method declare_namespace _loc (m : ('loc, 'loc) Ast.Statement.DeclareNamespace.t) = + let open Ast.Statement.DeclareNamespace in + let { id; body; comments } = m in + let id' = + match id with + | Global g_id -> + let g_id' = this#identifier g_id in + if g_id == g_id' then + id + else + Global g_id' + | Local p_id -> + let p_id' = this#pattern_identifier ~kind:Ast.Variable.Const p_id in + if p_id == p_id' then + id + else + Local p_id' + in + let body' = map_loc this#block body in + let comments' = this#syntax_opt comments in + if id' == id && body' == body && comments == comments' then + m + else + { id = id'; body = body'; comments = comments' } + + method declare_type_alias loc (decl : ('loc, 'loc) Ast.Statement.TypeAlias.t) = + this#type_alias loc decl + + method declare_variable _loc (decl : ('loc, 'loc) Ast.Statement.DeclareVariable.t) = + let open Ast.Statement.DeclareVariable in + let { id = ident; annot; kind; comments } = decl in + let id' = this#pattern_identifier ~kind ident in + let annot' = this#type_annotation annot in + let comments' = this#syntax_opt comments in + if id' == ident && annot' == annot && comments' == comments then + decl + else + { id = id'; annot = annot'; kind; comments = comments' } + + method do_while _loc (stuff : ('loc, 'loc) Ast.Statement.DoWhile.t) = + let open Ast.Statement.DoWhile in + let { body; test; comments } = stuff in + let body' = this#statement body in + let test' = this#predicate_expression test in + let comments' = this#syntax_opt comments in + if body == body' && test == test' && comments == comments' then + stuff + else + { body = body'; test = test'; comments = comments' } + + method empty _loc empty = + let open Ast.Statement.Empty in + let { comments } = empty in + let comments' = this#syntax_opt comments in + if comments == comments' then + empty + else + { comments = comments' } + + method enum_declaration _loc (enum : ('loc, 'loc) Ast.Statement.EnumDeclaration.t) = + let open Ast.Statement.EnumDeclaration in + let { id = ident; body; comments } = enum in + let id' = this#pattern_identifier ~kind:Ast.Variable.Const ident in + let body' = this#enum_body body in + let comments' = this#syntax_opt comments in + if ident == id' && body == body' && comments == comments' then + enum + else + { id = id'; body = body'; comments = comments' } + + method enum_body (body : 'loc Ast.Statement.EnumDeclaration.body) = + let open Ast.Statement.EnumDeclaration in + match body with + | (loc, BooleanBody boolean_body) -> + id this#enum_boolean_body boolean_body body (fun body -> (loc, BooleanBody body)) + | (loc, NumberBody number_body) -> + id this#enum_number_body number_body body (fun body -> (loc, NumberBody body)) + | (loc, StringBody string_body) -> + id this#enum_string_body string_body body (fun body -> (loc, StringBody body)) + | (loc, SymbolBody symbol_body) -> + id this#enum_symbol_body symbol_body body (fun body -> (loc, SymbolBody body)) + | (loc, BigIntBody bigint_body) -> + id this#enum_bigint_body bigint_body body (fun body -> (loc, BigIntBody body)) + + method enum_boolean_body (body : 'loc Ast.Statement.EnumDeclaration.BooleanBody.t) = + let open Ast.Statement.EnumDeclaration.BooleanBody in + let { members; explicit_type = _; has_unknown_members = _; comments } = body in + let members' = map_list this#enum_boolean_member members in + let comments' = this#syntax_opt comments in + if members == members' && comments == comments' then + body + else + { body with members = members'; comments = comments' } + + method enum_number_body (body : 'loc Ast.Statement.EnumDeclaration.NumberBody.t) = + let open Ast.Statement.EnumDeclaration.NumberBody in + let { members; explicit_type = _; has_unknown_members = _; comments } = body in + let members' = map_list this#enum_number_member members in + let comments' = this#syntax_opt comments in + if members == members' && comments == comments' then + body + else + { body with members = members'; comments = comments' } + + method enum_string_body (body : 'loc Ast.Statement.EnumDeclaration.StringBody.t) = + let open Ast.Statement.EnumDeclaration.StringBody in + let { members; explicit_type = _; has_unknown_members = _; comments } = body in + let members' = + match members with + | Defaulted m -> id (map_list this#enum_defaulted_member) m members (fun m -> Defaulted m) + | Initialized m -> id (map_list this#enum_string_member) m members (fun m -> Initialized m) + in + let comments' = this#syntax_opt comments in + if members == members' && comments == comments' then + body + else + { body with members = members'; comments = comments' } + + method enum_symbol_body (body : 'loc Ast.Statement.EnumDeclaration.SymbolBody.t) = + let open Ast.Statement.EnumDeclaration.SymbolBody in + let { members; has_unknown_members = _; comments } = body in + let members' = map_list this#enum_defaulted_member members in + let comments' = this#syntax_opt comments in + if members == members' && comments == comments' then + body + else + { body with members = members'; comments = comments' } + + method enum_bigint_body (body : 'loc Ast.Statement.EnumDeclaration.BigIntBody.t) = + let open Ast.Statement.EnumDeclaration.BigIntBody in + let { members; explicit_type = _; has_unknown_members = _; comments } = body in + let members' = map_list this#enum_bigint_member members in + let comments' = this#syntax_opt comments in + if members == members' && comments == comments' then + body + else + { body with members = members'; comments = comments' } + + method enum_defaulted_member (member : 'loc Ast.Statement.EnumDeclaration.DefaultedMember.t) = + let open Ast.Statement.EnumDeclaration.DefaultedMember in + let (loc, { id = ident }) = member in + let id' = this#enum_member_identifier ident in + if ident == id' then + member + else + (loc, { id = id' }) + + method enum_boolean_member + (member : + ('loc Ast.BooleanLiteral.t, 'loc) Ast.Statement.EnumDeclaration.InitializedMember.t + ) = + let open Ast.Statement.EnumDeclaration.InitializedMember in + let (loc, { id = ident; init }) = member in + let id' = this#enum_member_identifier ident in + if ident == id' then + member + else + (loc, { id = id'; init }) + + method enum_number_member + (member : ('loc Ast.NumberLiteral.t, 'loc) Ast.Statement.EnumDeclaration.InitializedMember.t) + = + let open Ast.Statement.EnumDeclaration.InitializedMember in + let (loc, { id = ident; init }) = member in + let id' = this#enum_member_identifier ident in + if ident == id' then + member + else + (loc, { id = id'; init }) + + method enum_string_member + (member : ('loc Ast.StringLiteral.t, 'loc) Ast.Statement.EnumDeclaration.InitializedMember.t) + = + let open Ast.Statement.EnumDeclaration.InitializedMember in + let (loc, { id = ident; init }) = member in + let id' = this#enum_member_identifier ident in + if ident == id' then + member + else + (loc, { id = id'; init }) + + method enum_bigint_member + (member : ('loc Ast.BigIntLiteral.t, 'loc) Ast.Statement.EnumDeclaration.InitializedMember.t) + = + let open Ast.Statement.EnumDeclaration.InitializedMember in + let (loc, { id = ident; init }) = member in + let id' = this#enum_member_identifier ident in + if ident == id' then + member + else + (loc, { id = id'; init }) + + method enum_member_identifier (id : ('loc, 'loc) Ast.Identifier.t) = this#identifier id + + method export_default_declaration + _loc (decl : ('loc, 'loc) Ast.Statement.ExportDefaultDeclaration.t) = + let open Ast.Statement.ExportDefaultDeclaration in + let { default; declaration; comments } = decl in + let declaration' = this#export_default_declaration_decl declaration in + let comments' = this#syntax_opt comments in + if declaration' == declaration && comments' == comments then + decl + else + { default; declaration = declaration'; comments = comments' } + + method export_default_declaration_decl + (decl : ('loc, 'loc) Ast.Statement.ExportDefaultDeclaration.declaration) = + let open Ast.Statement.ExportDefaultDeclaration in + match decl with + | Declaration stmt -> id this#statement stmt decl (fun stmt -> Declaration stmt) + | Expression expr -> id this#expression expr decl (fun expr -> Expression expr) + + method export_named_declaration _loc (decl : ('loc, 'loc) Ast.Statement.ExportNamedDeclaration.t) + = + let open Ast.Statement.ExportNamedDeclaration in + let { export_kind; source; specifiers; declaration; comments } = decl in + let source' = map_loc_opt this#export_source source in + let specifiers' = map_opt this#export_named_specifier specifiers in + let declaration' = map_opt this#statement declaration in + let comments' = this#syntax_opt comments in + if + source == source' + && specifiers == specifiers' + && declaration == declaration' + && comments == comments' + then + decl + else + { + export_kind; + source = source'; + specifiers = specifiers'; + declaration = declaration'; + comments = comments'; + } + + method export_named_declaration_specifier + (spec : ('loc, 'loc) Ast.Statement.ExportNamedDeclaration.ExportSpecifier.t) = + let open Ast.Statement.ExportNamedDeclaration.ExportSpecifier in + let (loc, { local; exported; from_remote; imported_name_def_loc }) = spec in + let local' = this#identifier local in + let exported' = map_opt this#identifier exported in + if local == local' && exported == exported' then + spec + else + (loc, { local = local'; exported = exported'; from_remote; imported_name_def_loc }) + + method export_batch_specifier + (spec : ('loc, 'loc) Ast.Statement.ExportNamedDeclaration.ExportBatchSpecifier.t) = + let (loc, id_opt) = spec in + let id_opt' = map_opt this#identifier id_opt in + if id_opt == id_opt' then + spec + else + (loc, id_opt') + + method export_named_specifier + (spec : ('loc, 'loc) Ast.Statement.ExportNamedDeclaration.specifier) = + let open Ast.Statement.ExportNamedDeclaration in + match spec with + | ExportSpecifiers spec_list -> + let spec_list' = map_list this#export_named_declaration_specifier spec_list in + if spec_list == spec_list' then + spec + else + ExportSpecifiers spec_list' + | ExportBatchSpecifier batch -> + let batch' = this#export_batch_specifier batch in + if batch == batch' then + spec + else + ExportBatchSpecifier batch' + + method export_source _loc (source : 'loc Ast.StringLiteral.t) = + let open Ast.StringLiteral in + let { value; raw; comments } = source in + let comments' = this#syntax_opt comments in + if comments == comments' then + source + else + { value; raw; comments = comments' } + + method expression_statement _loc (stmt : ('loc, 'loc) Ast.Statement.Expression.t) = + let open Ast.Statement.Expression in + let { expression = expr; directive; comments } = stmt in + let expr' = this#expression expr in + let comments' = this#syntax_opt comments in + if expr == expr' && comments == comments' then + stmt + else + { expression = expr'; directive; comments = comments' } + + method expression_or_spread expr_or_spread = + let open Ast.Expression in + match expr_or_spread with + | Expression expr -> id this#expression expr expr_or_spread (fun expr -> Expression expr) + | Spread spread -> id this#spread_element spread expr_or_spread (fun spread -> Spread spread) + + method for_in_statement _loc (stmt : ('loc, 'loc) Ast.Statement.ForIn.t) = + let open Ast.Statement.ForIn in + let { left; right; body; each; comments } = stmt in + let left' = this#for_in_statement_lhs left in + let right' = this#expression right in + let body' = this#statement body in + let comments' = this#syntax_opt comments in + if left == left' && right == right' && body == body' && comments == comments' then + stmt + else + { left = left'; right = right'; body = body'; each; comments = comments' } + + method for_in_statement_lhs (left : ('loc, 'loc) Ast.Statement.ForIn.left) = + let open Ast.Statement.ForIn in + match left with + | LeftDeclaration decl -> + id this#for_in_left_declaration decl left (fun decl -> LeftDeclaration decl) + | LeftPattern patt -> + id this#for_in_assignment_pattern patt left (fun patt -> LeftPattern patt) + + method for_in_left_declaration left = + let (loc, decl) = left in + id_loc this#variable_declaration loc decl left (fun decl -> (loc, decl)) + + method for_of_statement _loc (stuff : ('loc, 'loc) Ast.Statement.ForOf.t) = + let open Ast.Statement.ForOf in + let { left; right; body; await; comments } = stuff in + let left' = this#for_of_statement_lhs left in + let right' = this#expression right in + let body' = this#statement body in + let comments' = this#syntax_opt comments in + if left == left' && right == right' && body == body' && comments == comments' then + stuff + else + { left = left'; right = right'; body = body'; await; comments = comments' } + + method for_of_statement_lhs (left : ('loc, 'loc) Ast.Statement.ForOf.left) = + let open Ast.Statement.ForOf in + match left with + | LeftDeclaration decl -> + id this#for_of_left_declaration decl left (fun decl -> LeftDeclaration decl) + | LeftPattern patt -> + id this#for_of_assignment_pattern patt left (fun patt -> LeftPattern patt) + + method for_of_left_declaration left = + let (loc, decl) = left in + id_loc this#variable_declaration loc decl left (fun decl -> (loc, decl)) + + method for_statement _loc (stmt : ('loc, 'loc) Ast.Statement.For.t) = + let open Ast.Statement.For in + let { init; test; update; body; comments } = stmt in + let init' = map_opt this#for_statement_init init in + let test' = map_opt this#predicate_expression test in + let update' = map_opt this#expression update in + let body' = this#statement body in + let comments' = this#syntax_opt comments in + if + init == init' + && test == test' + && update == update' + && body == body' + && comments == comments' + then + stmt + else + { init = init'; test = test'; update = update'; body = body'; comments = comments' } + + method for_statement_init (init : ('loc, 'loc) Ast.Statement.For.init) = + let open Ast.Statement.For in + match init with + | InitDeclaration decl -> + id this#for_init_declaration decl init (fun decl -> InitDeclaration decl) + | InitExpression expr -> id this#expression expr init (fun expr -> InitExpression expr) + + method for_init_declaration init = + let (loc, decl) = init in + id_loc this#variable_declaration loc decl init (fun decl -> (loc, decl)) + + method function_param_type (fpt : ('loc, 'loc) Ast.Type.Function.Param.t) = + let open Ast.Type.Function.Param in + let (loc, { annot; name; optional }) = fpt in + let annot' = this#type_ annot in + let name' = map_opt this#identifier name in + if annot' == annot && name' == name then + fpt + else + (loc, { annot = annot'; name = name'; optional }) + + method function_rest_param_type (frpt : ('loc, 'loc) Ast.Type.Function.RestParam.t) = + let open Ast.Type.Function.RestParam in + let (loc, { argument; comments }) = frpt in + let argument' = this#function_param_type argument in + let comments' = this#syntax_opt comments in + if argument' == argument && comments' == comments then + frpt + else + (loc, { argument = argument'; comments = comments' }) + + method function_this_param_type (this_param : ('loc, 'loc) Ast.Type.Function.ThisParam.t) = + let open Ast.Type.Function.ThisParam in + let (loc, { annot; comments }) = this_param in + let annot' = this#type_annotation annot in + let comments' = this#syntax_opt comments in + if annot' == annot && comments' == comments then + this_param + else + (loc, { annot = annot'; comments = comments' }) + + method function_type_return_annotation + (return : ('loc, 'loc) Ast.Type.Function.return_annotation) = + let open Ast.Type.Function in + match return with + | TypeAnnotation t -> id this#type_ t return (fun rt -> TypeAnnotation rt) + | TypeGuard g -> id this#type_guard g return (fun tg -> TypeGuard tg) + + method function_type _loc (ft : ('loc, 'loc) Ast.Type.Function.t) = + let open Ast.Type.Function in + let { + params = (params_loc, { Params.this_; params = ps; rest = rpo; comments = params_comments }); + return; + tparams; + comments = func_comments; + effect_; + } = + ft + in + let tparams' = map_opt (this#type_params ~kind:FunctionTypeTP) tparams in + let this_' = map_opt this#function_this_param_type this_ in + let ps' = map_list this#function_param_type ps in + let rpo' = map_opt this#function_rest_param_type rpo in + let return' = this#function_type_return_annotation return in + let func_comments' = this#syntax_opt func_comments in + let params_comments' = this#syntax_opt params_comments in + if + ps' == ps + && rpo' == rpo + && return' == return + && tparams' == tparams + && func_comments' == func_comments + && params_comments' == params_comments + && this_' == this_ + then + ft + else + { + params = + ( params_loc, + { Params.this_ = this_'; params = ps'; rest = rpo'; comments = params_comments' } + ); + return = return'; + tparams = tparams'; + comments = func_comments'; + effect_; + } + + method label_identifier (ident : ('loc, 'loc) Ast.Identifier.t) = this#identifier ident + + method object_property_value_type (opvt : ('loc, 'loc) Ast.Type.Object.Property.value) = + let open Ast.Type.Object.Property in + match opvt with + | Init t -> id this#type_ t opvt (fun t -> Init t) + | Get t -> id this#object_type_property_getter t opvt (fun t -> Get t) + | Set t -> id this#object_type_property_setter t opvt (fun t -> Set t) + + method object_type_property_getter getter = + let (loc, ft) = getter in + id_loc this#function_type loc ft getter (fun ft -> (loc, ft)) + + method object_type_property_setter setter = + let (loc, ft) = setter in + id_loc this#function_type loc ft setter (fun ft -> (loc, ft)) + + method object_property_type (opt : ('loc, 'loc) Ast.Type.Object.Property.t) = + let open Ast.Type.Object.Property in + let (loc, { key; value; optional; static; proto; _method; variance; comments }) = opt in + let key' = this#object_key key in + let value' = this#object_property_value_type value in + let variance' = this#variance_opt variance in + let comments' = this#syntax_opt comments in + if key' == key && value' == value && variance' == variance && comments' == comments then + opt + else + ( loc, + { + key = key'; + value = value'; + optional; + static; + proto; + _method; + variance = variance'; + comments = comments'; + } + ) + + method object_spread_property_type (opt : ('loc, 'loc) Ast.Type.Object.SpreadProperty.t) = + let open Ast.Type.Object.SpreadProperty in + let (loc, { argument; comments }) = opt in + let argument' = this#type_ argument in + let comments' = this#syntax_opt comments in + if argument' == argument && comments == comments' then + opt + else + (loc, { argument = argument'; comments = comments' }) + + method object_indexer_property_type (opt : ('loc, 'loc) Ast.Type.Object.Indexer.t) = + let open Ast.Type.Object.Indexer in + let (loc, { id; key; value; static; variance; comments }) = opt in + let key' = this#type_ key in + let value' = this#type_ value in + let variance' = this#variance_opt variance in + let comments' = this#syntax_opt comments in + if key' == key && value' == value && variance' == variance && comments' == comments then + opt + else + (loc, { id; key = key'; value = value'; static; variance = variance'; comments = comments' }) + + method object_internal_slot_property_type (slot : ('loc, 'loc) Ast.Type.Object.InternalSlot.t) = + let open Ast.Type.Object.InternalSlot in + let (loc, { id; value; optional; static; _method; comments }) = slot in + let id' = this#identifier id in + let value' = this#type_ value in + let comments' = this#syntax_opt comments in + if id == id' && value == value' && comments == comments' then + slot + else + (loc, { id = id'; value = value'; optional; static; _method; comments = comments' }) + + method object_call_property_type (call : ('loc, 'loc) Ast.Type.Object.CallProperty.t) = + let open Ast.Type.Object.CallProperty in + let (loc, { value = (value_loc, value); static; comments }) = call in + let value' = this#function_type value_loc value in + let comments' = this#syntax_opt comments in + if value == value' && comments == comments' then + call + else + (loc, { value = (value_loc, value'); static; comments = comments' }) + + method object_mapped_type_property (mt : ('loc, 'loc) Ast.Type.Object.MappedType.t) = + let open Ast.Type.Object.MappedType in + let (loc, { key_tparam; prop_type; source_type; variance; comments; optional }) = mt in + let key_tparam' = this#type_param ~kind:ObjectMappedTypeTP key_tparam in + let prop_type' = this#type_ prop_type in + let source_type' = this#type_ source_type in + let variance' = this#variance_opt variance in + let comments' = this#syntax_opt comments in + if + key_tparam' == key_tparam + && prop_type' == prop_type + && source_type' == source_type + && variance' == variance + && comments' == comments + then + mt + else + ( loc, + { + key_tparam = key_tparam'; + prop_type = prop_type'; + source_type = source_type'; + variance = variance'; + comments = comments'; + optional; + } + ) + + method object_type _loc (ot : ('loc, 'loc) Ast.Type.Object.t) = + let open Ast.Type.Object in + let { properties; exact; inexact; comments } = ot in + let properties' = map_list this#object_type_property properties in + let comments' = this#syntax_opt comments in + if properties' == properties && comments == comments' then + ot + else + { properties = properties'; exact; inexact; comments = comments' } + + method object_type_property (p : ('loc, 'loc) Ast.Type.Object.property) = + let open Ast.Type.Object in + match p with + | Property p' -> id this#object_property_type p' p (fun p' -> Property p') + | SpreadProperty p' -> id this#object_spread_property_type p' p (fun p' -> SpreadProperty p') + | Indexer p' -> id this#object_indexer_property_type p' p (fun p' -> Indexer p') + | InternalSlot p' -> + id this#object_internal_slot_property_type p' p (fun p' -> InternalSlot p') + | CallProperty p' -> id this#object_call_property_type p' p (fun p' -> CallProperty p') + | MappedType p' -> id this#object_mapped_type_property p' p (fun p' -> MappedType p') + + method interface_type _loc (i : ('loc, 'loc) Ast.Type.Interface.t) = + let open Ast.Type.Interface in + let { extends; body; comments } = i in + let extends' = map_list (map_loc this#generic_type) extends in + let body' = map_loc this#object_type body in + let comments' = this#syntax_opt comments in + if extends' == extends && body' == body && comments == comments' then + i + else + { extends = extends'; body = body'; comments = comments' } + + method generic_identifier_type (git : ('loc, 'loc) Ast.Type.Generic.Identifier.t) = + let open Ast.Type.Generic.Identifier in + match git with + | Unqualified i -> id this#type_identifier_reference i git (fun i -> Unqualified i) + | Qualified i -> id this#generic_qualified_identifier_type i git (fun i -> Qualified i) + + method generic_qualified_identifier_type qual = + let open Ast.Type.Generic.Identifier in + let (loc, { qualification; id }) = qual in + let qualification' = this#generic_identifier_type qualification in + let id' = this#member_type_identifier id in + if qualification' == qualification && id' == id then + qual + else + (loc, { qualification = qualification'; id = id' }) + + method member_type_identifier id = this#identifier id + + method variance (variance : 'loc Ast.Variance.t) = + let (loc, { Ast.Variance.kind; comments }) = variance in + let comments' = this#syntax_opt comments in + if comments == comments' then + variance + else + (loc, { Ast.Variance.kind; comments = comments' }) + + method variance_opt (opt : 'loc Ast.Variance.t option) = map_opt this#variance opt + + method tparam_const_modifier (c : 'loc Ast.Type.TypeParam.ConstModifier.t) = + let (loc, comments) = c in + let comments' = this#syntax_opt comments in + if comments == comments' then + c + else + (loc, comments') + + method type_args (targs : ('loc, 'loc) Ast.Type.TypeArgs.t) = + let open Ast.Type.TypeArgs in + let (loc, { arguments; comments }) = targs in + let arguments' = map_list this#type_ arguments in + let comments' = this#syntax_opt comments in + if arguments == arguments' && comments == comments' then + targs + else + (loc, { arguments = arguments'; comments = comments' }) + + method type_params ~kind (tparams : ('loc, 'loc) Ast.Type.TypeParams.t) = + let open Ast.Type.TypeParams in + let (loc, { params = tps; comments }) = tparams in + let tps' = map_list (this#type_param ~kind) tps in + let comments' = this#syntax_opt comments in + if tps' == tps && comments' == comments then + tparams + else + (loc, { params = tps'; comments = comments' }) + + method type_param ~kind:_ (tparam : ('loc, 'loc) Ast.Type.TypeParam.t) = + let open Ast.Type.TypeParam in + let (loc, { name; bound; bound_kind; variance; default; const }) = tparam in + let bound' = this#type_annotation_hint bound in + let variance' = this#variance_opt variance in + let default' = map_opt this#type_ default in + let const' = map_opt this#tparam_const_modifier const in + let name' = this#binding_type_identifier name in + if + name' == name + && bound' == bound + && variance' == variance + && default' == default + && const' == const + then + tparam + else + ( loc, + { + name = name'; + bound = bound'; + bound_kind; + variance = variance'; + default = default'; + const = const'; + } + ) + + method generic_type _loc (gt : ('loc, 'loc) Ast.Type.Generic.t) = + let open Ast.Type.Generic in + let { id; targs; comments } = gt in + let id' = this#generic_identifier_type id in + let targs' = map_opt this#type_args targs in + let comments' = this#syntax_opt comments in + if id' == id && targs' == targs && comments' == comments then + gt + else + { id = id'; targs = targs'; comments = comments' } + + method indexed_access_type _loc (ia : ('loc, 'loc) Ast.Type.IndexedAccess.t) = + let open Ast.Type.IndexedAccess in + let { _object; index; comments } = ia in + let _object' = this#type_ _object in + let index' = this#type_ index in + let comments' = this#syntax_opt comments in + if _object' == _object && index' == index && comments' == comments then + ia + else + { _object = _object'; index = index'; comments = comments' } + + method optional_indexed_access_type loc (ia : ('loc, 'loc) Ast.Type.OptionalIndexedAccess.t) = + let open Ast.Type.OptionalIndexedAccess in + let { indexed_access; optional } = ia in + let indexed_access' = this#indexed_access_type loc indexed_access in + if indexed_access' == indexed_access then + ia + else + { indexed_access = indexed_access'; optional } + + method string_literal _loc (lit : 'loc Ast.StringLiteral.t) = + let open Ast.StringLiteral in + let { value; raw; comments } = lit in + let comments' = this#syntax_opt comments in + if comments == comments' then + lit + else + { value; raw; comments = comments' } + + method number_literal _loc (lit : 'loc Ast.NumberLiteral.t) = + let open Ast.NumberLiteral in + let { value; raw; comments } = lit in + let comments' = this#syntax_opt comments in + if comments == comments' then + lit + else + { value; raw; comments = comments' } + + method bigint_literal _loc (lit : 'loc Ast.BigIntLiteral.t) = + let open Ast.BigIntLiteral in + let { value; raw; comments } = lit in + let comments' = this#syntax_opt comments in + if comments == comments' then + lit + else + { value; raw; comments = comments' } + + method boolean_literal _loc (lit : 'loc Ast.BooleanLiteral.t) = + let open Ast.BooleanLiteral in + let { value; comments } = lit in + let comments' = this#syntax_opt comments in + if comments == comments' then + lit + else + { value; comments = comments' } + + method null_literal _loc comments = this#syntax_opt comments + + method regexp_literal _loc (lit : 'loc Ast.RegExpLiteral.t) = + let open Ast.RegExpLiteral in + let { pattern; flags; raw; comments } = lit in + let comments' = this#syntax_opt comments in + if comments == comments' then + lit + else + { pattern; flags; raw; comments = comments' } + + method module_ref_literal _loc (lit : ('loc, 'loc) Ast.ModuleRefLiteral.t) = + let open Ast.ModuleRefLiteral in + let { value; require_loc; def_loc_opt; prefix_len; legacy_interop; raw; comments } = lit in + let comments' = this#syntax_opt comments in + if comments == comments' then + lit + else + { value; require_loc; def_loc_opt; prefix_len; legacy_interop; raw; comments } + + method nullable_type (t : ('loc, 'loc) Ast.Type.Nullable.t) = + let open Ast.Type.Nullable in + let { argument; comments } = t in + let argument' = this#type_ argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + t + else + { argument = argument'; comments = comments' } + + method conditional_type (t : ('loc, 'loc) Ast.Type.Conditional.t) = + let open Ast.Type.Conditional in + let { check_type; extends_type; true_type; false_type; comments } = t in + let check_type' = this#type_ check_type in + let extends_type' = this#type_ extends_type in + let true_type' = this#type_ true_type in + let false_type' = this#type_ false_type in + let comments' = this#syntax_opt comments in + if + check_type == check_type' + && extends_type == extends_type' + && true_type == true_type' + && false_type == false_type' + && comments == comments' + then + t + else + { + check_type = check_type'; + extends_type = extends_type'; + true_type = true_type'; + false_type = false_type'; + comments = comments'; + } + + method infer_type (t : ('loc, 'loc) Ast.Type.Infer.t) = + let open Ast.Type.Infer in + let { tparam; comments } = t in + let tparam' = this#type_param ~kind:InferTP tparam in + let comments' = this#syntax_opt comments in + if tparam == tparam' && comments == comments' then + t + else + { tparam = tparam'; comments = comments' } + + method typeof_type (t : ('loc, 'loc) Ast.Type.Typeof.t) = + let open Ast.Type.Typeof in + let { argument; targs; comments } = t in + let argument' = this#typeof_expression argument in + let targs' = map_opt this#type_args targs in + let comments' = this#syntax_opt comments in + if argument == argument' && targs = targs' && comments == comments' then + t + else + { argument = argument'; targs = targs'; comments = comments' } + + method typeof_expression (git : ('loc, 'loc) Ast.Type.Typeof.Target.t) = + let open Ast.Type.Typeof.Target in + match git with + | Unqualified i -> id this#typeof_identifier i git (fun i -> Unqualified i) + | Qualified i -> id this#typeof_qualified_identifier i git (fun i -> Qualified i) + + method typeof_identifier id = this#identifier id + + method typeof_member_identifier id = this#identifier id + + method typeof_qualified_identifier qual = + let open Ast.Type.Typeof.Target in + let (loc, { qualification; id }) = qual in + let qualification' = this#typeof_expression qualification in + let id' = this#typeof_member_identifier id in + if qualification' == qualification && id' == id then + qual + else + (loc, { qualification = qualification'; id = id' }) + + method keyof_type (t : ('loc, 'loc) Ast.Type.Keyof.t) = + let open Ast.Type.Keyof in + let { argument; comments } = t in + let argument' = this#type_ argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + t + else + { argument = argument'; comments = comments' } + + method render_type (t : ('loc, 'loc) Ast.Type.Renders.t) = + let open Ast.Type.Renders in + let { operator_loc; argument; variant; comments } = t in + let argument' = this#type_ argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + t + else + { operator_loc; argument = argument'; comments = comments'; variant } + + method readonly_type (t : ('loc, 'loc) Ast.Type.ReadOnly.t) = + let open Ast.Type.ReadOnly in + let { argument; comments } = t in + let argument' = this#type_ argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + t + else + { argument = argument'; comments = comments' } + + method tuple_type (t : ('loc, 'loc) Ast.Type.Tuple.t) = + let open Ast.Type.Tuple in + let { elements; inexact; comments } = t in + let elements' = map_list this#tuple_element elements in + let comments' = this#syntax_opt comments in + if elements == elements' && comments == comments' then + t + else + { elements = elements'; inexact; comments = comments' } + + method tuple_element (el : ('loc, 'loc) Ast.Type.Tuple.element) = + let open Ast.Type.Tuple in + match el with + | (loc, UnlabeledElement t) -> id this#type_ t el (fun t -> (loc, UnlabeledElement t)) + | (loc, LabeledElement e) -> + id this#tuple_labeled_element e el (fun e -> (loc, LabeledElement e)) + | (loc, SpreadElement e) -> id this#tuple_spread_element e el (fun e -> (loc, SpreadElement e)) + + method tuple_labeled_element (t : ('loc, 'loc) Ast.Type.Tuple.LabeledElement.t) = + let open Ast.Type.Tuple.LabeledElement in + (* Tuple element labels are not bindings so don't map over `name`. *) + let { annot; name; variance; optional } = t in + let annot' = this#type_ annot in + let variance' = this#variance_opt variance in + if annot' == annot && variance' == variance then + t + else + { annot = annot'; name; variance = variance'; optional } + + method tuple_spread_element (t : ('loc, 'loc) Ast.Type.Tuple.SpreadElement.t) = + let open Ast.Type.Tuple.SpreadElement in + let { annot; name } = t in + let annot' = this#type_ annot in + if annot' == annot then + t + else + { annot = annot'; name } + + method array_type (t : ('loc, 'loc) Ast.Type.Array.t) = + let open Ast.Type.Array in + let { argument; comments } = t in + let argument' = this#type_ argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + t + else + { argument = argument'; comments = comments' } + + method union_type _loc (t : ('loc, 'loc) Ast.Type.Union.t) = + let open Ast.Type.Union in + let { types = (t0, t1, ts); comments } = t in + let t0' = this#type_ t0 in + let t1' = this#type_ t1 in + let ts' = map_list this#type_ ts in + let comments' = this#syntax_opt comments in + if t0' == t0 && t1' == t1 && ts' == ts && comments' == comments then + t + else + { types = (t0', t1', ts'); comments = comments' } + + method intersection_type _loc (t : ('loc, 'loc) Ast.Type.Intersection.t) = + let open Ast.Type.Intersection in + let { types = (t0, t1, ts); comments } = t in + let t0' = this#type_ t0 in + let t1' = this#type_ t1 in + let ts' = map_list this#type_ ts in + let comments' = this#syntax_opt comments in + if t0' == t0 && t1' == t1 && ts' == ts && comments' == comments then + t + else + { types = (t0', t1', ts'); comments = comments' } + + method type_ (t : ('loc, 'loc) Ast.Type.t) = + let open Ast.Type in + match t with + | (loc, Any comments) -> id this#syntax_opt comments t (fun comments -> (loc, Any comments)) + | (loc, Mixed comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Mixed comments)) + | (loc, Empty comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Empty comments)) + | (loc, Void comments) -> id this#syntax_opt comments t (fun comments -> (loc, Void comments)) + | (loc, Null comments) -> id this#syntax_opt comments t (fun comments -> (loc, Null comments)) + | (loc, Symbol comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Symbol comments)) + | (loc, Number comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Number comments)) + | (loc, BigInt comments) -> + id this#syntax_opt comments t (fun comments -> (loc, BigInt comments)) + | (loc, String comments) -> + id this#syntax_opt comments t (fun comments -> (loc, String comments)) + | (loc, Boolean { raw; comments }) -> + id this#syntax_opt comments t (fun comments -> (loc, Boolean { raw; comments })) + | (loc, Exists comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Exists comments)) + | (loc, Unknown comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Unknown comments)) + | (loc, Never comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Never comments)) + | (loc, Undefined comments) -> + id this#syntax_opt comments t (fun comments -> (loc, Undefined comments)) + | (loc, Nullable t') -> id this#nullable_type t' t (fun t' -> (loc, Nullable t')) + | (loc, Array t') -> id this#array_type t' t (fun t' -> (loc, Array t')) + | (loc, Conditional t') -> id this#conditional_type t' t (fun t' -> (loc, Conditional t')) + | (loc, Infer t') -> id this#infer_type t' t (fun t' -> (loc, Infer t')) + | (loc, Typeof t') -> id this#typeof_type t' t (fun t' -> (loc, Typeof t')) + | (loc, Keyof t') -> id this#keyof_type t' t (fun t' -> (loc, Keyof t')) + | (loc, Renders t') -> id this#render_type t' t (fun t' -> (loc, Renders t')) + | (loc, ReadOnly t') -> id this#readonly_type t' t (fun t' -> (loc, ReadOnly t')) + | (loc, Function ft) -> id_loc this#function_type loc ft t (fun ft -> (loc, Function ft)) + | (loc, Component ct) -> id_loc this#component_type loc ct t (fun ct -> (loc, Component ct)) + | (loc, Object ot) -> id_loc this#object_type loc ot t (fun ot -> (loc, Object ot)) + | (loc, Interface i) -> id_loc this#interface_type loc i t (fun i -> (loc, Interface i)) + | (loc, Generic gt) -> id_loc this#generic_type loc gt t (fun gt -> (loc, Generic gt)) + | (loc, IndexedAccess ia) -> + id_loc this#indexed_access_type loc ia t (fun ia -> (loc, IndexedAccess ia)) + | (loc, OptionalIndexedAccess ia) -> + id_loc this#optional_indexed_access_type loc ia t (fun ia -> (loc, OptionalIndexedAccess ia)) + | (loc, StringLiteral lit) -> + id_loc this#string_literal loc lit t (fun lit -> (loc, StringLiteral lit)) + | (loc, NumberLiteral lit) -> + id_loc this#number_literal loc lit t (fun lit -> (loc, NumberLiteral lit)) + | (loc, BigIntLiteral lit) -> + id_loc this#bigint_literal loc lit t (fun lit -> (loc, BigIntLiteral lit)) + | (loc, BooleanLiteral lit) -> + id_loc this#boolean_literal loc lit t (fun lit -> (loc, BooleanLiteral lit)) + | (loc, Union t') -> id_loc this#union_type loc t' t (fun t' -> (loc, Union t')) + | (loc, Intersection t') -> + id_loc this#intersection_type loc t' t (fun t' -> (loc, Intersection t')) + | (loc, Tuple t') -> id this#tuple_type t' t (fun t' -> (loc, Tuple t')) + + method type_annotation (annot : ('loc, 'loc) Ast.Type.annotation) = + let (loc, a) = annot in + id this#type_ a annot (fun a -> (loc, a)) + + method type_annotation_hint (return : ('M, 'T) Ast.Type.annotation_or_hint) = + let open Ast.Type in + match return with + | Available annot -> id this#type_annotation annot return (fun a -> Available a) + | Missing _loc -> return + + method component_renders_annotation (renders : ('M, 'T) Ast.Type.component_renders_annotation) = + let open Ast.Type in + match renders with + | AvailableRenders (loc, render_type) -> + let render_type' = this#render_type render_type in + if render_type' == render_type then + renders + else + AvailableRenders (loc, render_type') + | MissingRenders _loc -> renders + + method function_declaration loc (stmt : ('loc, 'loc) Ast.Function.t) = this#function_ loc stmt + + method function_expression loc (stmt : ('loc, 'loc) Ast.Function.t) = + this#function_expression_or_method loc stmt + + (** previously, we conflated [function_expression] and [class_method]. callers should be + updated to override those individually. + + DEPRECATED: use either function_expression or class_method *) + method function_expression_or_method loc (stmt : ('loc, 'loc) Ast.Function.t) = + this#function_ loc stmt + + (* Internal helper for function declarations, function expressions and arrow functions *) + method function_ _loc (expr : ('loc, 'loc) Ast.Function.t) = + let open Ast.Function in + let { + id = ident; + params; + body; + async; + generator; + effect_; + predicate; + return; + tparams; + sig_loc; + comments; + } = + expr + in + let ident' = map_opt this#function_identifier ident in + let tparams' = map_opt (this#type_params ~kind:FunctionTP) tparams in + let params' = this#function_params params in + let return' = this#function_return_annotation return in + let body' = this#function_body_any body in + let predicate' = map_opt this#predicate predicate in + let comments' = this#syntax_opt comments in + if + ident == ident' + && params == params' + && body == body' + && predicate == predicate' + && return == return' + && tparams == tparams' + && comments == comments' + then + expr + else + { + id = ident'; + params = params'; + return = return'; + body = body'; + async; + generator; + effect_; + predicate = predicate'; + tparams = tparams'; + sig_loc; + comments = comments'; + } + + method function_params (params : ('loc, 'loc) Ast.Function.Params.t) = + let open Ast.Function in + let (loc, { Params.params = params_list; rest; comments; this_ }) = params in + let params_list' = map_list this#function_param params_list in + let rest' = map_opt this#function_rest_param rest in + let this_' = map_opt this#function_this_param this_ in + let comments' = this#syntax_opt comments in + if params_list == params_list' && rest == rest' && comments == comments' && this_ == this_' + then + params + else + (loc, { Params.params = params_list'; rest = rest'; comments = comments'; this_ = this_' }) + + method function_this_param (this_param : ('loc, 'loc) Ast.Function.ThisParam.t) = + let open Ast.Function.ThisParam in + let (loc, { annot; comments }) = this_param in + let annot' = this#type_annotation annot in + let comments' = this#syntax_opt comments in + if annot' == annot && comments' == comments then + this_param + else + (loc, { annot = annot'; comments = comments' }) + + method function_param (param : ('loc, 'loc) Ast.Function.Param.t) = + let open Ast.Function.Param in + let (loc, { argument; default }) = param in + let argument' = this#function_param_pattern argument in + let default' = this#default_opt default in + if argument == argument' && default == default' then + param + else + (loc, { argument = argument'; default = default' }) + + method function_return_annotation (return : ('loc, 'loc) Ast.Function.ReturnAnnot.t) = + let open Ast.Function.ReturnAnnot in + match return with + | Missing _loc -> return + | Available t -> id this#type_annotation t return (fun rt -> Available rt) + | TypeGuard g -> id this#type_guard_annotation g return (fun tg -> TypeGuard tg) + + method function_body_any (body : ('loc, 'loc) Ast.Function.body) = + match body with + | Ast.Function.BodyBlock block -> + id this#function_body block body (fun block -> Ast.Function.BodyBlock block) + | Ast.Function.BodyExpression expr -> + id this#body_expression expr body (fun expr -> Ast.Function.BodyExpression expr) + + method function_body (body : 'loc * ('loc, 'loc) Ast.Statement.Block.t) = + let (loc, block) = body in + id_loc this#block loc block body (fun block -> (loc, block)) + + method body_expression (expr : ('loc, 'loc) Ast.Expression.t) = this#expression expr + + method function_identifier (ident : ('loc, 'loc) Ast.Identifier.t) = + this#pattern_identifier ~kind:Ast.Variable.Var ident + + method identifier (id : ('loc, 'loc) Ast.Identifier.t) = + let open Ast.Identifier in + let (loc, { name; comments }) = id in + let comments' = this#syntax_opt comments in + if comments == comments' then + id + else + (loc, { name; comments = comments' }) + + method type_identifier (id : ('loc, 'loc) Ast.Identifier.t) = this#identifier id + + method type_identifier_reference (id : ('loc, 'loc) Ast.Identifier.t) = this#type_identifier id + + method binding_type_identifier (id : ('loc, 'loc) Ast.Identifier.t) = this#type_identifier id + + method interface _loc (interface : ('loc, 'loc) Ast.Statement.Interface.t) = + let open Ast.Statement.Interface in + let { id = ident; tparams; extends; body; comments } = interface in + let id' = this#binding_type_identifier ident in + let tparams' = map_opt (this#type_params ~kind:InterfaceTP) tparams in + let extends' = map_list (map_loc this#generic_type) extends in + let body' = map_loc this#object_type body in + let comments' = this#syntax_opt comments in + if + id' == ident + && tparams' == tparams + && extends' == extends + && body' == body + && comments' == comments + then + interface + else + { id = id'; tparams = tparams'; extends = extends'; body = body'; comments = comments' } + + method interface_declaration loc (decl : ('loc, 'loc) Ast.Statement.Interface.t) = + this#interface loc decl + + method private_name (id : 'loc Ast.PrivateName.t) = + let open Ast.PrivateName in + let (loc, { name; comments }) = id in + let comments' = this#syntax_opt comments in + if comments == comments' then + id + else + (loc, { name; comments = comments' }) + + method computed_key (key : ('loc, 'loc) Ast.ComputedKey.t) = + let open Ast.ComputedKey in + let (loc, { expression; comments }) = key in + let expression' = this#expression expression in + let comments' = this#syntax_opt comments in + if expression == expression' && comments == comments' then + key + else + (loc, { expression = expression'; comments = comments' }) + + method import _loc (expr : ('loc, 'loc) Ast.Expression.Import.t) = + let open Ast.Expression.Import in + let { argument; comments } = expr in + let argument' = this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + expr + else + { argument = argument'; comments = comments' } + + method if_consequent_statement ~has_else (stmt : ('loc, 'loc) Ast.Statement.t) = + ignore has_else; + this#statement stmt + + method if_alternate_statement _loc (altern : ('loc, 'loc) Ast.Statement.If.Alternate.t') = + let open Ast.Statement.If.Alternate in + let { body; comments } = altern in + let body' = this#statement body in + let comments' = this#syntax_opt comments in + if body == body' && comments == comments' then + altern + else + { body = body'; comments = comments' } + + method if_statement _loc (stmt : ('loc, 'loc) Ast.Statement.If.t) = + let open Ast.Statement.If in + let { test; consequent; alternate; comments } = stmt in + let test' = this#predicate_expression test in + let consequent' = this#if_consequent_statement ~has_else:(alternate <> None) consequent in + let alternate' = map_opt (map_loc this#if_alternate_statement) alternate in + let comments' = this#syntax_opt comments in + if + test == test' + && consequent == consequent' + && alternate == alternate' + && comments == comments' + then + stmt + else + { test = test'; consequent = consequent'; alternate = alternate'; comments = comments' } + + method import_declaration _loc (decl : ('loc, 'loc) Ast.Statement.ImportDeclaration.t) = + let open Ast.Statement.ImportDeclaration in + let { import_kind; source; specifiers; default; comments } = decl in + let source' = map_loc this#import_source source in + let specifiers' = map_opt (this#import_specifier ~import_kind) specifiers in + let default' = + map_opt + (fun ({ identifier; remote_default_name_def_loc } as id) -> + let identifier' = this#import_default_specifier ~import_kind identifier in + if identifier' == identifier then + id + else + { identifier = identifier'; remote_default_name_def_loc }) + default + in + let comments' = this#syntax_opt comments in + if + source == source' + && specifiers == specifiers' + && default == default' + && comments == comments' + then + decl + else + { + import_kind; + source = source'; + specifiers = specifiers'; + default = default'; + comments = comments'; + } + + method import_source _loc (source : 'loc Ast.StringLiteral.t) = + let open Ast.StringLiteral in + let { value; raw; comments } = source in + let comments' = this#syntax_opt comments in + if comments == comments' then + source + else + { value; raw; comments = comments' } + + method import_specifier + ~import_kind (specifier : ('loc, 'loc) Ast.Statement.ImportDeclaration.specifier) = + let open Ast.Statement.ImportDeclaration in + match specifier with + | ImportNamedSpecifiers named_specifiers -> + let named_specifiers' = + map_list (this#import_named_specifier ~import_kind) named_specifiers + in + if named_specifiers == named_specifiers' then + specifier + else + ImportNamedSpecifiers named_specifiers' + | ImportNamespaceSpecifier (loc, ident) -> + id_loc (this#import_namespace_specifier ~import_kind) loc ident specifier (fun ident -> + ImportNamespaceSpecifier (loc, ident) + ) + + method remote_identifier id = this#identifier id + + method import_named_specifier + ~(import_kind : Ast.Statement.ImportDeclaration.import_kind) + (specifier : ('loc, 'loc) Ast.Statement.ImportDeclaration.named_specifier) = + let open Ast.Statement.ImportDeclaration in + let { kind; local; remote; remote_name_def_loc } = specifier in + let (is_type_remote, is_type_local) = + match (import_kind, kind) with + | (ImportType, _) + | (_, Some ImportType) -> + (true, true) + | (ImportTypeof, _) + | (_, Some ImportTypeof) -> + (false, true) + | _ -> (false, false) + in + let remote' = + match local with + | None -> + if is_type_remote then + this#binding_type_identifier remote + else + this#pattern_identifier ~kind:Ast.Variable.Let remote + | Some _ -> this#remote_identifier remote + in + let local' = + match local with + | None -> None + | Some ident -> + let local_visitor = + if is_type_local then + this#binding_type_identifier + else + this#pattern_identifier ~kind:Ast.Variable.Let + in + id local_visitor ident local (fun ident -> Some ident) + in + if local == local' && remote == remote' then + specifier + else + { kind; local = local'; remote = remote'; remote_name_def_loc } + + method import_default_specifier ~import_kind (id : ('loc, 'loc) Ast.Identifier.t) = + let open Ast.Statement.ImportDeclaration in + let local_visitor = + match import_kind with + | ImportType + | ImportTypeof -> + this#binding_type_identifier + | _ -> this#pattern_identifier ~kind:Ast.Variable.Let + in + local_visitor id + + method import_namespace_specifier ~import_kind _loc (id : ('loc, 'loc) Ast.Identifier.t) = + let open Ast.Statement.ImportDeclaration in + let local_visitor = + match import_kind with + | ImportType + | ImportTypeof -> + this#binding_type_identifier + | _ -> this#pattern_identifier ~kind:Ast.Variable.Let + in + local_visitor id + + method jsx_element _loc (expr : ('loc, 'loc) Ast.JSX.element) = + let open Ast.JSX in + let { opening_element; closing_element; children; comments } = expr in + let opening_element' = this#jsx_opening_element opening_element in + let closing_element' = map_opt this#jsx_closing_element closing_element in + let children' = this#jsx_children children in + let comments' = this#syntax_opt comments in + if + opening_element == opening_element' + && closing_element == closing_element' + && children == children' + && comments == comments' + then + expr + else + { + opening_element = opening_element'; + closing_element = closing_element'; + children = children'; + comments = comments'; + } + + method jsx_fragment _loc (expr : ('loc, 'loc) Ast.JSX.fragment) = + let open Ast.JSX in + let { frag_children; frag_comments; _ } = expr in + let children' = this#jsx_children frag_children in + let frag_comments' = this#syntax_opt frag_comments in + if frag_children == children' && frag_comments == frag_comments' then + expr + else + { expr with frag_children = children'; frag_comments = frag_comments' } + + method jsx_opening_element (elem : ('loc, 'loc) Ast.JSX.Opening.t) = + let open Ast.JSX.Opening in + let (loc, { name; targs; self_closing; attributes }) = elem in + let name' = this#jsx_element_name name in + let targs' = map_opt this#call_type_args targs in + let attributes' = map_list this#jsx_opening_attribute attributes in + if name == name' && targs == targs' && attributes == attributes' then + elem + else + (loc, { name = name'; targs = targs'; self_closing; attributes = attributes' }) + + method jsx_closing_element (elem : ('loc, 'loc) Ast.JSX.Closing.t) = + let open Ast.JSX.Closing in + let (loc, { name }) = elem in + let name' = this#jsx_element_name name in + if name == name' then + elem + else + (loc, { name = name' }) + + method jsx_opening_attribute (jsx_attr : ('loc, 'loc) Ast.JSX.Opening.attribute) = + let open Ast.JSX.Opening in + match jsx_attr with + | Attribute attr -> id this#jsx_attribute attr jsx_attr (fun attr -> Attribute attr) + | SpreadAttribute (loc, attr) -> + id_loc this#jsx_spread_attribute loc attr jsx_attr (fun attr -> SpreadAttribute (loc, attr)) + + method jsx_spread_attribute _loc (attr : ('loc, 'loc) Ast.JSX.SpreadAttribute.t') = + let open Ast.JSX.SpreadAttribute in + let { argument; comments } = attr in + let argument' = this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + attr + else + { argument = argument'; comments = comments' } + + method jsx_attribute (attr : ('loc, 'loc) Ast.JSX.Attribute.t) = + let open Ast.JSX.Attribute in + let (loc, { name; value }) = attr in + let name' = this#jsx_attribute_name name in + let value' = map_opt this#jsx_attribute_value value in + if name == name' && value == value' then + attr + else + (loc, { name = name'; value = value' }) + + method jsx_attribute_name (name : ('loc, 'loc) Ast.JSX.Attribute.name) = + let open Ast.JSX.Attribute in + match name with + | Identifier ident -> + id this#jsx_attribute_name_identifier ident name (fun ident -> Identifier ident) + | NamespacedName ns -> + id this#jsx_attribute_name_namespaced ns name (fun ns -> NamespacedName ns) + + method jsx_attribute_name_identifier ident = this#jsx_identifier ident + + method jsx_attribute_name_namespaced ns = this#jsx_namespaced_name ns + + method jsx_attribute_value (value : ('loc, 'loc) Ast.JSX.Attribute.value) = + let open Ast.JSX.Attribute in + match value with + | StringLiteral (loc, lit) -> + id_loc this#jsx_attribute_value_literal loc lit value (fun lit -> StringLiteral (loc, lit)) + | ExpressionContainer (loc, expr) -> + id_loc this#jsx_attribute_value_expression loc expr value (fun expr -> + ExpressionContainer (loc, expr) + ) + + method jsx_attribute_value_expression loc (jsx_expr : ('loc, 'loc) Ast.JSX.ExpressionContainer.t) + = + this#jsx_expression loc jsx_expr + + method jsx_attribute_value_literal loc (lit : 'loc Ast.StringLiteral.t) = + this#string_literal loc lit + + method jsx_children ((loc, children) as orig : 'loc * ('loc, 'loc) Ast.JSX.child list) = + let children' = map_list this#jsx_child children in + if children == children' then + orig + else + (loc, children') + + method jsx_child (child : ('loc, 'loc) Ast.JSX.child) = + let open Ast.JSX in + match child with + | (loc, Element elem) -> + id_loc this#jsx_element loc elem child (fun elem -> (loc, Element elem)) + | (loc, Fragment frag) -> + id_loc this#jsx_fragment loc frag child (fun frag -> (loc, Fragment frag)) + | (loc, ExpressionContainer expr) -> + id_loc this#jsx_expression loc expr child (fun expr -> (loc, ExpressionContainer expr)) + | (loc, SpreadChild spread) -> + id this#jsx_spread_child spread child (fun spread -> (loc, SpreadChild spread)) + | (_loc, Text _) -> child + + method jsx_expression _loc (jsx_expr : ('loc, 'loc) Ast.JSX.ExpressionContainer.t) = + let open Ast.JSX.ExpressionContainer in + let { expression; comments } = jsx_expr in + let comments' = this#syntax_opt comments in + match expression with + | Expression expr -> + let expr' = this#expression expr in + if expr == expr' && comments == comments' then + jsx_expr + else + { expression = Expression expr'; comments = comments' } + | EmptyExpression -> + if comments == comments' then + jsx_expr + else + { expression = EmptyExpression; comments = comments' } + + method jsx_spread_child (jsx_spread_child : ('loc, 'loc) Ast.JSX.SpreadChild.t) = + let open Ast.JSX.SpreadChild in + let { expression; comments } = jsx_spread_child in + let expression' = this#expression expression in + let comments' = this#syntax_opt comments in + if expression == expression' && comments == comments' then + jsx_spread_child + else + { expression = expression'; comments = comments' } + + method jsx_element_name (name : ('loc, 'loc) Ast.JSX.name) = + let open Ast.JSX in + match name with + | Identifier ident -> + id this#jsx_element_name_identifier ident name (fun ident -> Identifier ident) + | NamespacedName ns -> + id this#jsx_element_name_namespaced ns name (fun ns -> NamespacedName ns) + | MemberExpression expr -> + id this#jsx_element_name_member_expression expr name (fun expr -> MemberExpression expr) + + method jsx_element_name_identifier ident = this#jsx_identifier ident + + method jsx_element_name_namespaced ns = this#jsx_namespaced_name ns + + method jsx_element_name_member_expression expr = this#jsx_member_expression expr + + method jsx_namespaced_name (namespaced_name : ('loc, 'loc) Ast.JSX.NamespacedName.t) = + let open Ast.JSX in + NamespacedName.( + let (loc, { namespace; name }) = namespaced_name in + let namespace' = this#jsx_identifier namespace in + let name' = this#jsx_identifier name in + if namespace == namespace' && name == name' then + namespaced_name + else + (loc, { namespace = namespace'; name = name' }) + ) + + method jsx_member_expression (member_exp : ('loc, 'loc) Ast.JSX.MemberExpression.t) = + let open Ast.JSX in + let (loc, { MemberExpression._object; MemberExpression.property }) = member_exp in + let _object' = this#jsx_member_expression_object _object in + let property' = this#jsx_identifier property in + if _object == _object' && property == property' then + member_exp + else + (loc, MemberExpression.{ _object = _object'; property = property' }) + + method jsx_member_expression_object (_object : ('loc, 'loc) Ast.JSX.MemberExpression._object) = + let open Ast.JSX.MemberExpression in + match _object with + | Identifier ident -> + id this#jsx_member_expression_identifier ident _object (fun ident -> Identifier ident) + | MemberExpression nested_exp -> + id this#jsx_member_expression nested_exp _object (fun exp -> MemberExpression exp) + + method jsx_member_expression_identifier ident = this#jsx_element_name_identifier ident + + method jsx_identifier (id : ('loc, 'loc) Ast.JSX.Identifier.t) = + let open Ast.JSX.Identifier in + let (loc, { name; comments }) = id in + let comments' = this#syntax_opt comments in + if comments == comments' then + id + else + (loc, { name; comments = comments' }) + + method labeled_statement _loc (stmt : ('loc, 'loc) Ast.Statement.Labeled.t) = + let open Ast.Statement.Labeled in + let { label; body; comments } = stmt in + let label' = this#label_identifier label in + let body' = this#statement body in + let comments' = this#syntax_opt comments in + if label == label' && body == body' && comments == comments' then + stmt + else + { label = label'; body = body'; comments = comments' } + + method logical _loc (expr : ('loc, 'loc) Ast.Expression.Logical.t) = + let open Ast.Expression.Logical in + let { operator = _; left; right; comments } = expr in + let left' = this#expression left in + let right' = this#expression right in + let comments' = this#syntax_opt comments in + if left == left' && right == right' && comments == comments' then + expr + else + { expr with left = left'; right = right'; comments = comments' } + + method match_ + : 'B. + 'loc -> + on_case_body:('B -> 'B) -> + ('loc, 'loc, 'B) Ast.Match.t -> + ('loc, 'loc, 'B) Ast.Match.t = + fun _loc ~on_case_body x -> + let open Ast.Match in + let { arg; cases; match_keyword_loc; comments } = x in + let arg' = this#expression arg in + let cases' = map_list (this#match_case ~on_case_body) cases in + let comments' = this#syntax_opt comments in + if arg == arg' && cases == cases' && comments == comments' then + x + else + { arg = arg'; cases = cases'; match_keyword_loc; comments = comments' } + + method match_case + : 'B. + on_case_body:('B -> 'B) -> + ('loc, 'loc, 'B) Ast.Match.Case.t -> + ('loc, 'loc, 'B) Ast.Match.Case.t = + fun ~on_case_body case -> + let open Ast.Match.Case in + let (loc, { pattern; body; guard; comments }) = case in + let pattern' = this#match_pattern pattern in + let body' = on_case_body body in + let guard' = map_opt this#expression guard in + let comments' = this#syntax_opt comments in + if pattern == pattern' && body == body' && guard == guard' && comments == comments' then + case + else + (loc, { pattern = pattern'; body = body'; guard = guard'; comments = comments' }) + + method match_expression loc (x : ('loc, 'loc) Ast.Expression.match_expression) = + this#match_ loc ~on_case_body:this#expression x + + method match_statement loc (x : ('loc, 'loc) Ast.Statement.match_statement) = + this#match_ loc ~on_case_body:this#statement x + + method match_pattern (pattern : ('loc, 'loc) Ast.MatchPattern.t) = + let open Ast.MatchPattern in + match pattern with + | (loc, WildcardPattern x) -> id this#syntax_opt x pattern (fun x -> (loc, WildcardPattern x)) + | (loc, StringPattern x) -> + id_loc this#string_literal loc x pattern (fun x -> (loc, StringPattern x)) + | (loc, BooleanPattern x) -> + id_loc this#boolean_literal loc x pattern (fun x -> (loc, BooleanPattern x)) + | (loc, NullPattern x) -> id this#syntax_opt x pattern (fun x -> (loc, NullPattern x)) + | (loc, NumberPattern x) -> + id_loc this#number_literal loc x pattern (fun x -> (loc, NumberPattern x)) + | (loc, BigIntPattern x) -> + id_loc this#bigint_literal loc x pattern (fun x -> (loc, BigIntPattern x)) + | (loc, UnaryPattern x) -> + id this#match_unary_pattern x pattern (fun x -> (loc, UnaryPattern x)) + | (loc, IdentifierPattern x) -> + id this#identifier x pattern (fun x -> (loc, IdentifierPattern x)) + | (loc, MemberPattern x) -> + id this#match_member_pattern x pattern (fun x -> (loc, MemberPattern x)) + | (loc, BindingPattern x) -> + id_loc this#match_binding_pattern loc x pattern (fun x -> (loc, BindingPattern x)) + | (loc, ObjectPattern x) -> + id this#match_object_pattern x pattern (fun x -> (loc, ObjectPattern x)) + | (loc, ArrayPattern x) -> + id this#match_array_pattern x pattern (fun x -> (loc, ArrayPattern x)) + | (loc, OrPattern x) -> id this#match_or_pattern x pattern (fun x -> (loc, OrPattern x)) + | (loc, AsPattern x) -> id this#match_as_pattern x pattern (fun x -> (loc, AsPattern x)) + + method match_unary_pattern (unary_pattern : 'loc Ast.MatchPattern.UnaryPattern.t) = + let open Ast.MatchPattern.UnaryPattern in + let { operator; argument; comments } = unary_pattern in + let (arg_loc, arg) = argument in + let argument' = + id_loc this#match_unary_pattern_argument arg_loc arg argument (fun arg -> (arg_loc, arg)) + in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + unary_pattern + else + { operator; argument = argument'; comments = comments' } + + method match_unary_pattern_argument loc (argument : 'loc Ast.MatchPattern.UnaryPattern.argument) + = + let open Ast.MatchPattern.UnaryPattern in + match argument with + | NumberLiteral lit -> + id_loc this#number_literal loc lit argument (fun lit -> NumberLiteral lit) + | BigIntLiteral lit -> + id_loc this#bigint_literal loc lit argument (fun lit -> BigIntLiteral lit) + + method match_member_pattern (member_pattern : ('loc, 'loc) Ast.MatchPattern.MemberPattern.t) = + let open Ast.MatchPattern.MemberPattern in + let (loc, { base; property; comments }) = member_pattern in + let base' = this#match_member_pattern_base base in + let property' = this#match_member_pattern_property property in + let comments' = this#syntax_opt comments in + if base == base' && property == property' && comments == comments' then + member_pattern + else + (loc, { base = base'; property = property'; comments = comments' }) + + method match_member_pattern_base (base : ('loc, 'loc) Ast.MatchPattern.MemberPattern.base) = + let open Ast.MatchPattern.MemberPattern in + match base with + | BaseIdentifier x -> id this#identifier x base (fun x -> BaseIdentifier x) + | BaseMember x -> id this#match_member_pattern x base (fun x -> BaseMember x) + + method match_member_pattern_property + (prop : ('loc, 'loc) Ast.MatchPattern.MemberPattern.property) = + let open Ast.MatchPattern.MemberPattern in + match prop with + | PropertyString (loc, lit) -> + id_loc this#string_literal loc lit prop (fun lit -> PropertyString (loc, lit)) + | PropertyNumber (loc, lit) -> + id_loc this#number_literal loc lit prop (fun lit -> PropertyNumber (loc, lit)) + | PropertyBigInt (loc, lit) -> + id_loc this#bigint_literal loc lit prop (fun lit -> PropertyBigInt (loc, lit)) + | PropertyIdentifier ident -> + id this#identifier ident prop (fun ident -> PropertyIdentifier ident) + + method match_binding_pattern + _loc (binding_pattern : ('loc, 'loc) Ast.MatchPattern.BindingPattern.t) = + let open Ast.MatchPattern.BindingPattern in + let { id; kind; comments } = binding_pattern in + let id' = this#pattern_identifier ~kind id in + let comments' = this#syntax_opt comments in + if id == id' && comments == comments' then + binding_pattern + else + { id = id'; kind; comments = comments' } + + method match_object_pattern (object_pattern : ('loc, 'loc) Ast.MatchPattern.ObjectPattern.t) = + let open Ast.MatchPattern.ObjectPattern in + let { properties; rest; comments } = object_pattern in + let properties' = map_list this#match_object_pattern_property properties in + let rest' = map_loc_opt this#match_rest_pattern rest in + let comments' = this#syntax_opt comments in + if properties == properties' && rest == rest' && comments == comments' then + object_pattern + else + { properties = properties'; rest = rest'; comments = comments' } + + method match_object_pattern_property + (prop : ('loc, 'loc) Ast.MatchPattern.ObjectPattern.Property.t) = + let open Ast.MatchPattern.ObjectPattern.Property in + match prop with + | (loc, Valid { key; pattern; shorthand; comments }) -> + let key' = this#match_object_pattern_property_key key in + let pattern' = this#match_pattern pattern in + let comments' = this#syntax_opt comments in + if key == key' && pattern == pattern' && comments == comments' then + prop + else + (loc, Valid { key = key'; pattern = pattern'; shorthand; comments = comments' }) + | (loc, InvalidShorthand id) -> + let id' = this#identifier id in + if id == id' then + prop + else + (loc, InvalidShorthand id') + + method match_object_pattern_property_key + (key : ('loc, 'loc) Ast.MatchPattern.ObjectPattern.Property.key) = + let open Ast.MatchPattern.ObjectPattern.Property in + match key with + | StringLiteral (loc, lit) -> + id_loc this#string_literal loc lit key (fun lit -> StringLiteral (loc, lit)) + | NumberLiteral (loc, lit) -> + id_loc this#number_literal loc lit key (fun lit -> NumberLiteral (loc, lit)) + | BigIntLiteral (loc, lit) -> + id_loc this#bigint_literal loc lit key (fun lit -> BigIntLiteral (loc, lit)) + | Identifier ident -> id this#identifier ident key (fun ident -> Identifier ident) + + method match_array_pattern (array_pattern : ('loc, 'loc) Ast.MatchPattern.ArrayPattern.t) = + let open Ast.MatchPattern.ArrayPattern in + let { elements; rest; comments } = array_pattern in + let elements' = map_list this#match_pattern_array_element elements in + let rest' = map_loc_opt this#match_rest_pattern rest in + let comments' = this#syntax_opt comments in + if elements == elements' && rest == rest' && comments == comments' then + array_pattern + else + { elements = elements'; rest = rest'; comments = comments' } + + method match_pattern_array_element + (element : ('loc, 'loc) Ast.MatchPattern.ArrayPattern.Element.t) = + let open Ast.MatchPattern.ArrayPattern.Element in + let { pattern; index } = element in + let pattern' = this#match_pattern pattern in + if pattern == pattern' then + element + else + { pattern = pattern'; index } + + method match_rest_pattern _loc (rest : ('loc, 'loc) Ast.MatchPattern.RestPattern.t') = + let open Ast.MatchPattern.RestPattern in + let { argument; comments } = rest in + let argument' = map_loc_opt this#match_binding_pattern argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + rest + else + { argument = argument'; comments = comments' } + + method match_or_pattern (or_pattern : ('loc, 'loc) Ast.MatchPattern.OrPattern.t) = + let open Ast.MatchPattern.OrPattern in + let { patterns; comments } = or_pattern in + let patterns' = map_list this#match_pattern patterns in + let comments' = this#syntax_opt comments in + if patterns == patterns' && comments == comments' then + or_pattern + else + { patterns = patterns'; comments = comments' } + + method match_as_pattern (as_pattern : ('loc, 'loc) Ast.MatchPattern.AsPattern.t) = + let open Ast.MatchPattern.AsPattern in + let { pattern; target; comments } = as_pattern in + let pattern' = this#match_pattern pattern in + let target' = this#match_as_pattern_target target in + let comments' = this#syntax_opt comments in + if pattern == pattern' && target == target' && comments == comments' then + as_pattern + else + { pattern = pattern'; target = target'; comments = comments' } + + method match_as_pattern_target (target : ('loc, 'loc) Ast.MatchPattern.AsPattern.target) = + let open Ast.MatchPattern.AsPattern in + match target with + | Binding (loc, binding) -> + id_loc this#match_binding_pattern loc binding target (fun x -> Binding (loc, x)) + | Identifier ident -> + id (this#pattern_identifier ~kind:Ast.Variable.Const) ident target (fun x -> Identifier x) + + method member _loc (expr : ('loc, 'loc) Ast.Expression.Member.t) = + let open Ast.Expression.Member in + let { _object; property; comments } = expr in + let _object' = this#expression _object in + let property' = this#member_property property in + let comments' = this#syntax_opt comments in + if _object == _object' && property == property' && comments == comments' then + expr + else + { _object = _object'; property = property'; comments = comments' } + + method optional_member loc (expr : ('loc, 'loc) Ast.Expression.OptionalMember.t) = + let open Ast.Expression.OptionalMember in + let { member; optional = _; filtered_out = _ } = expr in + let member' = this#member loc member in + if member == member' then + expr + else + { expr with member = member' } + + method member_property (expr : ('loc, 'loc) Ast.Expression.Member.property) = + let open Ast.Expression.Member in + match expr with + | PropertyIdentifier ident -> + id this#member_property_identifier ident expr (fun ident -> PropertyIdentifier ident) + | PropertyPrivateName ident -> + id this#member_private_name ident expr (fun ident -> PropertyPrivateName ident) + | PropertyExpression e -> + id this#member_property_expression e expr (fun e -> PropertyExpression e) + + method member_property_identifier (ident : ('loc, 'loc) Ast.Identifier.t) = + this#identifier ident + + method member_private_name (name : 'loc Ast.PrivateName.t) = this#private_name name + + method member_property_expression (expr : ('loc, 'loc) Ast.Expression.t) = this#expression expr + + method meta_property _loc (expr : 'loc Ast.Expression.MetaProperty.t) = + let open Ast.Expression.MetaProperty in + let { meta; property; comments } = expr in + let meta' = this#identifier meta in + let property' = this#identifier property in + let comments' = this#syntax_opt comments in + if meta == meta' && property == property' && comments == comments' then + expr + else + { meta = meta'; property = property'; comments = comments' } + + method new_ _loc (expr : ('loc, 'loc) Ast.Expression.New.t) = + let open Ast.Expression.New in + let { callee; targs; arguments; comments } = expr in + let callee' = this#expression callee in + let targs' = map_opt this#call_type_args targs in + let arguments' = map_opt this#arg_list arguments in + let comments' = this#syntax_opt comments in + if callee == callee' && targs == targs' && arguments == arguments' && comments == comments' + then + expr + else + { callee = callee'; targs = targs'; arguments = arguments'; comments = comments' } + + method object_ _loc (expr : ('loc, 'loc) Ast.Expression.Object.t) = + let open Ast.Expression.Object in + let { properties; comments } = expr in + let properties' = + map_list + (fun prop -> + match prop with + | Property p -> + let p' = this#object_property p in + if p == p' then + prop + else + Property p' + | SpreadProperty s -> + let s' = this#spread_property s in + if s == s' then + prop + else + SpreadProperty s') + properties + in + let comments' = this#syntax_opt comments in + if properties == properties' && comments == comments' then + expr + else + { properties = properties'; comments = comments' } + + method object_property (prop : ('loc, 'loc) Ast.Expression.Object.Property.t) = + let open Ast.Expression.Object.Property in + match prop with + | (loc, Init { key; value; shorthand }) -> + let key' = this#object_key key in + let value' = this#expression value in + let shorthand' = + (* Try to figure out if shorthand should still be true--if + key and value change differently, it should become false *) + shorthand + && + match (key', value') with + | ( Identifier (_, { Ast.Identifier.name = key_name; _ }), + (_, Ast.Expression.Identifier (_, { Ast.Identifier.name = value_name; _ })) + ) -> + String.equal key_name value_name + | _ -> key == key' && value == value' + in + if key == key' && value == value' && shorthand == shorthand' then + prop + else + (loc, Init { key = key'; value = value'; shorthand = shorthand' }) + | (loc, Method { key; value = fn }) -> + let key' = this#object_key key in + let fn' = map_loc this#function_expression_or_method fn in + if key == key' && fn == fn' then + prop + else + (loc, Method { key = key'; value = fn' }) + | (loc, Get { key; value = fn; comments }) -> + let key' = this#object_key key in + let fn' = map_loc this#function_expression_or_method fn in + let comments' = this#syntax_opt comments in + if key == key' && fn == fn' && comments == comments' then + prop + else + (loc, Get { key = key'; value = fn'; comments = comments' }) + | (loc, Set { key; value = fn; comments }) -> + let key' = this#object_key key in + let fn' = map_loc this#function_expression_or_method fn in + let comments' = this#syntax_opt comments in + if key == key' && fn == fn' && comments == comments' then + prop + else + (loc, Set { key = key'; value = fn'; comments = comments' }) + + method object_key (key : ('loc, 'loc) Ast.Expression.Object.Property.key) = + let open Ast.Expression.Object.Property in + match key with + | StringLiteral lit -> id this#object_key_string_literal lit key (fun lit -> StringLiteral lit) + | NumberLiteral lit -> id this#object_key_number_literal lit key (fun lit -> NumberLiteral lit) + | BigIntLiteral lit -> id this#object_key_bigint_literal lit key (fun lit -> BigIntLiteral lit) + | Identifier ident -> id this#object_key_identifier ident key (fun ident -> Identifier ident) + | PrivateName ident -> id this#private_name ident key (fun ident -> PrivateName ident) + | Computed computed -> id this#object_key_computed computed key (fun expr -> Computed expr) + + method object_key_string_literal (literal : 'loc * 'loc Ast.StringLiteral.t) = + let (loc, lit) = literal in + id_loc this#string_literal loc lit literal (fun lit -> (loc, lit)) + + method object_key_number_literal (literal : 'loc * 'loc Ast.NumberLiteral.t) = + let (loc, lit) = literal in + id_loc this#number_literal loc lit literal (fun lit -> (loc, lit)) + + method object_key_bigint_literal (literal : 'loc * 'loc Ast.BigIntLiteral.t) = + let (loc, lit) = literal in + id_loc this#bigint_literal loc lit literal (fun lit -> (loc, lit)) + + method object_key_identifier (ident : ('loc, 'loc) Ast.Identifier.t) = this#identifier ident + + method object_key_computed (key : ('loc, 'loc) Ast.ComputedKey.t) = this#computed_key key + + method opaque_type _loc (otype : ('loc, 'loc) Ast.Statement.OpaqueType.t) = + let open Ast.Statement.OpaqueType in + let { id; tparams; impltype; supertype; comments } = otype in + let id' = this#binding_type_identifier id in + let tparams' = map_opt (this#type_params ~kind:OpaqueTypeTP) tparams in + let impltype' = map_opt this#type_ impltype in + let supertype' = map_opt this#type_ supertype in + let comments' = this#syntax_opt comments in + if + id == id' + && impltype == impltype' + && tparams == tparams' + && impltype == impltype' + && supertype == supertype' + && comments == comments' + then + otype + else + { + id = id'; + tparams = tparams'; + impltype = impltype'; + supertype = supertype'; + comments = comments'; + } + + method function_param_pattern (expr : ('loc, 'loc) Ast.Pattern.t) = + this#binding_pattern ~kind:Ast.Variable.Let expr + + method variable_declarator_pattern ~kind (expr : ('loc, 'loc) Ast.Pattern.t) = + this#binding_pattern ~kind expr + + method catch_clause_pattern (expr : ('loc, 'loc) Ast.Pattern.t) = + this#binding_pattern ~kind:Ast.Variable.Let expr + + method for_in_assignment_pattern (expr : ('loc, 'loc) Ast.Pattern.t) = + this#assignment_pattern expr + + method for_of_assignment_pattern (expr : ('loc, 'loc) Ast.Pattern.t) = + this#assignment_pattern expr + + method binding_pattern ?(kind = Ast.Variable.Var) (expr : ('loc, 'loc) Ast.Pattern.t) = + this#pattern ~kind expr + + method assignment_pattern (expr : ('loc, 'loc) Ast.Pattern.t) = this#pattern expr + + (* NOTE: Patterns are highly overloaded. A pattern can be a binding pattern, + which has a kind (Var/Let/Const, with Var being the default for all pre-ES5 + bindings), or an assignment pattern, which has no kind. Subterms that are + patterns inherit the kind (or lack thereof). *) + method pattern ?kind (expr : ('loc, 'loc) Ast.Pattern.t) = + let open Ast.Pattern in + let (loc, patt) = expr in + let patt' = + match patt with + | Object { Object.properties; annot; comments } -> + let properties' = map_list (this#pattern_object_p ?kind) properties in + let annot' = this#type_annotation_hint annot in + let comments' = this#syntax_opt comments in + if properties' == properties && annot' == annot && comments' == comments then + patt + else + Object { Object.properties = properties'; annot = annot'; comments = comments' } + | Array { Array.elements; annot; comments } -> + let elements' = map_list (this#pattern_array_e ?kind) elements in + let annot' = this#type_annotation_hint annot in + let comments' = this#syntax_opt comments in + if comments == comments' && elements' == elements && annot' == annot then + patt + else + Array { Array.elements = elements'; annot = annot'; comments = comments' } + | Identifier { Identifier.name; annot; optional } -> + let name' = this#pattern_identifier ?kind name in + let annot' = this#type_annotation_hint annot in + if name == name' && annot == annot' then + patt + else + Identifier { Identifier.name = name'; annot = annot'; optional } + | Expression e -> id this#pattern_expression e patt (fun e -> Expression e) + in + if patt == patt' then + expr + else + (loc, patt') + + method pattern_identifier ?kind (ident : ('loc, 'loc) Ast.Identifier.t) = + ignore kind; + this#identifier ident + + method pattern_string_literal ?kind loc (expr : 'loc Ast.StringLiteral.t) = + ignore kind; + this#string_literal loc expr + + method pattern_number_literal ?kind loc (expr : 'loc Ast.NumberLiteral.t) = + ignore kind; + this#number_literal loc expr + + method pattern_bigint_literal ?kind loc (expr : 'loc Ast.BigIntLiteral.t) = + ignore kind; + this#bigint_literal loc expr + + method pattern_object_p ?kind (p : ('loc, 'loc) Ast.Pattern.Object.property) = + let open Ast.Pattern.Object in + match p with + | Property prop -> id (this#pattern_object_property ?kind) prop p (fun prop -> Property prop) + | RestElement prop -> + id (this#pattern_object_rest_property ?kind) prop p (fun prop -> RestElement prop) + + method pattern_object_property ?kind (prop : ('loc, 'loc) Ast.Pattern.Object.Property.t) = + let open Ast.Pattern.Object.Property in + let (loc, { key; pattern; default; shorthand }) = prop in + let key' = this#pattern_object_property_key ?kind key in + let pattern' = this#pattern_object_property_pattern ?kind pattern in + let default' = this#default_opt default in + let shorthand' = + (* Try to figure out if shorthand should still be true--if + key and value change differently, it should become false *) + shorthand + && + match (key', pattern') with + | ( Identifier (_, { Ast.Identifier.name = key_name; _ }), + ( _, + Ast.Pattern.Identifier + { Ast.Pattern.Identifier.name = (_, { Ast.Identifier.name = value_name; _ }); _ } + ) + ) -> + String.equal key_name value_name + | _ -> key == key' && pattern == pattern' + in + if key' == key && pattern' == pattern && default' == default && shorthand == shorthand' then + prop + else + (loc, { key = key'; pattern = pattern'; default = default'; shorthand = shorthand' }) + + method pattern_object_property_key ?kind (key : ('loc, 'loc) Ast.Pattern.Object.Property.key) = + let open Ast.Pattern.Object.Property in + match key with + | StringLiteral lit -> + id (this#pattern_object_property_string_literal_key ?kind) lit key (fun lit' -> + StringLiteral lit' + ) + | NumberLiteral lit -> + id (this#pattern_object_property_number_literal_key ?kind) lit key (fun lit' -> + NumberLiteral lit' + ) + | BigIntLiteral lit -> + id (this#pattern_object_property_bigint_literal_key ?kind) lit key (fun lit' -> + BigIntLiteral lit' + ) + | Identifier identifier -> + id (this#pattern_object_property_identifier_key ?kind) identifier key (fun id' -> + Identifier id' + ) + | Computed expr -> + id (this#pattern_object_property_computed_key ?kind) expr key (fun expr' -> Computed expr') + + method pattern_object_property_string_literal_key + ?kind (literal : 'loc * 'loc Ast.StringLiteral.t) = + let (loc, key) = literal in + id_loc (this#pattern_string_literal ?kind) loc key literal (fun key' -> (loc, key')) + + method pattern_object_property_number_literal_key + ?kind (literal : 'loc * 'loc Ast.NumberLiteral.t) = + let (loc, key) = literal in + id_loc (this#pattern_number_literal ?kind) loc key literal (fun key' -> (loc, key')) + + method pattern_object_property_bigint_literal_key + ?kind (literal : 'loc * 'loc Ast.BigIntLiteral.t) = + let (loc, key) = literal in + id_loc (this#pattern_bigint_literal ?kind) loc key literal (fun key' -> (loc, key')) + + method pattern_object_property_identifier_key ?kind (key : ('loc, 'loc) Ast.Identifier.t) = + this#pattern_identifier ?kind key + + method pattern_object_property_computed_key ?kind (key : ('loc, 'loc) Ast.ComputedKey.t) = + ignore kind; + this#computed_key key + + method pattern_object_rest_property ?kind (prop : ('loc, 'loc) Ast.Pattern.RestElement.t) = + let open Ast.Pattern.RestElement in + let (loc, { argument; comments }) = prop in + let argument' = this#pattern_object_rest_property_pattern ?kind argument in + let comments' = this#syntax_opt comments in + if argument' == argument && comments == comments' then + prop + else + (loc, { argument = argument'; comments = comments' }) + + method pattern_object_property_pattern ?kind (expr : ('loc, 'loc) Ast.Pattern.t) = + this#pattern ?kind expr + + method pattern_object_rest_property_pattern ?kind (expr : ('loc, 'loc) Ast.Pattern.t) = + this#pattern ?kind expr + + method pattern_array_e ?kind (e : ('loc, 'loc) Ast.Pattern.Array.element) = + let open Ast.Pattern.Array in + match e with + | Hole _ -> e + | Element elem -> id (this#pattern_array_element ?kind) elem e (fun elem -> Element elem) + | RestElement elem -> + id (this#pattern_array_rest_element ?kind) elem e (fun elem -> RestElement elem) + + method pattern_array_element ?kind (elem : ('loc, 'loc) Ast.Pattern.Array.Element.t) = + let open Ast.Pattern.Array.Element in + let (loc, { argument; default }) = elem in + let argument' = this#pattern_array_element_pattern ?kind argument in + let default' = this#default_opt default in + if argument == argument' && default == default' then + elem + else + (loc, { argument = argument'; default = default' }) + + method pattern_array_element_pattern ?kind (patt : ('loc, 'loc) Ast.Pattern.t) = + this#pattern ?kind patt + + method pattern_array_rest_element ?kind (elem : ('loc, 'loc) Ast.Pattern.RestElement.t) = + let open Ast.Pattern.RestElement in + let (loc, { argument; comments }) = elem in + let argument' = this#pattern_array_rest_element_pattern ?kind argument in + let comments' = this#syntax_opt comments in + if argument' == argument && comments == comments' then + elem + else + (loc, { argument = argument'; comments = comments' }) + + method pattern_array_rest_element_pattern ?kind (expr : ('loc, 'loc) Ast.Pattern.t) = + this#pattern ?kind expr + + method pattern_expression (expr : ('loc, 'loc) Ast.Expression.t) = this#expression expr + + method predicate (pred : ('loc, 'loc) Ast.Type.Predicate.t) = + let open Ast.Type.Predicate in + let (loc, { kind; comments }) = pred in + let kind' = + match kind with + | Inferred -> kind + | Declared expr -> id this#expression expr kind (fun expr' -> Declared expr') + in + let comments' = this#syntax_opt comments in + if kind == kind' && comments == comments' then + pred + else + (loc, { kind = kind'; comments = comments' }) + + method predicate_expression (expr : ('loc, 'loc) Ast.Expression.t) = this#expression expr + + method type_guard_annotation + (type_guard_annotation : ('loc, 'loc) Ast.Type.type_guard_annotation) = + let (loc, type_guard) = type_guard_annotation in + let type_guard' = this#type_guard type_guard in + if type_guard' = type_guard then + type_guard_annotation + else + (loc, type_guard') + + method type_guard (guard : ('loc, 'loc) Ast.Type.TypeGuard.t) = + let open Ast.Type.TypeGuard in + let (loc, { kind; guard = (x, t); comments }) = guard in + let x' = this#identifier x in + let t' = map_opt this#type_ t in + let comments' = this#syntax_opt comments in + if x' == x && t' == t && comments' == comments then + guard + else + (loc, { kind; guard = (x', t'); comments = comments' }) + + method function_rest_param (expr : ('loc, 'loc) Ast.Function.RestParam.t) = + let open Ast.Function.RestParam in + let (loc, { argument; comments }) = expr in + let argument' = this#function_param_pattern argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + expr + else + (loc, { argument = argument'; comments = comments' }) + + method return _loc (stmt : ('loc, 'loc) Ast.Statement.Return.t) = + let open Ast.Statement.Return in + let { argument; comments; return_out } = stmt in + let argument' = map_opt this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + stmt + else + { argument = argument'; comments = comments'; return_out } + + method sequence _loc (expr : ('loc, 'loc) Ast.Expression.Sequence.t) = + let open Ast.Expression.Sequence in + let { expressions; comments } = expr in + let expressions' = map_list this#expression expressions in + let comments' = this#syntax_opt comments in + if expressions == expressions' && comments == comments' then + expr + else + { expressions = expressions'; comments = comments' } + + method toplevel_statement_list (stmts : ('loc, 'loc) Ast.Statement.t list) = + this#statement_list stmts + + method statement_list (stmts : ('loc, 'loc) Ast.Statement.t list) = + map_list_multiple this#statement_fork_point stmts + + method statement_fork_point (stmt : ('loc, 'loc) Ast.Statement.t) = [this#statement stmt] + + method spread_element (expr : ('loc, 'loc) Ast.Expression.SpreadElement.t) = + let open Ast.Expression.SpreadElement in + let (loc, { argument; comments }) = expr in + let argument' = this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + expr + else + (loc, { argument = argument'; comments = comments' }) + + method spread_property (expr : ('loc, 'loc) Ast.Expression.Object.SpreadProperty.t) = + let open Ast.Expression.Object.SpreadProperty in + let (loc, { argument; comments }) = expr in + let argument' = this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + expr + else + (loc, { argument = argument'; comments = comments' }) + + method super_expression _loc (expr : 'loc Ast.Expression.Super.t) = + let open Ast.Expression.Super in + let { comments } = expr in + let comments' = this#syntax_opt comments in + if comments == comments' then + expr + else + { comments = comments' } + + method switch _loc (switch : ('loc, 'loc) Ast.Statement.Switch.t) = + let open Ast.Statement.Switch in + let { discriminant; cases; comments; exhaustive_out } = switch in + let discriminant' = this#expression discriminant in + let cases' = map_list this#switch_case cases in + let comments' = this#syntax_opt comments in + if discriminant == discriminant' && cases == cases' && comments == comments' then + switch + else + { discriminant = discriminant'; cases = cases'; comments = comments'; exhaustive_out } + + method switch_case (case : ('loc, 'loc) Ast.Statement.Switch.Case.t) = + let open Ast.Statement.Switch.Case in + let (loc, { test; consequent; comments }) = case in + let test' = map_opt this#expression test in + let consequent' = this#statement_list consequent in + let comments' = this#syntax_opt comments in + if test == test' && consequent == consequent' && comments == comments' then + case + else + (loc, { test = test'; consequent = consequent'; comments = comments' }) + + method tagged_template _loc (expr : ('loc, 'loc) Ast.Expression.TaggedTemplate.t) = + let open Ast.Expression.TaggedTemplate in + let { tag; quasi; comments } = expr in + let tag' = this#expression tag in + let quasi' = map_loc this#template_literal quasi in + let comments' = this#syntax_opt comments in + if tag == tag' && quasi == quasi' && comments == comments' then + expr + else + { tag = tag'; quasi = quasi'; comments = comments' } + + method template_literal _loc (expr : ('loc, 'loc) Ast.Expression.TemplateLiteral.t) = + let open Ast.Expression.TemplateLiteral in + let { quasis; expressions; comments } = expr in + let quasis' = map_list this#template_literal_element quasis in + let expressions' = map_list this#expression expressions in + let comments' = this#syntax_opt comments in + if quasis == quasis' && expressions == expressions' && comments == comments' then + expr + else + { quasis = quasis'; expressions = expressions'; comments = comments' } + + (* TODO *) + method template_literal_element (elem : 'loc Ast.Expression.TemplateLiteral.Element.t) = elem + + method this_expression _loc (expr : 'loc Ast.Expression.This.t) = + let open Ast.Expression.This in + let { comments } = expr in + let comments' = this#syntax_opt comments in + if comments == comments' then + expr + else + { comments = comments' } + + method throw _loc (stmt : ('loc, 'loc) Ast.Statement.Throw.t) = + let open Ast.Statement.Throw in + let { argument; comments } = stmt in + let argument' = this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + stmt + else + { argument = argument'; comments = comments' } + + method try_catch _loc (stmt : ('loc, 'loc) Ast.Statement.Try.t) = + let open Ast.Statement.Try in + let { block; handler; finalizer; comments } = stmt in + let block' = map_loc this#block block in + let handler' = + match handler with + | Some (loc, clause) -> + id_loc this#catch_clause loc clause handler (fun clause -> Some (loc, clause)) + | None -> handler + in + let finalizer' = + match finalizer with + | Some (finalizer_loc, block) -> + id_loc this#block finalizer_loc block finalizer (fun block -> Some (finalizer_loc, block)) + | None -> finalizer + in + let comments' = this#syntax_opt comments in + if block == block' && handler == handler' && finalizer == finalizer' && comments == comments' + then + stmt + else + { block = block'; handler = handler'; finalizer = finalizer'; comments = comments' } + + method type_cast _loc (expr : ('loc, 'loc) Ast.Expression.TypeCast.t) = + let open Ast.Expression.TypeCast in + let { expression; annot; comments } = expr in + let expression' = this#expression expression in + let annot' = this#type_annotation annot in + let comments' = this#syntax_opt comments in + if expression' == expression && annot' == annot && comments' == comments then + expr + else + { expression = expression'; annot = annot'; comments = comments' } + + method ts_satisfies _loc (expr : ('loc, 'loc) Ast.Expression.TSSatisfies.t) = + let open Ast.Expression.TSSatisfies in + let { expression; annot; comments } = expr in + let expression' = this#expression expression in + let annot' = this#type_annotation annot in + let comments' = this#syntax_opt comments in + if expression' == expression && annot' = annot && comments' == comments then + expr + else + { expression = expression'; annot = annot'; comments = comments' } + + method unary_expression _loc (expr : ('loc, 'loc) Flow_ast.Expression.Unary.t) = + let open Flow_ast.Expression.Unary in + let { argument; operator = _; comments } = expr in + let argument' = this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + expr + else + { expr with argument = argument'; comments = comments' } + + method update_expression _loc (expr : ('loc, 'loc) Ast.Expression.Update.t) = + let open Ast.Expression.Update in + let { argument; operator = _; prefix = _; comments } = expr in + let argument' = this#expression argument in + let comments' = this#syntax_opt comments in + if argument == argument' && comments == comments' then + expr + else + { expr with argument = argument'; comments = comments' } + + method variable_declaration _loc (decl : ('loc, 'loc) Ast.Statement.VariableDeclaration.t) = + let open Ast.Statement.VariableDeclaration in + let { declarations; kind; comments } = decl in + let decls' = map_list (this#variable_declarator ~kind) declarations in + let comments' = this#syntax_opt comments in + if declarations == decls' && comments == comments' then + decl + else + { declarations = decls'; kind; comments = comments' } + + method variable_declarator + ~kind (decl : ('loc, 'loc) Ast.Statement.VariableDeclaration.Declarator.t) = + let open Ast.Statement.VariableDeclaration.Declarator in + let (loc, { id; init }) = decl in + let id' = this#variable_declarator_pattern ~kind id in + let init' = map_opt this#expression init in + if id == id' && init == init' then + decl + else + (loc, { id = id'; init = init' }) + + method while_ _loc (stuff : ('loc, 'loc) Ast.Statement.While.t) = + let open Ast.Statement.While in + let { test; body; comments } = stuff in + let test' = this#predicate_expression test in + let body' = this#statement body in + let comments' = this#syntax_opt comments in + if test == test' && body == body' && comments == comments' then + stuff + else + { test = test'; body = body'; comments = comments' } + + method with_ _loc (stuff : ('loc, 'loc) Ast.Statement.With.t) = + let open Ast.Statement.With in + let { _object; body; comments } = stuff in + let _object' = this#expression _object in + let body' = this#statement body in + let comments' = this#syntax_opt comments in + if _object == _object' && body == body' && comments == comments' then + stuff + else + { _object = _object'; body = body'; comments = comments' } + + method type_alias _loc (stuff : ('loc, 'loc) Ast.Statement.TypeAlias.t) = + let open Ast.Statement.TypeAlias in + let { id; tparams; right; comments } = stuff in + let id' = this#binding_type_identifier id in + let tparams' = map_opt (this#type_params ~kind:TypeAliasTP) tparams in + let right' = this#type_ right in + let comments' = this#syntax_opt comments in + if id == id' && right == right' && tparams == tparams' && comments == comments' then + stuff + else + { id = id'; tparams = tparams'; right = right'; comments = comments' } + + method yield _loc (expr : ('loc, 'loc) Ast.Expression.Yield.t) = + let open Ast.Expression.Yield in + let { argument; delegate; comments; result_out } = expr in + let argument' = map_opt this#expression argument in + let comments' = this#syntax_opt comments in + if comments == comments' && argument == argument' then + expr + else + { argument = argument'; delegate; comments = comments'; result_out } + end + +let fold_program (mappers : 'a mapper list) ast = + List.fold_left (fun ast (m : 'a mapper) -> m#program ast) ast mappers diff --git a/compiler/flow_parser/parser/flow_ast_utils.ml b/compiler/flow_parser/parser/flow_ast_utils.ml new file mode 100644 index 00000000000..2c35b2050c7 --- /dev/null +++ b/compiler/flow_parser/parser/flow_ast_utils.ml @@ -0,0 +1,761 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Flow_ast +module E = Expression +module I = Identifier + +type 'loc binding = 'loc * string + +type 'loc ident = 'loc * string [@@deriving show] + +type 'loc source = 'loc * string [@@deriving show] + +let rec fold_bindings_of_pattern = + Pattern.( + let property f acc = + Object.( + function + | Property (_, { Property.pattern = p; _ }) + | RestElement (_, { RestElement.argument = p; comments = _ }) -> + fold_bindings_of_pattern f acc p + ) + in + let element f acc = + Array.( + function + | Hole _ -> acc + | Element (_, { Element.argument = p; default = _ }) + | RestElement (_, { RestElement.argument = p; comments = _ }) -> + fold_bindings_of_pattern f acc p + ) + in + fun f acc -> function + | (_, Identifier { Identifier.name; _ }) -> f acc name + | (_, Object { Object.properties; _ }) -> List.fold_left (property f) acc properties + | (_, Array { Array.elements; _ }) -> List.fold_left (element f) acc elements + (* This is for assignment and default param destructuring `[a.b=1]=c`, ignore these for now. *) + | (_, Expression _) -> acc + ) + +let fold_bindings_of_variable_declarations f acc declarations = + let open Statement.VariableDeclaration in + List.fold_left + (fun acc -> function + | (_, { Declarator.id = pattern; _ }) -> + let has_anno = + (* Only the toplevel annotation in a pattern is meaningful *) + let open Pattern in + match pattern with + | (_, Array { Array.annot = Type.Available _; _ }) + | (_, Object { Object.annot = Type.Available _; _ }) + | (_, Identifier { Identifier.annot = Type.Available _; _ }) -> + true + | _ -> false + in + fold_bindings_of_pattern (f has_anno) acc pattern) + acc + declarations + +let rec pattern_has_binding = + let open Pattern in + let property = + let open Object in + function + | Property (_, { Property.pattern = p; _ }) + | RestElement (_, { RestElement.argument = p; comments = _ }) -> + pattern_has_binding p + in + let element = + let open Array in + function + | Hole _ -> false + | Element (_, { Element.argument = p; default = _ }) + | RestElement (_, { RestElement.argument = p; comments = _ }) -> + pattern_has_binding p + in + function + | (_, Identifier _) -> true + | (_, Object { Object.properties; _ }) -> List.exists property properties + | (_, Array { Array.elements; _ }) -> List.exists element elements + | (_, Expression _) -> false + +let rec match_pattern_has_binding = + let open MatchPattern in + let property = function + | (_, ObjectPattern.Property.Valid { ObjectPattern.Property.pattern = p; _ }) -> + match_pattern_has_binding p + | (_, ObjectPattern.Property.InvalidShorthand _) -> false + in + let rest_has_binding = function + | Some (_, { RestPattern.argument = Some _; comments = _ }) -> true + | _ -> false + in + function + | (_, WildcardPattern _) + | (_, NumberPattern _) + | (_, BigIntPattern _) + | (_, StringPattern _) + | (_, BooleanPattern _) + | (_, NullPattern _) + | (_, UnaryPattern _) + | (_, IdentifierPattern _) + | (_, MemberPattern _) -> + false + | (_, BindingPattern _) -> true + | (_, ObjectPattern { ObjectPattern.properties; rest; comments = _ }) -> + rest_has_binding rest || List.exists property properties + | (_, ArrayPattern { ArrayPattern.elements; rest; comments = _ }) -> + rest_has_binding rest + || List.exists + (fun { ArrayPattern.Element.pattern; _ } -> match_pattern_has_binding pattern) + elements + | (_, OrPattern { OrPattern.patterns; _ }) -> List.exists match_pattern_has_binding patterns + | (_, AsPattern _) -> true + +let string_of_variable_kind = function + | Variable.Var -> "var" + | Variable.Let -> "let" + | Variable.Const -> "const" + +let partition_directives statements = + let open Statement in + let rec helper directives = function + | ((_, Expression { Expression.directive = Some _; _ }) as directive) :: rest -> + helper (directive :: directives) rest + | rest -> (List.rev directives, rest) + in + helper [] statements + +let hoist_function_and_component_declarations stmts = + let open Statement in + let (func_and_component_decs, other_stmts) = + List.partition + (function + (* function f() {} / component F() {} *) + | (_, (FunctionDeclaration { Function.id = Some _; _ } | ComponentDeclaration _)) + (* export function f() {} / export component F() {} *) + | ( _, + ExportNamedDeclaration + { + ExportNamedDeclaration.declaration = + Some + (_, (FunctionDeclaration { Function.id = Some _; _ } | ComponentDeclaration _)); + _; + } + ) + (* export default function f() {} / export default component F() {} *) + | ( _, + ExportDefaultDeclaration + { + ExportDefaultDeclaration.declaration = + ExportDefaultDeclaration.Declaration + (_, (FunctionDeclaration { Function.id = Some _; _ } | ComponentDeclaration _)); + _; + } + ) + (* TODO(jmbrown): Hoist declared components *) + (* declare function f(): void; *) + | (_, DeclareFunction _) + (* declare export function f(): void; *) + | ( _, + DeclareExportDeclaration DeclareExportDeclaration.{ declaration = Some (Function _); _ } + ) -> + true + | _ -> false) + stmts + in + func_and_component_decs @ other_stmts + +let negate_raw_lit raw = + let raw_len = String.length raw in + if raw_len > 0 && raw.[0] = '-' then + String.sub raw 1 (raw_len - 1) + else + "-" ^ raw + +let negate_number_literal (value, raw) = (~-.value, negate_raw_lit raw) + +let negate_bigint_literal (value, raw) = + match value with + | None -> (None, raw) + | Some value -> (Some (Int64.neg value), negate_raw_lit raw) + +let is_number_literal node = + match node with + | Expression.NumberLiteral _ + | Expression.Unary + { + Expression.Unary.operator = Expression.Unary.Minus; + argument = (_, Expression.NumberLiteral _); + comments = _; + } -> + true + | _ -> false + +let extract_number_literal node = + match node with + | Expression.NumberLiteral { NumberLiteral.value; raw; comments = _ } -> Some (value, raw) + | Expression.Unary + { + Expression.Unary.operator = Expression.Unary.Minus; + argument = (_, Expression.NumberLiteral { NumberLiteral.value; raw; _ }); + comments = _; + } -> + Some (negate_number_literal (value, raw)) + | _ -> None + +let is_bigint_literal node = + match node with + | Expression.BigIntLiteral _ + | Expression.Unary + { + Expression.Unary.operator = Expression.Unary.Minus; + argument = (_, Expression.BigIntLiteral _); + comments = _; + } -> + true + | _ -> false + +let extract_bigint_literal node = + match node with + | Expression.BigIntLiteral { BigIntLiteral.value; raw; comments = _ } -> Some (value, raw) + | Expression.Unary + { + Expression.Unary.operator = Expression.Unary.Minus; + argument = (_, Expression.BigIntLiteral { BigIntLiteral.value; raw; comments = _ }); + comments = _; + } -> + Some (negate_bigint_literal (value, raw)) + | _ -> None + +let is_call_to_invariant callee = + match callee with + | (_, Expression.Identifier (_, { Identifier.name = "invariant"; _ })) -> true + | _ -> false + +let is_call_to_require callee = + match callee with + | (_, Expression.Identifier (_, { Identifier.name = "require"; _ })) -> true + | _ -> false + +let is_call_to_is_array callee = + match callee with + | ( _, + E.Member + { + E.Member._object = (_, E.Identifier (_, { I.name = "Array"; comments = _ })); + property = E.Member.PropertyIdentifier (_, { I.name = "isArray"; comments = _ }); + comments = _; + } + ) -> + true + | _ -> false + +let is_call_to_object_dot_freeze callee = + match callee with + | ( _, + E.Member + { + E.Member._object = (_, E.Identifier (_, { I.name = "Object"; comments = _ })); + property = E.Member.PropertyIdentifier (_, { I.name = "freeze"; comments = _ }); + comments = _; + } + ) -> + true + | _ -> false + +let get_call_to_object_dot_freeze_arg callee targs args = + match (targs, args) with + | (None, (_args_loc, { E.ArgList.arguments = [E.Expression (obj_loc, E.Object o)]; comments = _ })) + when is_call_to_object_dot_freeze callee -> + Some (obj_loc, o) + | _ -> None + +let is_call_to_object_static_method callee = + match callee with + | ( _, + E.Member + { + E.Member._object = (_, E.Identifier (_, { I.name = "Object"; comments = _ })); + property = E.Member.PropertyIdentifier _; + comments = _; + } + ) -> + true + | _ -> false + +let is_module_dot_exports callee = + match callee with + | ( _, + E.Member + { + E.Member._object = (_, E.Identifier (_, { I.name = "module"; comments = _ })); + property = E.Member.PropertyIdentifier (_, { I.name = "exports"; comments = _ }); + comments = _; + } + ) -> + true + | _ -> false + +let get_call_to_jest_module_mocking_fn callee arguments = + match (callee, arguments) with + | ( ( _, + E.Member + { + E.Member._object = (_, E.Identifier (jest_loc, { I.name = "jest"; comments = _ })); + property = + E.Member.PropertyIdentifier + ( _, + (* See https://jestjs.io/docs/jest-object#mock-modules *) + { + I.name = + ( "createMockFromModule" | "mock" | "unmock" | "deepUnmock" | "doMock" + | "dontMock" | "setMock" | "requireActual" | "requireMock" ); + comments = _; + } + ); + _; + } + ), + ( _, + { + E.ArgList.arguments = + E.Expression + ( source_loc, + ( E.StringLiteral { StringLiteral.value = name; _ } + | E.TemplateLiteral + { + E.TemplateLiteral.quasis = + [ + ( _, + { + E.TemplateLiteral.Element.value = + { E.TemplateLiteral.Element.cooked = name; _ }; + _; + } + ); + ]; + _; + } ) + ) + :: _; + comments = _; + } + ) + ) -> + Some (jest_loc, source_loc, name) + | _ -> None + +let is_super_member_access = function + | { E.Member._object = (_, E.Super _); _ } -> true + | _ -> false + +let acceptable_statement_in_declaration_context ~in_declare_namespace = + let open Statement in + function + | Block _ -> Error "block" + | Break _ -> Error "break" + | ClassDeclaration _ -> Error "class declaration" + | ComponentDeclaration _ -> Error "component declaration" + | Continue _ -> Error "continue" + | Debugger _ -> Error "debugger" + | DoWhile _ -> Error "do while" + | ExportDefaultDeclaration _ -> Error "export default" + | ExportNamedDeclaration { ExportNamedDeclaration.export_kind = ExportValue; _ } -> + Error "value export" + | Expression _ -> Error "expression" + | For _ -> Error "for" + | ForIn _ -> Error "for in" + | ForOf _ -> Error "for of" + | FunctionDeclaration _ -> Error "function declaration" + | If _ -> Error "if" + | Labeled _ -> Error "labeled" + | Match _ -> Error "match" + | Return _ -> Error "return" + | Switch _ -> Error "switch" + | Throw _ -> Error "throw" + | Try _ -> Error "try" + | VariableDeclaration _ -> Error "variable declaration" + | While _ -> Error "while" + | With _ -> Error "with" + | ImportDeclaration _ -> + if in_declare_namespace then + Error "import declaration" + else + Ok () + | DeclareModuleExports _ -> + if in_declare_namespace then + Error "declare module.exports" + else + Ok () + | DeclareClass _ + | DeclareComponent _ + | DeclareEnum _ + | DeclareExportDeclaration _ + | DeclareFunction _ + | DeclareInterface _ + | DeclareModule _ + | DeclareNamespace _ + | DeclareOpaqueType _ + | DeclareTypeAlias _ + | DeclareVariable _ + | Empty _ + | EnumDeclaration _ + | ExportNamedDeclaration { ExportNamedDeclaration.export_kind = ExportType; _ } + | InterfaceDeclaration _ + | OpaqueType _ + | TypeAlias _ -> + Ok () + +let rec is_type_only_declaration_statement (_, stmt') = + let open Statement in + let is_type_only_declaration_statement' = function + | DeclareInterface _ + | DeclareOpaqueType _ + | DeclareTypeAlias _ + | Empty _ + | InterfaceDeclaration _ + | OpaqueType _ + | TypeAlias _ -> + true + | DeclareExportDeclaration + DeclareExportDeclaration. + { declaration = Some (NamedType _ | NamedOpaqueType _ | Interface _); _ } -> + true + | DeclareNamespace { DeclareNamespace.body = (_, { Block.body; _ }); _ } -> + List.for_all is_type_only_declaration_statement body + | ExportNamedDeclaration { ExportNamedDeclaration.export_kind; _ } -> export_kind = ExportType + | Block _ + | Break _ + | ClassDeclaration _ + | ComponentDeclaration _ + | Continue _ + | Debugger _ + | DoWhile _ + | EnumDeclaration _ + | ExportDefaultDeclaration _ + | Expression _ + | For _ + | ForIn _ + | ForOf _ + | FunctionDeclaration _ + | If _ + | Labeled _ + | Match _ + | Return _ + | Switch _ + | Throw _ + | Try _ + | VariableDeclaration _ + | While _ + | With _ + | ImportDeclaration _ + | DeclareClass _ + | DeclareComponent _ + | DeclareEnum _ + | DeclareExportDeclaration _ + | DeclareFunction _ + | DeclareModule _ + | DeclareModuleExports _ + | DeclareVariable _ -> + false + in + is_type_only_declaration_statement' stmt' + +let loc_of_statement = fst + +let loc_of_expression = fst + +let loc_of_pattern = fst + +let loc_of_ident = fst + +let name_of_ident (_, { Identifier.name; comments = _ }) = name + +let source_of_ident (loc, { Identifier.name; comments = _ }) = (loc, name) + +let ident_of_source ?comments (loc, name) = (loc, { Identifier.name; comments }) + +let mk_comments ?(leading = []) ?(trailing = []) a = { Syntax.leading; trailing; internal = a } + +let mk_comments_opt ?(leading = []) ?(trailing = []) () = + match (leading, trailing) with + | ([], []) -> None + | (_, _) -> Some (mk_comments ~leading ~trailing ()) + +let mk_comments_with_internal_opt ?(leading = []) ?(trailing = []) ~internal () = + match (leading, trailing, internal) with + | ([], [], []) -> None + | _ -> Some (mk_comments ~leading ~trailing internal) + +let merge_comments ~inner ~outer = + let open Syntax in + match (inner, outer) with + | (None, c) + | (c, None) -> + c + | (Some inner, Some outer) -> + mk_comments_opt + ~leading:(outer.leading @ inner.leading) + ~trailing:(inner.trailing @ outer.trailing) + () + +let merge_comments_with_internal ~inner ~outer = + match (inner, outer) with + | (inner, None) -> inner + | (None, Some { Syntax.leading; trailing; _ }) -> + mk_comments_with_internal_opt ~leading ~trailing ~internal:[] () + | ( Some { Syntax.leading = inner_leading; trailing = inner_trailing; internal }, + Some { Syntax.leading = outer_leading; trailing = outer_trailing; _ } + ) -> + mk_comments_with_internal_opt + ~leading:(outer_leading @ inner_leading) + ~trailing:(inner_trailing @ outer_trailing) + ~internal + () + +let split_comments comments = + match comments with + | None -> (None, None) + | Some { Syntax.leading; trailing; _ } -> + (mk_comments_opt ~leading (), mk_comments_opt ~trailing ()) + +let string_of_assignment_operator op = + let open E.Assignment in + match op with + | PlusAssign -> "+=" + | MinusAssign -> "-=" + | MultAssign -> "*=" + | ExpAssign -> "**=" + | DivAssign -> "/=" + | ModAssign -> "%=" + | LShiftAssign -> "<<=" + | RShiftAssign -> ">>=" + | RShift3Assign -> ">>>=" + | BitOrAssign -> "|=" + | BitXorAssign -> "^=" + | BitAndAssign -> "&=" + | NullishAssign -> "??=" + | AndAssign -> "&&=" + | OrAssign -> "||=" + +let string_of_binary_operator op = + let open E.Binary in + match op with + | Equal -> "==" + | NotEqual -> "!=" + | StrictEqual -> "===" + | StrictNotEqual -> "!==" + | LessThan -> "<" + | LessThanEqual -> "<=" + | GreaterThan -> ">" + | GreaterThanEqual -> ">=" + | LShift -> "<<" + | RShift -> ">>" + | RShift3 -> ">>>" + | Plus -> "+" + | Minus -> "-" + | Mult -> "*" + | Exp -> "**" + | Div -> "/" + | Mod -> "%" + | BitOr -> "|" + | Xor -> "^" + | BitAnd -> "&" + | In -> "in" + | Instanceof -> "instanceof" + +module ExpressionSort = struct + type t = + | Array + | ArrowFunction + | Assignment + | Binary + | Call + | Class + | Conditional + | Function + | Identifier + | Import + | JSXElement + | JSXFragment + | Literal + | Logical + | Match + | Member + | MetaProperty + | New + | Object + | OptionalCall + | OptionalMember + | Satisfies + | Sequence + | Super + | TaggedTemplate + | TemplateLiteral + | This + | TypeCast + | Unary + | Update + | Yield + [@@deriving show] + + let to_string = function + | Array -> "array" + | ArrowFunction -> "arrow function" + | Assignment -> "assignment expression" + | Binary -> "binary expression" + | Call -> "call expression" + | Class -> "class" + | Conditional -> "conditional expression" + | Function -> "function" + | Identifier -> "identifier" + | Import -> "import expression" + | JSXElement -> "JSX element" + | JSXFragment -> "JSX fragment" + | Literal -> "literal" + | Logical -> "logical expression" + | Match -> "match expression" + | Member -> "member expression" + | MetaProperty -> "metaproperty expression" + | New -> "new expression" + | Object -> "object" + | OptionalCall -> "optional call expression" + | OptionalMember -> "optional member expression" + | Satisfies -> "satisfies expression" + | Sequence -> "sequence" + | Super -> "`super` reference" + | TaggedTemplate -> "tagged template expression" + | TemplateLiteral -> "template literal" + | This -> "`this` reference" + | TypeCast -> "type cast" + | Unary -> "unary expression" + | Update -> "update expression" + | Yield -> "yield expression" +end + +let loc_of_annotation_or_hint = + let open Type in + function + | Missing loc + | Available (_, (loc, _)) -> + loc + +let loc_of_return_annot = + let open Function.ReturnAnnot in + function + | Missing loc + | Available (_, (loc, _)) + | TypeGuard (loc, _) -> + loc + +(* Apply type [t] at the toplevel of expression [exp]. This is straightforward overall + * except for the case of Identifier and Member, where we push the type within the + * identifier and member property type position as well. This is to ensure that + * type-at-pos searcher will detect the updated type. *) +let push_toplevel_type t exp = + let open E in + let push_toplevel_identifier id = + let ((id_loc, _), id) = id in + ((id_loc, t), id) + in + let push_to_member mem = + match mem with + | { Member.property = Member.PropertyIdentifier id; _ } -> + { mem with Member.property = Member.PropertyIdentifier (push_toplevel_identifier id) } + | p -> p + in + let ((loc, _), e) = exp in + let e' = + match e with + | Identifier id -> Identifier (push_toplevel_identifier id) + | Member member -> Member (push_to_member member) + | OptionalMember ({ OptionalMember.member; _ } as omem) -> + OptionalMember { omem with OptionalMember.member = push_to_member member } + | _ -> e + in + ((loc, t), e') + +let hook_name s = + let is_cap c = c = Char.uppercase_ascii c in + String.starts_with ~prefix:"use" s && (String.length s = 3 || is_cap s.[3]) + +let hook_function { Function.id; _ } = + match id with + | Some (loc, { I.name; _ }) when hook_name name -> Some loc + | _ -> None + +let hook_call { E.Call.callee; _ } = + (* A.B.C.useFoo() is a hook, A().useFoo() is not *) + let open E in + let rec hook_callee top exp = + match exp with + | (_, Identifier (_, { I.name; _ })) -> hook_name name || not top + | (_, Member { Member._object; property = Member.PropertyIdentifier (_, { I.name; _ }); _ }) -> + (hook_name name || not top) && hook_callee false _object + | _ -> false + in + hook_callee true callee + +(* Match *) +let match_root_name = "" + +let match_root_ident loc = (loc, { Identifier.name = match_root_name; comments = None }) + +let expression_of_match_member_pattern ~visit_expression pattern = + let open MatchPattern in + let rec f (loc, { MemberPattern.base; property; comments }) = + let (_object, root_name) = + match base with + | MemberPattern.BaseIdentifier ((loc, _) as id) -> ((loc, Expression.Identifier id), id) + | MemberPattern.BaseMember mem -> f mem + in + let property = + match property with + | MemberPattern.PropertyIdentifier id -> Expression.Member.PropertyIdentifier id + | MemberPattern.PropertyString (loc, lit) -> + Expression.Member.PropertyExpression (loc, Expression.StringLiteral lit) + | MemberPattern.PropertyNumber (loc, lit) -> + Expression.Member.PropertyExpression (loc, Expression.NumberLiteral lit) + | MemberPattern.PropertyBigInt (loc, lit) -> + Expression.Member.PropertyExpression (loc, Expression.BigIntLiteral lit) + in + let exp = (loc, Expression.Member { Expression.Member._object; property; comments }) in + visit_expression exp; + (exp, root_name) + in + f pattern + +(* Type Guards *) +let get_inferred_type_guard_candidate params body return = + match (body, return) with + | (Function.BodyExpression _, Function.ReturnAnnot.Missing _) -> begin + match params with + | ( _, + { + Function.Params.params = + [ + ( _, + { + Function.Param.argument = + ( _, + Pattern.Identifier + { Pattern.Identifier.name = (loc, { Identifier.name; _ }); _ } + ); + _; + } + ); + ]; + rest = None; + _; + } + ) -> + Some (loc, name) + | _ -> None + end + | _ -> None diff --git a/compiler/flow_parser/parser/flow_ast_utils.mli b/compiler/flow_parser/parser/flow_ast_utils.mli new file mode 100644 index 00000000000..531f419db71 --- /dev/null +++ b/compiler/flow_parser/parser/flow_ast_utils.mli @@ -0,0 +1,195 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type 'loc binding = 'loc * string + +type 'loc ident = 'loc * string [@@deriving show] + +type 'loc source = 'loc * string [@@deriving show] + +val fold_bindings_of_pattern : + ('a -> ('m, 't) Flow_ast.Identifier.t -> 'a) -> 'a -> ('m, 't) Flow_ast.Pattern.t -> 'a + +val fold_bindings_of_variable_declarations : + (bool -> 'a -> ('m, 't) Flow_ast.Identifier.t -> 'a) -> + 'a -> + ('m, 't) Flow_ast.Statement.VariableDeclaration.Declarator.t list -> + 'a + +val pattern_has_binding : ('m, 't) Flow_ast.Pattern.t -> bool + +val match_pattern_has_binding : ('m, 't) Flow_ast.MatchPattern.t -> bool + +val string_of_variable_kind : Flow_ast.Variable.kind -> string + +val partition_directives : + (Loc.t, Loc.t) Flow_ast.Statement.t list -> + (Loc.t, Loc.t) Flow_ast.Statement.t list * (Loc.t, Loc.t) Flow_ast.Statement.t list + +val hoist_function_and_component_declarations : + ('a, 'b) Flow_ast.Statement.t list -> ('a, 'b) Flow_ast.Statement.t list + +val is_call_to_invariant : ('a, 'b) Flow_ast.Expression.t -> bool + +val is_call_to_require : ('a, 'b) Flow_ast.Expression.t -> bool + +val is_call_to_is_array : ('a, 'b) Flow_ast.Expression.t -> bool + +val is_call_to_object_dot_freeze : ('a, 'b) Flow_ast.Expression.t -> bool + +val get_call_to_object_dot_freeze_arg : + ('a, 'b) Flow_ast.Expression.t -> + ('a, 'b) Flow_ast.Expression.CallTypeArgs.t option -> + ('a, 'b) Flow_ast.Expression.ArgList.t -> + ('b * ('a, 'b) Flow_ast.Expression.Object.t) option + +val is_call_to_object_static_method : ('a, 'b) Flow_ast.Expression.t -> bool + +val is_module_dot_exports : ('a, 'b) Flow_ast.Expression.t -> bool + +val get_call_to_jest_module_mocking_fn : + ('loc, 'annot) Flow_ast.Expression.t -> + ('loc, 'annot) Flow_ast.Expression.ArgList.t -> + ('annot * 'annot * string) option + +val is_super_member_access : ('a, 'b) Flow_ast.Expression.Member.t -> bool + +(* Returns Ok () for such statement, and Error kind_of_statement otherwise. *) +val acceptable_statement_in_declaration_context : + in_declare_namespace:bool -> ('a, 'b) Flow_ast.Statement.t' -> (unit, string) result + +val is_type_only_declaration_statement : ('a, 'b) Flow_ast.Statement.t -> bool + +val negate_number_literal : float * string -> float * string + +val negate_bigint_literal : int64 option * string -> int64 option * string + +val is_number_literal : ('a, 'b) Flow_ast.Expression.t' -> bool + +val extract_number_literal : ('a, 'b) Flow_ast.Expression.t' -> (float * string) option + +val is_bigint_literal : ('a, 'b) Flow_ast.Expression.t' -> bool + +val extract_bigint_literal : ('a, 'b) Flow_ast.Expression.t' -> (int64 option * string) option + +val loc_of_expression : ('a, 'a) Flow_ast.Expression.t -> 'a + +val loc_of_statement : ('a, 'a) Flow_ast.Statement.t -> 'a + +val loc_of_pattern : ('a, 'a) Flow_ast.Pattern.t -> 'a + +val loc_of_ident : ('a, 'a) Flow_ast.Identifier.t -> 'a + +val name_of_ident : ('loc, 'a) Flow_ast.Identifier.t -> string + +val source_of_ident : ('a, 'a) Flow_ast.Identifier.t -> 'a source + +val ident_of_source : + ?comments:('a, unit) Flow_ast.Syntax.t -> 'a source -> ('a, 'a) Flow_ast.Identifier.t + +val mk_comments : + ?leading:'loc Flow_ast.Comment.t list -> + ?trailing:'loc Flow_ast.Comment.t list -> + 'a -> + ('loc, 'a) Flow_ast.Syntax.t + +val mk_comments_opt : + ?leading:'loc Flow_ast.Comment.t list -> + ?trailing:'loc Flow_ast.Comment.t list -> + unit -> + ('loc, unit) Flow_ast.Syntax.t option + +val mk_comments_with_internal_opt : + ?leading:'loc Flow_ast.Comment.t list -> + ?trailing:'loc Flow_ast.Comment.t list -> + internal:'loc Flow_ast.Comment.t list -> + unit -> + ('loc, 'loc Flow_ast.Comment.t list) Flow_ast.Syntax.t option + +val merge_comments : + inner:('M, unit) Flow_ast.Syntax.t option -> + outer:('M, unit) Flow_ast.Syntax.t option -> + ('M, unit) Flow_ast.Syntax.t option + +val merge_comments_with_internal : + inner:('M, 'loc Flow_ast.Comment.t list) Flow_ast.Syntax.t option -> + outer:('M, 'a) Flow_ast.Syntax.t option -> + ('M, 'loc Flow_ast.Comment.t list) Flow_ast.Syntax.t option + +val split_comments : + ('loc, unit) Flow_ast.Syntax.t option -> + ('loc, unit) Flow_ast.Syntax.t option * ('loc, unit) Flow_ast.Syntax.t option + +module ExpressionSort : sig + type t = + | Array + | ArrowFunction + | Assignment + | Binary + | Call + | Class + | Conditional + | Function + | Identifier + | Import + | JSXElement + | JSXFragment + | Literal + | Logical + | Match + | Member + | MetaProperty + | New + | Object + | OptionalCall + | OptionalMember + | Satisfies + | Sequence + | Super + | TaggedTemplate + | TemplateLiteral + | This + | TypeCast + | Unary + | Update + | Yield + [@@deriving show] + + val to_string : t -> string +end + +val string_of_assignment_operator : Flow_ast.Expression.Assignment.operator -> string + +val string_of_binary_operator : Flow_ast.Expression.Binary.operator -> string + +val loc_of_annotation_or_hint : ('loc, 'loc) Flow_ast.Type.annotation_or_hint -> 'loc + +val loc_of_return_annot : ('loc, 'loc) Flow_ast.Function.ReturnAnnot.t -> 'loc + +val push_toplevel_type : + 't -> ('loc, 'loc * 't) Flow_ast.Expression.t -> ('loc, 'loc * 't) Flow_ast.Expression.t + +val hook_function : ('a, 'b) Flow_ast.Function.t -> 'b option + +val hook_call : ('a, 'b) Flow_ast.Expression.Call.t -> bool + +val hook_name : string -> bool + +val match_root_name : string + +val match_root_ident : 'loc -> ('loc, 'loc) Flow_ast.Identifier.t + +val expression_of_match_member_pattern : + visit_expression:(('loc, 'loc) Flow_ast.Expression.t -> unit) -> + ('loc, 'loc) Flow_ast.MatchPattern.MemberPattern.t -> + ('loc, 'loc) Flow_ast.Expression.t * ('loc, 'loc) Flow_ast.Identifier.t + +val get_inferred_type_guard_candidate : + ('l, 't) Flow_ast.Function.Params.t -> + ('l, 't) Flow_ast.Function.body -> + ('l, 't) Flow_ast.Function.ReturnAnnot.t -> + ('t * string) option diff --git a/compiler/flow_parser/parser/flow_lexer.ml b/compiler/flow_parser/parser/flow_lexer.ml new file mode 100644 index 00000000000..41d35e96073 --- /dev/null +++ b/compiler/flow_parser/parser/flow_lexer.ml @@ -0,0 +1,1895 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +[@@@warning "-39"] (* sedlex inserts some unnecessary `rec`s *) + +open Token +open Lex_env +module Sedlexing = Flow_sedlexing + +let lexeme = Sedlexing.Utf8.lexeme + +let lexeme_to_buffer = Sedlexing.Utf8.lexeme_to_buffer + +let lexeme_to_buffer2 = Sedlexing.Utf8.lexeme_to_buffer2 + +let sub_lexeme = Sedlexing.Utf8.sub_lexeme + +let letter = [%sedlex.regexp? 'a' .. 'z' | 'A' .. 'Z' | '$'] + +let id_letter = [%sedlex.regexp? letter | '_'] + +let digit = [%sedlex.regexp? '0' .. '9'] + +let digit_non_zero = [%sedlex.regexp? '1' .. '9'] + +let decintlit = [%sedlex.regexp? '0' | ('1' .. '9', Star digit)] + +(* DecimalIntegerLiteral *) + +let alphanumeric = [%sedlex.regexp? digit | letter] + +let word = [%sedlex.regexp? (letter, Star alphanumeric)] + +let hex_digit = [%sedlex.regexp? digit | 'a' .. 'f' | 'A' .. 'F'] + +let non_hex_letter = [%sedlex.regexp? 'g' .. 'z' | 'G' .. 'Z' | '$'] + +let bin_digit = [%sedlex.regexp? '0' | '1'] + +let oct_digit = [%sedlex.regexp? '0' .. '7'] + +(* This regex could be simplified to (digit Star (digit OR '_' digit)) + * That makes the underscore and failure cases faster, and the base case take x2-3 the steps + * As the codebase contains more base cases than underscored or errors, prefer this version *) +let underscored_bin = + [%sedlex.regexp? Plus bin_digit | (bin_digit, Star (bin_digit | ('_', bin_digit)))] + +let underscored_oct = + [%sedlex.regexp? Plus oct_digit | (oct_digit, Star (oct_digit | ('_', oct_digit)))] + +let underscored_hex = + [%sedlex.regexp? Plus hex_digit | (hex_digit, Star (hex_digit | ('_', hex_digit)))] + +let underscored_digit = [%sedlex.regexp? Plus digit | (digit_non_zero, Star (digit | ('_', digit)))] + +let underscored_decimal = [%sedlex.regexp? Plus digit | (digit, Star (digit | ('_', digit)))] + +(* Different ways you can write a number *) +let binnumber = [%sedlex.regexp? ('0', ('B' | 'b'), underscored_bin)] + +let octnumber = [%sedlex.regexp? ('0', ('O' | 'o'), underscored_oct)] + +let legacyoctnumber = [%sedlex.regexp? ('0', Plus oct_digit)] + +(* no underscores allowed *) + +let legacynonoctnumber = [%sedlex.regexp? ('0', Star oct_digit, '8' .. '9', Star digit)] + +let hexnumber = [%sedlex.regexp? ('0', ('X' | 'x'), underscored_hex)] + +let scinumber = + [%sedlex.regexp? + ( ((decintlit, Opt ('.', Opt underscored_decimal)) | ('.', underscored_decimal)), + ('e' | 'E'), + Opt ('-' | '+'), + underscored_digit + )] + +let wholenumber = [%sedlex.regexp? (underscored_digit, Opt '.')] + +let floatnumber = [%sedlex.regexp? (Opt underscored_digit, '.', underscored_decimal)] + +let binbigint = [%sedlex.regexp? (binnumber, 'n')] + +let octbigint = [%sedlex.regexp? (octnumber, 'n')] + +let hexbigint = [%sedlex.regexp? (hexnumber, 'n')] + +let scibigint = [%sedlex.regexp? (scinumber, 'n')] + +let wholebigint = [%sedlex.regexp? (underscored_digit, 'n')] + +let floatbigint = [%sedlex.regexp? ((floatnumber | (underscored_digit, '.')), 'n')] + +(* 2-8 alphanumeric characters. I could match them directly, but this leads to + * ~5k more lines of generated lexer + let htmlentity = "quot" | "amp" | "apos" | "lt" | "gt" | "nbsp" | "iexcl" + | "cent" | "pound" | "curren" | "yen" | "brvbar" | "sect" | "uml" | "copy" + | "ordf" | "laquo" | "not" | "shy" | "reg" | "macr" | "deg" | "plusmn" + | "sup2" | "sup3" | "acute" | "micro" | "para" | "middot" | "cedil" | "sup1" + | "ordm" | "raquo" | "frac14" | "frac12" | "frac34" | "iquest" | "Agrave" + | "Aacute" | "Acirc" | "Atilde" | "Auml" | "Aring" | "AElig" | "Ccedil" + | "Egrave" | "Eacute" | "Ecirc" | "Euml" | "Igrave" | "Iacute" | "Icirc" + | "Iuml" | "ETH" | "Ntilde" | "Ograve" | "Oacute" | "Ocirc" | "Otilde" + | "Ouml" | "times" | "Oslash" | "Ugrave" | "Uacute" | "Ucirc" | "Uuml" + | "Yacute" | "THORN" | "szlig" | "agrave" | "aacute" | "acirc" | "atilde" + | "auml" | "aring" | "aelig" | "ccedil" | "egrave" | "eacute" | "ecirc" + | "euml" | "igrave" | "iacute" | "icirc" | "iuml" | "eth" | "ntilde" + | "ograve" | "oacute" | "ocirc" | "otilde" | "ouml" | "divide" | "oslash" + | "ugrave" | "uacute" | "ucirc" | "uuml" | "yacute" | "thorn" | "yuml" + | "OElig" | "oelig" | "Scaron" | "scaron" | "Yuml" | "fnof" | "circ" | "tilde" + | "Alpha" | "Beta" | "Gamma" | "Delta" | "Epsilon" | "Zeta" | "Eta" | "Theta" + | "Iota" | "Kappa" | "Lambda" | "Mu" | "Nu" | "Xi" | "Omicron" | "Pi" | "Rho" + | "Sigma" | "Tau" | "Upsilon" | "Phi" | "Chi" | "Psi" | "Omega" | "alpha" + | "beta" | "gamma" | "delta" | "epsilon" | "zeta" | "eta" | "theta" | "iota" + | "kappa" | "lambda" | "mu" | "nu" | "xi" | "omicron" | "pi" | "rho" + | "sigmaf" | "sigma" | "tau" | "upsilon" | "phi" | "chi" | "psi" | "omega" + | "thetasym" | "upsih" | "piv" | "ensp" | "emsp" | "thinsp" | "zwnj" | "zwj" + | "lrm" | "rlm" | "ndash" | "mdash" | "lsquo" | "rsquo" | "sbquo" | "ldquo" + | "rdquo" | "bdquo" | "dagger" | "Dagger" | "bull" | "hellip" | "permil" + | "prime" | "Prime" | "lsaquo" | "rsaquo" | "oline" | "frasl" | "euro" + | "image" | "weierp" | "real" | "trade" | "alefsym" | "larr" | "uarr" | "rarr" + | "darr" | "harr" | "crarr" | "lArr" | "uArr" | "rArr" | "dArr" | "hArr" + | "forall" | "part" | "exist" | "empty" | "nabla" | "isin" | "notin" | "ni" + | "prod" | "sum" | "minus" | "lowast" | "radic" | "prop" | "infin" | "ang" + | "and" | "or" | "cap" | "cup" | "'int'" | "there4" | "sim" | "cong" | "asymp" + | "ne" | "equiv" | "le" | "ge" | "sub" | "sup" | "nsub" | "sube" | "supe" + | "oplus" | "otimes" | "perp" | "sdot" | "lceil" | "rceil" | "lfloor" + | "rfloor" | "lang" | "rang" | "loz" | "spades" | "clubs" | "hearts" | "diams" + *) +let htmlentity = + [%sedlex.regexp? + ( alphanumeric, + alphanumeric, + Opt alphanumeric, + Opt alphanumeric, + Opt alphanumeric, + Opt alphanumeric, + Opt alphanumeric, + Opt alphanumeric + )] + +(* https://tc39.github.io/ecma262/#sec-white-space *) +let whitespace = + [%sedlex.regexp? + ( 0x0009 | 0x000B | 0x000C | 0x0020 | 0x00A0 | 0xfeff | 0x1680 + | 0x2000 .. 0x200a + | 0x202f | 0x205f | 0x3000 )] + +let line_terminator_sequence = [%sedlex.regexp? '\n' | '\r' | "\r\n" | 0x2028 | 0x2029] + +let line_terminator_sequence_start = [%sedlex.regexp? '\n' | '\r' | 0x2028 | 0x2029] + +let hex_quad = [%sedlex.regexp? (hex_digit, hex_digit, hex_digit, hex_digit)] + +let unicode_escape = [%sedlex.regexp? ("\\u", hex_quad)] + +let codepoint_escape = [%sedlex.regexp? ("\\u{", Plus hex_digit, '}')] + +let js_id_start = [%sedlex.regexp? '$' | '_' | id_start | unicode_escape | codepoint_escape] + +let ascii_id_start = [%sedlex.regexp? '$' | '_' | 'a' .. 'z' | 'A' .. 'Z'] + +let ascii_id_continue = [%sedlex.regexp? '$' | '_' | 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9'] + +(* Assuming that the first code point is already lexed + return true means that the whole [lexbuf] is valid identifier +*) +let rec loop_id_continues lexbuf = + match%sedlex lexbuf with + | unicode_escape + | codepoint_escape + | ascii_id_continue -> + loop_id_continues lexbuf + | eof -> true + | any -> + (* TODO: Optimize later *) + let s = Sedlexing.current_code_point lexbuf in + if Js_id.is_valid_unicode_id s then + loop_id_continues lexbuf + else begin + Sedlexing.backoff lexbuf 1; + false + end + | _ -> assert false + +(* Assuming that the first code point is already lexed *) +let rec loop_jsx_id_continues lexbuf : unit = + match%sedlex lexbuf with + | '-' + | ascii_id_continue + | unicode_escape + | codepoint_escape -> + loop_jsx_id_continues lexbuf + | eof -> () + | any -> + let s = Sedlexing.current_code_point lexbuf in + if Js_id.is_valid_unicode_id s then + loop_jsx_id_continues lexbuf + else + Sedlexing.backoff lexbuf 1 + | _ -> assert false + +let pos_at_offset env offset = + { Loc.line = Lex_env.line env; column = offset - Lex_env.bol_offset env } + +let loc_of_offsets env start_offset end_offset = + { + Loc.source = Lex_env.source env; + start = pos_at_offset env start_offset; + _end = pos_at_offset env end_offset; + } + +let start_pos_of_lexbuf env (lexbuf : Sedlexing.lexbuf) = + let start_offset = Sedlexing.lexeme_start lexbuf in + pos_at_offset env start_offset + +let end_pos_of_lexbuf env (lexbuf : Sedlexing.lexbuf) = + let end_offset = Sedlexing.lexeme_end lexbuf in + pos_at_offset env end_offset + +let loc_of_lexbuf env (lexbuf : Sedlexing.lexbuf) = + let start_offset = Sedlexing.lexeme_start lexbuf in + let end_offset = Sedlexing.lexeme_end lexbuf in + loc_of_offsets env start_offset end_offset + +let loc_of_token env lex_token = + match lex_token with + | T_IDENTIFIER { loc; _ } + | T_JSX_IDENTIFIER { loc; _ } + | T_STRING (loc, _, _, _) -> + loc + | T_JSX_CHILD_TEXT (loc, _, _) -> loc + | T_JSX_QUOTE_TEXT (loc, _, _) -> loc + | T_TEMPLATE_PART (loc, _, _, _, _) -> loc + | T_REGEXP (loc, _, _) -> loc + | _ -> loc_of_lexbuf env env.lex_lb + +let lex_error (env : Lex_env.t) loc err : Lex_env.t = + let lex_errors_acc = (loc, err) :: env.lex_state.lex_errors_acc in + { env with lex_state = { lex_errors_acc } } + +let unexpected_error (env : Lex_env.t) (loc : Loc.t) value = + lex_error env loc (Parse_error.Unexpected (quote_token_value value)) + +let unexpected_error_w_suggest (env : Lex_env.t) (loc : Loc.t) value suggest = + lex_error env loc (Parse_error.UnexpectedTokenWithSuggestion (value, suggest)) + +let illegal (env : Lex_env.t) (loc : Loc.t) = + lex_error env loc (Parse_error.Unexpected "token ILLEGAL") + +let new_line env lexbuf = + let offset = Sedlexing.lexeme_end lexbuf in + let lex_bol = { line = Lex_env.line env + 1; offset } in + { env with Lex_env.lex_bol } + +let bigint_strip_n raw = + let size = String.length raw in + let str = + if size != 0 && raw.[size - 1] == 'n' then + String.sub raw 0 (size - 1) + else + raw + in + str + +let mk_comment + (env : Lex_env.t) + (start : Loc.position) + (_end : Loc.position) + (buf : Buffer.t) + (multiline : bool) : Loc.t Flow_ast.Comment.t = + let open Flow_ast.Comment in + let loc = { Loc.source = Lex_env.source env; start; _end } in + let text = Buffer.contents buf in + let kind = + if multiline then + Block + else + Line + in + let on_newline = Loc.(env.lex_last_loc._end.Loc.line < loc.start.Loc.line) in + let c = { kind; text; on_newline } in + (loc, c) + +let mk_num_singleton number_type (lexeme : int array) = + let raw = Sedlexing.string_of_utf8 lexeme in + (* convert singleton number type into a float *) + let value = + match number_type with + | LEGACY_OCTAL -> begin + try Int64.to_float (Int64.of_string ("0o" ^ raw)) with + | Failure _ -> failwith ("Invalid legacy octal " ^ raw) + end + | BINARY + | OCTAL -> begin + try Int64.to_float (Int64.of_string raw) with + | Failure _ -> failwith ("Invalid binary/octal " ^ raw) + end + | LEGACY_NON_OCTAL + | NORMAL -> begin + try float_of_string raw with + | Failure _ -> failwith ("Invalid number " ^ raw) + end + in + T_NUMBER_SINGLETON_TYPE { kind = number_type; value; raw } + +let mk_bignum_singleton kind lexeme = + let raw = Sedlexing.string_of_utf8 lexeme in + let postraw = bigint_strip_n raw in + let value = Int64.of_string_opt postraw in + T_BIGINT_SINGLETON_TYPE { kind; value; raw } + +(* This is valid since the escapes are already tackled*) +let assert_valid_unicode_in_identifier env loc code = + if Js_id.is_valid_unicode_id code then + env + else + lex_error env loc Parse_error.IllegalUnicodeEscape + +let decode_identifier = + let loc_and_sub_lexeme env offset lexbuf trim_start trim_end = + let start_offset = offset + Sedlexing.lexeme_start lexbuf in + let end_offset = offset + Sedlexing.lexeme_end lexbuf in + let loc = loc_of_offsets env start_offset end_offset in + (loc, sub_lexeme lexbuf trim_start (Sedlexing.lexeme_length lexbuf - trim_start - trim_end)) + in + let rec id_char env offset buf lexbuf = + match%sedlex lexbuf with + | unicode_escape -> + let (loc, hex) = loc_and_sub_lexeme env offset lexbuf 2 0 in + let code = int_of_string ("0x" ^ hex) in + let env = + if not (Uchar.is_valid code) then + lex_error env loc Parse_error.IllegalUnicodeEscape + else + assert_valid_unicode_in_identifier env loc code + in + Wtf8.add_wtf_8 buf code; + id_char env offset buf lexbuf + | codepoint_escape -> + let (loc, hex) = loc_and_sub_lexeme env offset lexbuf 3 1 in + let code = int_of_string ("0x" ^ hex) in + let env = assert_valid_unicode_in_identifier env loc code in + Wtf8.add_wtf_8 buf code; + id_char env offset buf lexbuf + | eof -> (env, Buffer.contents buf) + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl (eof | "\\")) + | any -> + lexeme_to_buffer lexbuf buf; + id_char env offset buf lexbuf + | _ -> failwith "unreachable id_char" + in + fun env raw -> + let offset = Sedlexing.lexeme_start env.lex_lb in + let lexbuf = Sedlexing.from_int_array raw in + let buf = Buffer.create (Array.length raw) in + id_char env offset buf lexbuf + +let recover env lexbuf ~f = + let env = illegal env (loc_of_lexbuf env lexbuf) in + Sedlexing.rollback lexbuf; + f env lexbuf + +type result = + | Token of Lex_env.t * Token.t + | Comment of Lex_env.t * Loc.t Flow_ast.Comment.t + | Continue of Lex_env.t + +let rec comment env buf lexbuf = + match%sedlex lexbuf with + | line_terminator_sequence -> + let env = new_line env lexbuf in + lexeme_to_buffer lexbuf buf; + comment env buf lexbuf + | "*/" -> + let env = + if is_in_comment_syntax env then + let loc = loc_of_lexbuf env lexbuf in + unexpected_error_w_suggest env loc "*/" "*-/" + else + env + in + (env, end_pos_of_lexbuf env lexbuf) + | "*-/" -> + if is_in_comment_syntax env then + (env, end_pos_of_lexbuf env lexbuf) + else ( + Buffer.add_string buf "*-/"; + comment env buf lexbuf + ) + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl (line_terminator_sequence_start | '*')) + | any -> + lexeme_to_buffer lexbuf buf; + comment env buf lexbuf + | _ -> + let env = illegal env (loc_of_lexbuf env lexbuf) in + (env, end_pos_of_lexbuf env lexbuf) + +let rec line_comment env buf lexbuf = + match%sedlex lexbuf with + | eof -> (env, end_pos_of_lexbuf env lexbuf) + | line_terminator_sequence -> + let { Loc.line; column } = end_pos_of_lexbuf env lexbuf in + let env = new_line env lexbuf in + let len = Sedlexing.lexeme_length lexbuf in + let end_pos = { Loc.line; column = column - len } in + (env, end_pos) + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl (eof | line_terminator_sequence_start)) + | any -> + lexeme_to_buffer lexbuf buf; + line_comment env buf lexbuf + | _ -> failwith "unreachable line_comment" + +let string_escape env lexbuf = + match%sedlex lexbuf with + | eof + | '\\' -> + let str = lexeme lexbuf in + let codes = Sedlexing.lexeme lexbuf in + (env, str, codes, false) + | ('x', hex_digit, hex_digit) -> + let str = lexeme lexbuf in + let code = int_of_string ("0" ^ str) in + (* 0xAB *) + (env, str, [| code |], false) + | ('0' .. '7', '0' .. '7', '0' .. '7') -> + let str = lexeme lexbuf in + let code = int_of_string ("0o" ^ str) in + (* 0o012 *) + (* If the 3 character octal code is larger than 256 + * then it is parsed as a 2 character octal code *) + if code < 256 then + (env, str, [| code |], true) + else + let remainder = code land 7 in + let code = code lsr 3 in + (env, str, [| code; Char.code '0' + remainder |], true) + | ('0' .. '7', '0' .. '7') -> + let str = lexeme lexbuf in + let code = int_of_string ("0o" ^ str) in + (* 0o01 *) + (env, str, [| code |], true) + | '0' -> (env, "0", [| 0x0 |], false) + | 'b' -> (env, "b", [| 0x8 |], false) + | 'f' -> (env, "f", [| 0xC |], false) + | 'n' -> (env, "n", [| 0xA |], false) + | 'r' -> (env, "r", [| 0xD |], false) + | 't' -> (env, "t", [| 0x9 |], false) + | 'v' -> (env, "v", [| 0xB |], false) + | '0' .. '7' -> + let str = lexeme lexbuf in + let code = int_of_string ("0o" ^ str) in + (* 0o1 *) + (env, str, [| code |], true) + | ('u', hex_quad) -> + let str = lexeme lexbuf in + let hex = String.sub str 1 (String.length str - 1) in + let code = int_of_string ("0x" ^ hex) in + (env, str, [| code |], false) + | ("u{", Plus hex_digit, '}') -> + let str = lexeme lexbuf in + let hex = String.sub str 2 (String.length str - 3) in + let code = int_of_string ("0x" ^ hex) in + (* 11.8.4.1 *) + let env = + if code > 0x10FFFF then + illegal env (loc_of_lexbuf env lexbuf) + else + env + in + (env, str, [| code |], false) + | 'u' + | 'x' + | '0' .. '7' -> + let str = lexeme lexbuf in + let codes = Sedlexing.lexeme lexbuf in + let env = illegal env (loc_of_lexbuf env lexbuf) in + (env, str, codes, false) + | line_terminator_sequence -> + let str = lexeme lexbuf in + let env = new_line env lexbuf in + (env, str, [||], false) + | any -> + let str = lexeme lexbuf in + let codes = Sedlexing.lexeme lexbuf in + (env, str, codes, false) + | _ -> failwith "unreachable string_escape" + +(* Really simple version of string lexing. Just try to find beginning and end of + * string. We can inspect the string later to find invalid escapes, etc *) +let rec string_quote env q buf raw octal lexbuf = + match%sedlex lexbuf with + | "'" + | '"' -> + let q' = lexeme lexbuf in + Buffer.add_string raw q'; + if q = q' then + (env, end_pos_of_lexbuf env lexbuf, octal) + else ( + Buffer.add_string buf q'; + string_quote env q buf raw octal lexbuf + ) + | '\\' -> + Buffer.add_string raw "\\"; + let (env, str, codes, octal') = string_escape env lexbuf in + let octal = octal' || octal in + Buffer.add_string raw str; + Array.iter (Wtf8.add_wtf_8 buf) codes; + string_quote env q buf raw octal lexbuf + | '\n' -> + let x = lexeme lexbuf in + Buffer.add_string raw x; + let env = illegal env (loc_of_lexbuf env lexbuf) in + let env = new_line env lexbuf in + Buffer.add_string buf x; + (env, end_pos_of_lexbuf env lexbuf, octal) + | eof -> + let x = lexeme lexbuf in + Buffer.add_string raw x; + let env = illegal env (loc_of_lexbuf env lexbuf) in + Buffer.add_string buf x; + (env, end_pos_of_lexbuf env lexbuf, octal) + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl ("'" | '"' | '\\' | '\n' | eof)) + | any -> + lexeme_to_buffer2 lexbuf raw buf; + string_quote env q buf raw octal lexbuf + | _ -> failwith "unreachable string_quote" + +let rec template_part env cooked raw lexbuf = + match%sedlex lexbuf with + | eof -> + let env = illegal env (loc_of_lexbuf env lexbuf) in + (env, true) + | '`' -> (env, true) + | "${" -> (env, false) + | '\\' -> + Buffer.add_char raw '\\'; + let (env, str, codes, _) = string_escape env lexbuf in + Buffer.add_string raw str; + Array.iter (Wtf8.add_wtf_8 cooked) codes; + template_part env cooked raw lexbuf + (* ECMAScript 6th Syntax, 11.8.6.1 Static Semantics: TV's and TRV's + * Long story short, is 0xA, is 0xA, and is 0xA + * *) + | "\r\n" -> + Buffer.add_string raw "\r\n"; + Buffer.add_string cooked "\n"; + let env = new_line env lexbuf in + template_part env cooked raw lexbuf + | "\n" + | "\r" -> + let lf = lexeme lexbuf in + Buffer.add_string raw lf; + Buffer.add_char cooked '\n'; + let env = new_line env lexbuf in + template_part env cooked raw lexbuf + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl (eof | '`' | '$' | '\\' | '\r' | '\n')) + | any -> + let c = lexeme lexbuf in + Buffer.add_string raw c; + Buffer.add_string cooked c; + template_part env cooked raw lexbuf + | _ -> failwith "unreachable template_part" + +let token (env : Lex_env.t) lexbuf : result = + match%sedlex lexbuf with + | line_terminator_sequence -> + let env = new_line env lexbuf in + Continue env + | Plus whitespace -> Continue env + | "/*" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf true) + | ("/*", Star whitespace, (":" | "::" | "flow-include")) -> + let pattern = lexeme lexbuf in + if not (is_comment_syntax_enabled env) then ( + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + Buffer.add_string buf (String.sub pattern 2 (String.length pattern - 2)); + let (env, end_pos) = comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf true) + ) else + let env = + if is_in_comment_syntax env then + let loc = loc_of_lexbuf env lexbuf in + unexpected_error env loc pattern + else + env + in + let env = in_comment_syntax true env in + let len = Sedlexing.lexeme_length lexbuf in + if + Sedlexing.Utf8.sub_lexeme lexbuf (len - 1) 1 = ":" + && Sedlexing.Utf8.sub_lexeme lexbuf (len - 2) 1 <> ":" + then + Token (env, T_COLON) + else + Continue env + | "*/" -> + if is_in_comment_syntax env then + let env = in_comment_syntax false env in + Continue env + else ( + Sedlexing.rollback lexbuf; + match%sedlex lexbuf with + | "*" -> Token (env, T_MULT) + | _ -> failwith "expected *" + ) + | "//" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = line_comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf false) + (* Support for the shebang at the beginning of a file. It is treated like a + * comment at the beginning or an error elsewhere *) + | "#!" -> + if Sedlexing.lexeme_start lexbuf = 0 then + let start = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, _end) = line_comment env buf lexbuf in + let loc = { Loc.source = Lex_env.source env; start; _end } in + Token (env, T_INTERPRETER (loc, Buffer.contents buf)) + else + Token (env, T_ERROR "#!") + (* Values *) + | "'" + | '"' -> + let quote = lexeme lexbuf in + let start = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let raw = Buffer.create 127 in + Buffer.add_string raw quote; + let octal = false in + let (env, _end, octal) = string_quote env quote buf raw octal lexbuf in + let loc = { Loc.source = Lex_env.source env; start; _end } in + Token (env, T_STRING (loc, Buffer.contents buf, Buffer.contents raw, octal)) + | '`' -> + let value = Buffer.create 127 in + let raw = Buffer.create 127 in + let start = start_pos_of_lexbuf env lexbuf in + let (env, is_tail) = template_part env value raw lexbuf in + let _end = end_pos_of_lexbuf env lexbuf in + let loc = { Loc.source = Lex_env.source env; start; _end } in + Token (env, T_TEMPLATE_PART (loc, Buffer.contents value, Buffer.contents raw, true, is_tail)) + | (binbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | binbigint -> Token (env, T_BIGINT { kind = BIG_BINARY; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token bigint" + ) + | binbigint -> Token (env, T_BIGINT { kind = BIG_BINARY; raw = lexeme lexbuf }) + | (binnumber, (letter | '2' .. '9'), Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | binnumber -> Token (env, T_NUMBER { kind = BINARY; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token bignumber" + ) + | binnumber -> Token (env, T_NUMBER { kind = BINARY; raw = lexeme lexbuf }) + | (octbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | octbigint -> Token (env, T_BIGINT { kind = BIG_OCTAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token octbigint" + ) + | octbigint -> Token (env, T_BIGINT { kind = BIG_OCTAL; raw = lexeme lexbuf }) + | (octnumber, (letter | '8' .. '9'), Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | octnumber -> Token (env, T_NUMBER { kind = OCTAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token octnumber" + ) + | octnumber -> Token (env, T_NUMBER { kind = OCTAL; raw = lexeme lexbuf }) + | (legacynonoctnumber, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | legacynonoctnumber -> + Token (env, T_NUMBER { kind = LEGACY_NON_OCTAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token legacynonoctnumber" + ) + | legacynonoctnumber -> Token (env, T_NUMBER { kind = LEGACY_NON_OCTAL; raw = lexeme lexbuf }) + | (legacyoctnumber, (letter | '8' .. '9'), Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | legacyoctnumber -> Token (env, T_NUMBER { kind = LEGACY_OCTAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token legacyoctnumber" + ) + | legacyoctnumber -> Token (env, T_NUMBER { kind = LEGACY_OCTAL; raw = lexeme lexbuf }) + | (hexbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | hexbigint -> Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token hexbigint" + ) + | hexbigint -> Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | (hexnumber, non_hex_letter, Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | hexnumber -> Token (env, T_NUMBER { kind = NORMAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token hexnumber" + ) + | hexnumber -> Token (env, T_NUMBER { kind = NORMAL; raw = lexeme lexbuf }) + | (scibigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | scibigint -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidSciBigInt in + Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token scibigint" + ) + | scibigint -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidSciBigInt in + Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | (scinumber, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | scinumber -> Token (env, T_NUMBER { kind = NORMAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token scinumber" + ) + | scinumber -> Token (env, T_NUMBER { kind = NORMAL; raw = lexeme lexbuf }) + | (floatbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | floatbigint -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidFloatBigInt in + Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token floatbigint" + ) + | (wholebigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | wholebigint -> Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token wholebigint" + ) + | floatbigint -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidFloatBigInt in + Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | wholebigint -> Token (env, T_BIGINT { kind = BIG_NORMAL; raw = lexeme lexbuf }) + | ((wholenumber | floatnumber), word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | wholenumber + | floatnumber -> + Token (env, T_NUMBER { kind = NORMAL; raw = lexeme lexbuf }) + | _ -> failwith "unreachable token wholenumber" + ) + | wholenumber + | floatnumber -> + Token (env, T_NUMBER { kind = NORMAL; raw = lexeme lexbuf }) + (* TODO: Use [Symbol.iterator] instead of @@iterator. *) + (* `@` is not a valid unicode name *) + | "@@iterator" + | "@@asyncIterator" -> + let loc = loc_of_lexbuf env lexbuf in + let raw = lexeme lexbuf in + Token (env, T_IDENTIFIER { loc; value = raw; raw }) + (* Syntax *) + | "{" -> Token (env, T_LCURLY) + | "}" -> Token (env, T_RCURLY) + | "(" -> Token (env, T_LPAREN) + | ")" -> Token (env, T_RPAREN) + | "[" -> Token (env, T_LBRACKET) + | "]" -> Token (env, T_RBRACKET) + | "..." -> Token (env, T_ELLIPSIS) + | "." -> Token (env, T_PERIOD) + | ";" -> Token (env, T_SEMICOLON) + | "," -> Token (env, T_COMMA) + | ":" -> Token (env, T_COLON) + | ("?.", digit) -> + Sedlexing.rollback lexbuf; + (match%sedlex lexbuf with + | "?" -> Token (env, T_PLING) + | _ -> failwith "expected ?") + | "?." -> Token (env, T_PLING_PERIOD) + | "??" -> Token (env, T_PLING_PLING) + | "?" -> Token (env, T_PLING) + | "&&" -> Token (env, T_AND) + | "||" -> Token (env, T_OR) + | "===" -> Token (env, T_STRICT_EQUAL) + | "!==" -> Token (env, T_STRICT_NOT_EQUAL) + | "<=" -> Token (env, T_LESS_THAN_EQUAL) + | ">=" -> Token (env, T_GREATER_THAN_EQUAL) + | "==" -> Token (env, T_EQUAL) + | "!=" -> Token (env, T_NOT_EQUAL) + | "++" -> Token (env, T_INCR) + | "--" -> Token (env, T_DECR) + | "<<=" -> Token (env, T_LSHIFT_ASSIGN) + | "<<" -> Token (env, T_LSHIFT) + | ">>=" -> Token (env, T_RSHIFT_ASSIGN) + | ">>>=" -> Token (env, T_RSHIFT3_ASSIGN) + | ">>>" -> Token (env, T_RSHIFT3) + | ">>" -> Token (env, T_RSHIFT) + | "+=" -> Token (env, T_PLUS_ASSIGN) + | "-=" -> Token (env, T_MINUS_ASSIGN) + | "*=" -> Token (env, T_MULT_ASSIGN) + | "**=" -> Token (env, T_EXP_ASSIGN) + | "%=" -> Token (env, T_MOD_ASSIGN) + | "&=" -> Token (env, T_BIT_AND_ASSIGN) + | "|=" -> Token (env, T_BIT_OR_ASSIGN) + | "^=" -> Token (env, T_BIT_XOR_ASSIGN) + | "??=" -> Token (env, T_NULLISH_ASSIGN) + | "&&=" -> Token (env, T_AND_ASSIGN) + | "||=" -> Token (env, T_OR_ASSIGN) + | "<" -> Token (env, T_LESS_THAN) + | ">" -> Token (env, T_GREATER_THAN) + | "+" -> Token (env, T_PLUS) + | "-" -> Token (env, T_MINUS) + | "*" -> Token (env, T_MULT) + | "**" -> Token (env, T_EXP) + | "%" -> Token (env, T_MOD) + | "|" -> Token (env, T_BIT_OR) + | "&" -> Token (env, T_BIT_AND) + | "^" -> Token (env, T_BIT_XOR) + | "!" -> Token (env, T_NOT) + | "~" -> Token (env, T_BIT_NOT) + | "=" -> Token (env, T_ASSIGN) + | "=>" -> Token (env, T_ARROW) + | "/=" -> Token (env, T_DIV_ASSIGN) + | "/" -> Token (env, T_DIV) + | "@" -> Token (env, T_AT) + | "#" -> Token (env, T_POUND) + (* To reason about its correctness: + 1. all tokens are still matched + 2. tokens like opaque, opaquex are matched correctly + the most fragile case is `opaquex` (matched with `opaque,x` instead) + 3. \a is disallowed + 4. a世界 recognized + *) + | '\\' -> + let env = illegal env (loc_of_lexbuf env lexbuf) in + Continue env + | js_id_start -> + let start_offset = Sedlexing.lexeme_start lexbuf in + loop_id_continues lexbuf |> ignore; + let end_offset = Sedlexing.lexeme_end lexbuf in + let loc = loc_of_offsets env start_offset end_offset in + Sedlexing.set_lexeme_start lexbuf start_offset; + (match lexeme lexbuf with + | "async" -> Token (env, T_ASYNC) + | "await" -> Token (env, T_AWAIT) + | "break" -> Token (env, T_BREAK) + | "case" -> Token (env, T_CASE) + | "catch" -> Token (env, T_CATCH) + | "class" -> Token (env, T_CLASS) + | "const" -> Token (env, T_CONST) + | "continue" -> Token (env, T_CONTINUE) + | "debugger" -> Token (env, T_DEBUGGER) + | "declare" -> Token (env, T_DECLARE) + | "default" -> Token (env, T_DEFAULT) + | "delete" -> Token (env, T_DELETE) + | "do" -> Token (env, T_DO) + | "else" -> Token (env, T_ELSE) + | "enum" -> Token (env, T_ENUM) + | "export" -> Token (env, T_EXPORT) + | "extends" -> Token (env, T_EXTENDS) + | "false" -> Token (env, T_FALSE) + | "finally" -> Token (env, T_FINALLY) + | "for" -> Token (env, T_FOR) + | "function" -> Token (env, T_FUNCTION) + | "if" -> Token (env, T_IF) + | "implements" -> Token (env, T_IMPLEMENTS) + | "import" -> Token (env, T_IMPORT) + | "in" -> Token (env, T_IN) + | "instanceof" -> Token (env, T_INSTANCEOF) + | "interface" -> Token (env, T_INTERFACE) + | "let" -> Token (env, T_LET) + | "match" -> Token (env, T_MATCH) + | "new" -> Token (env, T_NEW) + | "null" -> Token (env, T_NULL) + | "of" -> Token (env, T_OF) + | "opaque" -> Token (env, T_OPAQUE) + | "package" -> Token (env, T_PACKAGE) + | "private" -> Token (env, T_PRIVATE) + | "protected" -> Token (env, T_PROTECTED) + | "public" -> Token (env, T_PUBLIC) + | "return" -> Token (env, T_RETURN) + | "static" -> Token (env, T_STATIC) + | "super" -> Token (env, T_SUPER) + | "switch" -> Token (env, T_SWITCH) + | "this" -> Token (env, T_THIS) + | "throw" -> Token (env, T_THROW) + | "true" -> Token (env, T_TRUE) + | "try" -> Token (env, T_TRY) + | "type" -> Token (env, T_TYPE) + | "typeof" -> Token (env, T_TYPEOF) + | "var" -> Token (env, T_VAR) + | "void" -> Token (env, T_VOID) + | "while" -> Token (env, T_WHILE) + | "with" -> Token (env, T_WITH) + | "yield" -> Token (env, T_YIELD) + | _ -> + let raw = Sedlexing.lexeme lexbuf in + let (nenv, value) = decode_identifier env raw in + Token (nenv, T_IDENTIFIER { loc; value; raw = Sedlexing.string_of_utf8 raw })) + | eof -> + let env = + if is_in_comment_syntax env then + let loc = loc_of_lexbuf env lexbuf in + lex_error env loc Parse_error.UnexpectedEOS + else + env + in + Token (env, T_EOF) + | any -> + let env = illegal env (loc_of_lexbuf env lexbuf) in + Token (env, T_ERROR (lexeme lexbuf)) + | _ -> failwith "unreachable token" + +let rec regexp_class env buf lexbuf = + match%sedlex lexbuf with + | eof -> env + | "\\\\" -> + Buffer.add_string buf "\\\\"; + regexp_class env buf lexbuf + | ('\\', ']') -> + Buffer.add_char buf '\\'; + Buffer.add_char buf ']'; + regexp_class env buf lexbuf + | ']' -> + Buffer.add_char buf ']'; + env + | line_terminator_sequence -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.UnterminatedRegExp in + let env = new_line env lexbuf in + env + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl (eof | '\\' | ']' | line_terminator_sequence_start)) + | any -> + let str = lexeme lexbuf in + Buffer.add_string buf str; + regexp_class env buf lexbuf + | _ -> failwith "unreachable regexp_class" + +let rec regexp_body env buf lexbuf = + match%sedlex lexbuf with + | eof -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.UnterminatedRegExp in + (env, "") + | ('\\', line_terminator_sequence) -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.UnterminatedRegExp in + let env = new_line env lexbuf in + (env, "") + | ('\\', any) -> + let s = lexeme lexbuf in + Buffer.add_string buf s; + regexp_body env buf lexbuf + | ('/', Plus id_letter) -> + let flags = + let str = lexeme lexbuf in + String.sub str 1 (String.length str - 1) + in + (env, flags) + | '/' -> (env, "") + | '[' -> + Buffer.add_char buf '['; + let env = regexp_class env buf lexbuf in + regexp_body env buf lexbuf + | line_terminator_sequence -> + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.UnterminatedRegExp in + let env = new_line env lexbuf in + (env, "") + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl (eof | '\\' | '/' | '[' | line_terminator_sequence_start)) + | any -> + let str = lexeme lexbuf in + Buffer.add_string buf str; + regexp_body env buf lexbuf + | _ -> failwith "unreachable regexp_body" + +let regexp env lexbuf = + match%sedlex lexbuf with + | eof -> Token (env, T_EOF) + | line_terminator_sequence -> + let env = new_line env lexbuf in + Continue env + | Plus whitespace -> Continue env + | "//" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = line_comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf false) + | "/*" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf true) + | '/' -> + let start = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, flags) = regexp_body env buf lexbuf in + let _end = end_pos_of_lexbuf env lexbuf in + let loc = { Loc.source = Lex_env.source env; start; _end } in + Token (env, T_REGEXP (loc, Buffer.contents buf, flags)) + | any -> + let env = illegal env (loc_of_lexbuf env lexbuf) in + Token (env, T_ERROR (lexeme lexbuf)) + | _ -> failwith "unreachable regexp" + +let decode_html_entity = function + | "quot" -> Some 0x0022 + | "amp" -> Some 0x0026 + | "apos" -> Some 0x0027 + | "lt" -> Some 0x003C + | "gt" -> Some 0x003E + | "nbsp" -> Some 0x00A0 + | "iexcl" -> Some 0x00A1 + | "cent" -> Some 0x00A2 + | "pound" -> Some 0x00A3 + | "curren" -> Some 0x00A4 + | "yen" -> Some 0x00A5 + | "brvbar" -> Some 0x00A6 + | "sect" -> Some 0x00A7 + | "uml" -> Some 0x00A8 + | "copy" -> Some 0x00A9 + | "ordf" -> Some 0x00AA + | "laquo" -> Some 0x00AB + | "not" -> Some 0x00AC + | "shy" -> Some 0x00AD + | "reg" -> Some 0x00AE + | "macr" -> Some 0x00AF + | "deg" -> Some 0x00B0 + | "plusmn" -> Some 0x00B1 + | "sup2" -> Some 0x00B2 + | "sup3" -> Some 0x00B3 + | "acute" -> Some 0x00B4 + | "micro" -> Some 0x00B5 + | "para" -> Some 0x00B6 + | "middot" -> Some 0x00B7 + | "cedil" -> Some 0x00B8 + | "sup1" -> Some 0x00B9 + | "ordm" -> Some 0x00BA + | "raquo" -> Some 0x00BB + | "frac14" -> Some 0x00BC + | "frac12" -> Some 0x00BD + | "frac34" -> Some 0x00BE + | "iquest" -> Some 0x00BF + | "Agrave" -> Some 0x00C0 + | "Aacute" -> Some 0x00C1 + | "Acirc" -> Some 0x00C2 + | "Atilde" -> Some 0x00C3 + | "Auml" -> Some 0x00C4 + | "Aring" -> Some 0x00C5 + | "AElig" -> Some 0x00C6 + | "Ccedil" -> Some 0x00C7 + | "Egrave" -> Some 0x00C8 + | "Eacute" -> Some 0x00C9 + | "Ecirc" -> Some 0x00CA + | "Euml" -> Some 0x00CB + | "Igrave" -> Some 0x00CC + | "Iacute" -> Some 0x00CD + | "Icirc" -> Some 0x00CE + | "Iuml" -> Some 0x00CF + | "ETH" -> Some 0x00D0 + | "Ntilde" -> Some 0x00D1 + | "Ograve" -> Some 0x00D2 + | "Oacute" -> Some 0x00D3 + | "Ocirc" -> Some 0x00D4 + | "Otilde" -> Some 0x00D5 + | "Ouml" -> Some 0x00D6 + | "times" -> Some 0x00D7 + | "Oslash" -> Some 0x00D8 + | "Ugrave" -> Some 0x00D9 + | "Uacute" -> Some 0x00DA + | "Ucirc" -> Some 0x00DB + | "Uuml" -> Some 0x00DC + | "Yacute" -> Some 0x00DD + | "THORN" -> Some 0x00DE + | "szlig" -> Some 0x00DF + | "agrave" -> Some 0x00E0 + | "aacute" -> Some 0x00E1 + | "acirc" -> Some 0x00E2 + | "atilde" -> Some 0x00E3 + | "auml" -> Some 0x00E4 + | "aring" -> Some 0x00E5 + | "aelig" -> Some 0x00E6 + | "ccedil" -> Some 0x00E7 + | "egrave" -> Some 0x00E8 + | "eacute" -> Some 0x00E9 + | "ecirc" -> Some 0x00EA + | "euml" -> Some 0x00EB + | "igrave" -> Some 0x00EC + | "iacute" -> Some 0x00ED + | "icirc" -> Some 0x00EE + | "iuml" -> Some 0x00EF + | "eth" -> Some 0x00F0 + | "ntilde" -> Some 0x00F1 + | "ograve" -> Some 0x00F2 + | "oacute" -> Some 0x00F3 + | "ocirc" -> Some 0x00F4 + | "otilde" -> Some 0x00F5 + | "ouml" -> Some 0x00F6 + | "divide" -> Some 0x00F7 + | "oslash" -> Some 0x00F8 + | "ugrave" -> Some 0x00F9 + | "uacute" -> Some 0x00FA + | "ucirc" -> Some 0x00FB + | "uuml" -> Some 0x00FC + | "yacute" -> Some 0x00FD + | "thorn" -> Some 0x00FE + | "yuml" -> Some 0x00FF + | "OElig" -> Some 0x0152 + | "oelig" -> Some 0x0153 + | "Scaron" -> Some 0x0160 + | "scaron" -> Some 0x0161 + | "Yuml" -> Some 0x0178 + | "fnof" -> Some 0x0192 + | "circ" -> Some 0x02C6 + | "tilde" -> Some 0x02DC + | "Alpha" -> Some 0x0391 + | "Beta" -> Some 0x0392 + | "Gamma" -> Some 0x0393 + | "Delta" -> Some 0x0394 + | "Epsilon" -> Some 0x0395 + | "Zeta" -> Some 0x0396 + | "Eta" -> Some 0x0397 + | "Theta" -> Some 0x0398 + | "Iota" -> Some 0x0399 + | "Kappa" -> Some 0x039A + | "Lambda" -> Some 0x039B + | "Mu" -> Some 0x039C + | "Nu" -> Some 0x039D + | "Xi" -> Some 0x039E + | "Omicron" -> Some 0x039F + | "Pi" -> Some 0x03A0 + | "Rho" -> Some 0x03A1 + | "Sigma" -> Some 0x03A3 + | "Tau" -> Some 0x03A4 + | "Upsilon" -> Some 0x03A5 + | "Phi" -> Some 0x03A6 + | "Chi" -> Some 0x03A7 + | "Psi" -> Some 0x03A8 + | "Omega" -> Some 0x03A9 + | "alpha" -> Some 0x03B1 + | "beta" -> Some 0x03B2 + | "gamma" -> Some 0x03B3 + | "delta" -> Some 0x03B4 + | "epsilon" -> Some 0x03B5 + | "zeta" -> Some 0x03B6 + | "eta" -> Some 0x03B7 + | "theta" -> Some 0x03B8 + | "iota" -> Some 0x03B9 + | "kappa" -> Some 0x03BA + | "lambda" -> Some 0x03BB + | "mu" -> Some 0x03BC + | "nu" -> Some 0x03BD + | "xi" -> Some 0x03BE + | "omicron" -> Some 0x03BF + | "pi" -> Some 0x03C0 + | "rho" -> Some 0x03C1 + | "sigmaf" -> Some 0x03C2 + | "sigma" -> Some 0x03C3 + | "tau" -> Some 0x03C4 + | "upsilon" -> Some 0x03C5 + | "phi" -> Some 0x03C6 + | "chi" -> Some 0x03C7 + | "psi" -> Some 0x03C8 + | "omega" -> Some 0x03C9 + | "thetasym" -> Some 0x03D1 + | "upsih" -> Some 0x03D2 + | "piv" -> Some 0x03D6 + | "ensp" -> Some 0x2002 + | "emsp" -> Some 0x2003 + | "thinsp" -> Some 0x2009 + | "zwnj" -> Some 0x200C + | "zwj" -> Some 0x200D + | "lrm" -> Some 0x200E + | "rlm" -> Some 0x200F + | "ndash" -> Some 0x2013 + | "mdash" -> Some 0x2014 + | "lsquo" -> Some 0x2018 + | "rsquo" -> Some 0x2019 + | "sbquo" -> Some 0x201A + | "ldquo" -> Some 0x201C + | "rdquo" -> Some 0x201D + | "bdquo" -> Some 0x201E + | "dagger" -> Some 0x2020 + | "Dagger" -> Some 0x2021 + | "bull" -> Some 0x2022 + | "hellip" -> Some 0x2026 + | "permil" -> Some 0x2030 + | "prime" -> Some 0x2032 + | "Prime" -> Some 0x2033 + | "lsaquo" -> Some 0x2039 + | "rsaquo" -> Some 0x203A + | "oline" -> Some 0x203E + | "frasl" -> Some 0x2044 + | "euro" -> Some 0x20AC + | "image" -> Some 0x2111 + | "weierp" -> Some 0x2118 + | "real" -> Some 0x211C + | "trade" -> Some 0x2122 + | "alefsym" -> Some 0x2135 + | "larr" -> Some 0x2190 + | "uarr" -> Some 0x2191 + | "rarr" -> Some 0x2192 + | "darr" -> Some 0x2193 + | "harr" -> Some 0x2194 + | "crarr" -> Some 0x21B5 + | "lArr" -> Some 0x21D0 + | "uArr" -> Some 0x21D1 + | "rArr" -> Some 0x21D2 + | "dArr" -> Some 0x21D3 + | "hArr" -> Some 0x21D4 + | "forall" -> Some 0x2200 + | "part" -> Some 0x2202 + | "exist" -> Some 0x2203 + | "empty" -> Some 0x2205 + | "nabla" -> Some 0x2207 + | "isin" -> Some 0x2208 + | "notin" -> Some 0x2209 + | "ni" -> Some 0x220B + | "prod" -> Some 0x220F + | "sum" -> Some 0x2211 + | "minus" -> Some 0x2212 + | "lowast" -> Some 0x2217 + | "radic" -> Some 0x221A + | "prop" -> Some 0x221D + | "infin" -> Some 0x221E + | "ang" -> Some 0x2220 + | "and" -> Some 0x2227 + | "or" -> Some 0x2228 + | "cap" -> Some 0x2229 + | "cup" -> Some 0x222A + | "'int'" -> Some 0x222B + | "there4" -> Some 0x2234 + | "sim" -> Some 0x223C + | "cong" -> Some 0x2245 + | "asymp" -> Some 0x2248 + | "ne" -> Some 0x2260 + | "equiv" -> Some 0x2261 + | "le" -> Some 0x2264 + | "ge" -> Some 0x2265 + | "sub" -> Some 0x2282 + | "sup" -> Some 0x2283 + | "nsub" -> Some 0x2284 + | "sube" -> Some 0x2286 + | "supe" -> Some 0x2287 + | "oplus" -> Some 0x2295 + | "otimes" -> Some 0x2297 + | "perp" -> Some 0x22A5 + | "sdot" -> Some 0x22C5 + | "lceil" -> Some 0x2308 + | "rceil" -> Some 0x2309 + | "lfloor" -> Some 0x230A + | "rfloor" -> Some 0x230B + | "lang" -> Some 0x27E8 (* 0x2329 in HTML4 *) + | "rang" -> Some 0x27E9 (* 0x232A in HTML4 *) + | "loz" -> Some 0x25CA + | "spades" -> Some 0x2660 + | "clubs" -> Some 0x2663 + | "hearts" -> Some 0x2665 + | "diams" -> Some 0x2666 + | _ -> None + +let rec jsx_child_text env buf raw lexbuf = + match%sedlex lexbuf with + | '<' + | '{' -> + (* Don't actually want to consume these guys + * yet...they're not part of the JSX text *) + Sedlexing.rollback lexbuf; + env + | '>' -> unexpected_error_w_suggest env (loc_of_lexbuf env lexbuf) ">" "{'>'}" + | '}' -> unexpected_error_w_suggest env (loc_of_lexbuf env lexbuf) "}" "{'}'}" + | eof -> illegal env (loc_of_lexbuf env lexbuf) + | line_terminator_sequence -> + let lt = lexeme lexbuf in + Buffer.add_string raw lt; + Buffer.add_string buf lt; + let env = new_line env lexbuf in + jsx_child_text env buf raw lexbuf + | ("&#x", Plus hex_digit, ';') -> + let s = lexeme lexbuf in + let n = String.sub s 3 (String.length s - 4) in + Buffer.add_string raw s; + let code = int_of_string ("0x" ^ n) in + Wtf8.add_wtf_8 buf code; + jsx_child_text env buf raw lexbuf + | ("&#", Plus digit, ';') -> + let s = lexeme lexbuf in + let n = String.sub s 2 (String.length s - 3) in + Buffer.add_string raw s; + let code = int_of_string n in + Wtf8.add_wtf_8 buf code; + jsx_child_text env buf raw lexbuf + | ("&", htmlentity, ';') -> + let s = lexeme lexbuf in + let entity = String.sub s 1 (String.length s - 2) in + Buffer.add_string raw s; + (match decode_html_entity entity with + | Some code -> Wtf8.add_wtf_8 buf code + | None -> Buffer.add_string buf ("&" ^ entity ^ ";")); + jsx_child_text env buf raw lexbuf + (* match multi-char substrings that don't contain the start chars of the above patterns *) + (* TODO: this should include '>' and '}', but that leads to issues with arrow function parsing *) + | Plus (Compl ('<' | '{' | '&' | eof | line_terminator_sequence_start)) + | any -> + let c = lexeme lexbuf in + Buffer.add_string raw c; + Buffer.add_string buf c; + jsx_child_text env buf raw lexbuf + | _ -> failwith "unreachable jsxtext" + +let rec jsx_quote_text env single buf raw lexbuf = + match%sedlex lexbuf with + | '\'' -> + if single then + env + else ( + Buffer.add_char raw '\''; + Buffer.add_char buf '\''; + jsx_quote_text env single buf raw lexbuf + ) + | '"' -> + if not single then + env + else ( + Buffer.add_char raw '"'; + Buffer.add_char buf '"'; + jsx_quote_text env single buf raw lexbuf + ) + | eof -> + let env = illegal env (loc_of_lexbuf env lexbuf) in + env + | line_terminator_sequence -> + let lt = lexeme lexbuf in + Buffer.add_string raw lt; + Buffer.add_string buf lt; + let env = new_line env lexbuf in + jsx_quote_text env single buf raw lexbuf + | ("&#x", Plus hex_digit, ';') -> + let s = lexeme lexbuf in + let n = String.sub s 3 (String.length s - 4) in + Buffer.add_string raw s; + let code = int_of_string ("0x" ^ n) in + Wtf8.add_wtf_8 buf code; + jsx_quote_text env single buf raw lexbuf + | ("&#", Plus digit, ';') -> + let s = lexeme lexbuf in + let n = String.sub s 2 (String.length s - 3) in + Buffer.add_string raw s; + let code = int_of_string n in + Wtf8.add_wtf_8 buf code; + jsx_quote_text env single buf raw lexbuf + | ("&", htmlentity, ';') -> + let s = lexeme lexbuf in + let entity = String.sub s 1 (String.length s - 2) in + Buffer.add_string raw s; + (match decode_html_entity entity with + | Some code -> Wtf8.add_wtf_8 buf code + | None -> Buffer.add_string buf ("&" ^ entity ^ ";")); + jsx_quote_text env single buf raw lexbuf + (* match multi-char substrings that don't contain the start chars of the above patterns *) + | Plus (Compl ('\'' | '"' | '&' | eof | line_terminator_sequence_start)) + | any -> + let c = lexeme lexbuf in + Buffer.add_string raw c; + Buffer.add_string buf c; + jsx_quote_text env single buf raw lexbuf + | _ -> failwith "unreachable jsxtext" + +let jsx_tag env lexbuf = + match%sedlex lexbuf with + | eof -> Token (env, T_EOF) + | line_terminator_sequence -> + let env = new_line env lexbuf in + Continue env + | Plus whitespace -> Continue env + | "//" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = line_comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf false) + | "/*" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf true) + | '<' -> Token (env, T_LESS_THAN) + | '/' -> Token (env, T_DIV) + | '>' -> Token (env, T_GREATER_THAN) + | '{' -> Token (env, T_LCURLY) + | ':' -> Token (env, T_COLON) + | '.' -> Token (env, T_PERIOD) + | '=' -> Token (env, T_ASSIGN) + | "'" + | '"' -> + let quote = lexeme lexbuf in + let start = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let raw = Buffer.create 127 in + Buffer.add_string raw quote; + let single = quote = "'" in + let env = jsx_quote_text env single buf raw lexbuf in + let _end = end_pos_of_lexbuf env lexbuf in + Buffer.add_string raw quote; + let value = Buffer.contents buf in + let raw = Buffer.contents raw in + let loc = { Loc.source = Lex_env.source env; start; _end } in + Token (env, T_JSX_QUOTE_TEXT (loc, value, raw)) + | js_id_start -> + let start_offset = Sedlexing.lexeme_start lexbuf in + (* see #3837, we should fix it - the work could be done in decoding later - cold path*) + loop_jsx_id_continues lexbuf; + let end_offset = Sedlexing.lexeme_end lexbuf in + Sedlexing.set_lexeme_start lexbuf start_offset; + let raw = Sedlexing.lexeme lexbuf in + let loc = loc_of_offsets env start_offset end_offset in + Token (env, T_JSX_IDENTIFIER { raw = Sedlexing.string_of_utf8 raw; loc }) + | any -> Token (env, T_ERROR (lexeme lexbuf)) + | _ -> failwith "unreachable jsx_tag" + +let jsx_child env start buf raw lexbuf = + match%sedlex lexbuf with + | line_terminator_sequence -> + let lt = lexeme lexbuf in + Buffer.add_string raw lt; + Buffer.add_string buf lt; + let env = new_line env lexbuf in + let env = jsx_child_text env buf raw lexbuf in + let _end = end_pos_of_lexbuf env lexbuf in + let value = Buffer.contents buf in + let raw = Buffer.contents raw in + let loc = { Loc.source = Lex_env.source env; start; _end } in + (env, T_JSX_CHILD_TEXT (loc, value, raw)) + | eof -> (env, T_EOF) + | '<' -> (env, T_LESS_THAN) + | '{' -> (env, T_LCURLY) + | any -> + Sedlexing.rollback lexbuf; + (* let jsx_child_text consume this char *) + let env = jsx_child_text env buf raw lexbuf in + let _end = end_pos_of_lexbuf env lexbuf in + let value = Buffer.contents buf in + let raw = Buffer.contents raw in + let loc = { Loc.source = Lex_env.source env; start; _end } in + (env, T_JSX_CHILD_TEXT (loc, value, raw)) + | _ -> failwith "unreachable jsx_child" + +let template_tail env lexbuf = + match%sedlex lexbuf with + | line_terminator_sequence -> + let env = new_line env lexbuf in + Continue env + | Plus whitespace -> Continue env + | "//" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = line_comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf false) + | "/*" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf true) + | '}' -> + let start = start_pos_of_lexbuf env lexbuf in + let value = Buffer.create 127 in + let raw = Buffer.create 127 in + let (env, is_tail) = template_part env value raw lexbuf in + let _end = end_pos_of_lexbuf env lexbuf in + let loc = { Loc.source = Lex_env.source env; start; _end } in + Token (env, T_TEMPLATE_PART (loc, Buffer.contents value, Buffer.contents raw, false, is_tail)) + | any -> + let env = illegal env (loc_of_lexbuf env lexbuf) in + Token (env, T_TEMPLATE_PART (loc_of_lexbuf env lexbuf, "", "", false, true)) + | _ -> failwith "unreachable template_tail" + +(* There are some tokens that never show up in a type and which can cause + * ambiguity. For example, Foo> ends with two angle brackets, not + * with a right shift. + *) +let type_token env lexbuf = + match%sedlex lexbuf with + | line_terminator_sequence -> + let env = new_line env lexbuf in + Continue env + | Plus whitespace -> Continue env + | "/*" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf true) + | ("/*", Star whitespace, (":" | "::" | "flow-include")) -> + let pattern = lexeme lexbuf in + if not (is_comment_syntax_enabled env) then ( + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + Buffer.add_string buf pattern; + let (env, end_pos) = comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf true) + ) else + let env = + if is_in_comment_syntax env then + let loc = loc_of_lexbuf env lexbuf in + unexpected_error env loc pattern + else + env + in + let env = in_comment_syntax true env in + let len = Sedlexing.lexeme_length lexbuf in + if + Sedlexing.Utf8.sub_lexeme lexbuf (len - 1) 1 = ":" + && Sedlexing.Utf8.sub_lexeme lexbuf (len - 2) 1 <> ":" + then + Token (env, T_COLON) + else + Continue env + | "*/" -> + if is_in_comment_syntax env then + let env = in_comment_syntax false env in + Continue env + else ( + Sedlexing.rollback lexbuf; + match%sedlex lexbuf with + | "*" -> Token (env, T_MULT) + | _ -> failwith "expected *" + ) + | "//" -> + let start_pos = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let (env, end_pos) = line_comment env buf lexbuf in + Comment (env, mk_comment env start_pos end_pos buf false) + | "'" + | '"' -> + let quote = lexeme lexbuf in + let start = start_pos_of_lexbuf env lexbuf in + let buf = Buffer.create 127 in + let raw = Buffer.create 127 in + Buffer.add_string raw quote; + let octal = false in + let (env, _end, octal) = string_quote env quote buf raw octal lexbuf in + let loc = { Loc.source = Lex_env.source env; start; _end } in + Token (env, T_STRING (loc, Buffer.contents buf, Buffer.contents raw, octal)) + (* + * Number literals + *) + | (binbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | binbigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_BINARY num) + | _ -> failwith "unreachable type_token bigbigint" + ) + | binbigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_BINARY num) + | (binnumber, (letter | '2' .. '9'), Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | binnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton BINARY num) + | _ -> failwith "unreachable type_token binnumber" + ) + | binnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton BINARY num) + | (octbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | octbigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_OCTAL num) + | _ -> failwith "unreachable type_token octbigint" + ) + | octbigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_OCTAL num) + | (octnumber, (letter | '8' .. '9'), Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | octnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton OCTAL num) + | _ -> failwith "unreachable type_token octnumber" + ) + | octnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton OCTAL num) + | (legacyoctnumber, (letter | '8' .. '9'), Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | legacyoctnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton LEGACY_OCTAL num) + | _ -> failwith "unreachable type_token legacyoctnumber" + ) + | legacyoctnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton LEGACY_OCTAL num) + | (hexbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | hexbigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | _ -> failwith "unreachable type_token hexbigint" + ) + | hexbigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | (hexnumber, non_hex_letter, Star alphanumeric) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | hexnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton NORMAL num) + | _ -> failwith "unreachable type_token hexnumber" + ) + | hexnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton NORMAL num) + | (scibigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | scibigint -> + let num = Sedlexing.lexeme lexbuf in + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidSciBigInt in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | _ -> failwith "unreachable type_token scibigint" + ) + | scibigint -> + let num = Sedlexing.lexeme lexbuf in + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidSciBigInt in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | (scinumber, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | scinumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton NORMAL num) + | _ -> failwith "unreachable type_token scinumber" + ) + | scinumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton NORMAL num) + | (floatbigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | floatbigint -> + let num = Sedlexing.lexeme lexbuf in + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidFloatBigInt in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | _ -> failwith "unreachable type_token floatbigint" + ) + | (wholebigint, word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | wholebigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | _ -> failwith "unreachable type_token wholebigint" + ) + | floatbigint -> + let num = Sedlexing.lexeme lexbuf in + let loc = loc_of_lexbuf env lexbuf in + let env = lex_error env loc Parse_error.InvalidFloatBigInt in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | wholebigint -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_bignum_singleton BIG_NORMAL num) + | ((wholenumber | floatnumber), word) -> + (* Numbers cannot be immediately followed by words *) + recover env lexbuf ~f:(fun env lexbuf -> + match%sedlex lexbuf with + | wholenumber + | floatnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton NORMAL num) + | _ -> failwith "unreachable type_token wholenumber" + ) + | wholenumber + | floatnumber -> + let num = Sedlexing.lexeme lexbuf in + Token (env, mk_num_singleton NORMAL num) + (* Keywords *) + (* `%` is not a valid unicode name *) + | "%checks" -> Token (env, T_CHECKS) + (* Syntax *) + | "[" -> Token (env, T_LBRACKET) + | "]" -> Token (env, T_RBRACKET) + | "{" -> Token (env, T_LCURLY) + | "}" -> Token (env, T_RCURLY) + | "{|" -> Token (env, T_LCURLYBAR) + | "|}" -> Token (env, T_RCURLYBAR) + | "(" -> Token (env, T_LPAREN) + | ")" -> Token (env, T_RPAREN) + | "..." -> Token (env, T_ELLIPSIS) + | "." -> Token (env, T_PERIOD) + | ";" -> Token (env, T_SEMICOLON) + | "," -> Token (env, T_COMMA) + | ":" -> Token (env, T_COLON) + | "?." -> Token (env, T_PLING_PERIOD) + | "?" -> Token (env, T_PLING) + | "[" -> Token (env, T_LBRACKET) + | "]" -> Token (env, T_RBRACKET) + (* Generics *) + | "<" -> Token (env, T_LESS_THAN) + | ">" -> Token (env, T_GREATER_THAN) + (* Generic default *) + | "=" -> Token (env, T_ASSIGN) + (* Optional or nullable *) + | "?" -> Token (env, T_PLING) + (* Existential *) + | "*" -> Token (env, T_MULT) + (* Annotation or bound *) + | ":" -> Token (env, T_COLON) + (* Invalid - but to avoid being interpreted as invalid `|` and `&` *) + | "&&" -> Token (env, T_AND) + | "||" -> Token (env, T_OR) + (* Union *) + | '|' -> Token (env, T_BIT_OR) + (* Intersection *) + | '&' -> Token (env, T_BIT_AND) + (* Function type *) + | "=>" -> Token (env, T_ARROW) + (* Type alias *) + | '=' -> Token (env, T_ASSIGN) + (* Variance annotations *) + | '+' -> Token (env, T_PLUS) + | '-' -> Token (env, T_MINUS) + | "renders?" -> Token (env, T_RENDERS_QUESTION) + | "renders*" -> Token (env, T_RENDERS_STAR) + (* Identifiers *) + | js_id_start -> + let start_offset = Sedlexing.lexeme_start lexbuf in + loop_id_continues lexbuf |> ignore; + let end_offset = Sedlexing.lexeme_end lexbuf in + let loc = loc_of_offsets env start_offset end_offset in + Sedlexing.set_lexeme_start lexbuf start_offset; + let raw = Sedlexing.lexeme lexbuf in + let (env, value) = decode_identifier env raw in + (* keep this list in sync with Parser_env.is_reserved_type + and token_is_reserved_type *) + (match value with + | "any" -> Token (env, T_ANY_TYPE) + | "bigint" -> Token (env, T_BIGINT_TYPE) + | "bool" -> Token (env, T_BOOLEAN_TYPE BOOL) + | "boolean" -> Token (env, T_BOOLEAN_TYPE BOOLEAN) + | "const" -> Token (env, T_CONST) + | "empty" -> Token (env, T_EMPTY_TYPE) + | "extends" -> Token (env, T_EXTENDS) + | "false" -> Token (env, T_FALSE) + | "interface" -> Token (env, T_INTERFACE) + | "keyof" -> Token (env, T_KEYOF) + | "mixed" -> Token (env, T_MIXED_TYPE) + | "never" -> Token (env, T_NEVER_TYPE) + | "null" -> Token (env, T_NULL) + | "number" -> Token (env, T_NUMBER_TYPE) + | "readonly" -> Token (env, T_READONLY) + | "infer" -> Token (env, T_INFER) + | "is" -> Token (env, T_IS) + | "asserts" -> Token (env, T_ASSERTS) + | "implies" -> Token (env, T_IMPLIES) + | "static" -> Token (env, T_STATIC) + | "string" -> Token (env, T_STRING_TYPE) + | "symbol" -> Token (env, T_SYMBOL_TYPE) + | "true" -> Token (env, T_TRUE) + | "typeof" -> Token (env, T_TYPEOF) + | "undefined" -> Token (env, T_UNDEFINED_TYPE) + | "unknown" -> Token (env, T_UNKNOWN_TYPE) + | "void" -> Token (env, T_VOID_TYPE) + | _ -> Token (env, T_IDENTIFIER { loc; value; raw = Sedlexing.string_of_utf8 raw })) + (* Others *) + | eof -> + let env = + if is_in_comment_syntax env then + let loc = loc_of_lexbuf env lexbuf in + lex_error env loc Parse_error.UnexpectedEOS + else + env + in + Token (env, T_EOF) + | any -> Token (env, T_ERROR (lexeme lexbuf)) + | _ -> failwith "unreachable type_token" + +(* Lexing JSX children requires a string buffer to keep track of whitespace + * *) +let jsx_child env = + (* yes, the _start_ of the child is the _end_pos_ of the lexbuf! *) + let start = end_pos_of_lexbuf env env.lex_lb in + let buf = Buffer.create 127 in + let raw = Buffer.create 127 in + let (env, child) = jsx_child env start buf raw env.lex_lb in + let loc = loc_of_token env child in + let lex_errors_acc = env.lex_state.lex_errors_acc in + if lex_errors_acc = [] then + (env, { Lex_result.lex_token = child; lex_loc = loc; lex_comments = []; lex_errors = [] }) + else + ( { env with lex_state = { lex_errors_acc = [] } }, + { + Lex_result.lex_token = child; + lex_loc = loc; + lex_comments = []; + lex_errors = List.rev lex_errors_acc; + } + ) + +let wrap f = + let rec helper comments env = + match f env env.lex_lb with + | Token (env, t) -> + let loc = loc_of_token env t in + let lex_comments = + if comments = [] then + [] + else + List.rev comments + in + let lex_token = t in + let lex_errors_acc = env.lex_state.lex_errors_acc in + if lex_errors_acc = [] then + ( { env with lex_last_loc = loc }, + { Lex_result.lex_token; lex_loc = loc; lex_comments; lex_errors = [] } + ) + else + ( { env with lex_last_loc = loc; lex_state = Lex_env.empty_lex_state }, + { + Lex_result.lex_token; + lex_loc = loc; + lex_comments; + lex_errors = List.rev lex_errors_acc; + } + ) + | Comment (env, ((loc, _) as comment)) -> + let env = { env with lex_last_loc = loc } in + helper (comment :: comments) env + | Continue env -> helper comments env + in + (fun env -> helper [] env) + +let regexp = wrap regexp + +let jsx_tag = wrap jsx_tag + +let template_tail = wrap template_tail + +let type_token = wrap type_token + +let token = wrap token + +let is_valid_identifier_name lexbuf = + match%sedlex lexbuf with + | js_id_start -> + (* we need handle cases like \u1fa38 so that single code is not enough*) + loop_id_continues lexbuf + | _ -> false diff --git a/compiler/flow_parser/parser/flow_lexer.mli b/compiler/flow_parser/parser/flow_lexer.mli new file mode 100644 index 00000000000..8609d224532 --- /dev/null +++ b/compiler/flow_parser/parser/flow_lexer.mli @@ -0,0 +1,20 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +val jsx_child : Lex_env.t -> Lex_env.t * Lex_result.t + +val regexp : Lex_env.t -> Lex_env.t * Lex_result.t + +val jsx_tag : Lex_env.t -> Lex_env.t * Lex_result.t + +val template_tail : Lex_env.t -> Lex_env.t * Lex_result.t + +val type_token : Lex_env.t -> Lex_env.t * Lex_result.t + +val token : Lex_env.t -> Lex_env.t * Lex_result.t + +val is_valid_identifier_name : Flow_sedlexing.lexbuf -> bool diff --git a/compiler/flow_parser/parser/js_id.ml b/compiler/flow_parser/parser/js_id.ml new file mode 100644 index 00000000000..c5da8d8eb44 --- /dev/null +++ b/compiler/flow_parser/parser/js_id.ml @@ -0,0 +1,24 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +external ( .!() ) : (int * int) array -> int -> int * int = "%array_unsafe_get" + +let rec search (arr : _ array) (start : int) (finish : int) target = + if start > finish then + false + else + let mid = start + ((finish - start) / 2) in + let (a, b) = arr.!(mid) in + if target < a then + search arr start (mid - 1) target + else if target >= b then + search arr (mid + 1) finish target + else + true + +let is_valid_unicode_id (i : int) = + search Js_id_unicode.id_continue 0 (Array.length Js_id_unicode.id_continue - 1) i diff --git a/compiler/flow_parser/parser/js_id.mli b/compiler/flow_parser/parser/js_id.mli new file mode 100644 index 00000000000..8bcea4ee4e9 --- /dev/null +++ b/compiler/flow_parser/parser/js_id.mli @@ -0,0 +1,9 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* This test is applied to non-start unicode points *) +val is_valid_unicode_id : int -> bool diff --git a/compiler/flow_parser/parser/js_id_unicode.ml b/compiler/flow_parser/parser/js_id_unicode.ml new file mode 100644 index 00000000000..882ce20ada1 --- /dev/null +++ b/compiler/flow_parser/parser/js_id_unicode.ml @@ -0,0 +1,21 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* This lists two valid unicode point ranges in tuple format. + see more details in https://mathiasbynens.be/notes/javascript-identifiers-es6 + TODO: store it in a flat array + add more docs +*) +[@@@ocamlformat "disable"] + +(* JS has stricter rules with start id *) +let id_start = [|36,37;65,91;95,96;97,123;170,171;181,182;186,187;192,215;216,247;248,706;710,722;736,741;748,749;750,751;880,885;886,888;890,894;895,896;902,903;904,907;908,909;910,930;931,1014;1015,1154;1162,1328;1329,1367;1369,1370;1376,1417;1488,1515;1519,1523;1568,1611;1646,1648;1649,1748;1749,1750;1765,1767;1774,1776;1786,1789;1791,1792;1808,1809;1810,1840;1869,1958;1969,1970;1994,2027;2036,2038;2042,2043;2048,2070;2074,2075;2084,2085;2088,2089;2112,2137;2144,2155;2208,2229;2230,2238;2308,2362;2365,2366;2384,2385;2392,2402;2417,2433;2437,2445;2447,2449;2451,2473;2474,2481;2482,2483;2486,2490;2493,2494;2510,2511;2524,2526;2527,2530;2544,2546;2556,2557;2565,2571;2575,2577;2579,2601;2602,2609;2610,2612;2613,2615;2616,2618;2649,2653;2654,2655;2674,2677;2693,2702;2703,2706;2707,2729;2730,2737;2738,2740;2741,2746;2749,2750;2768,2769;2784,2786;2809,2810;2821,2829;2831,2833;2835,2857;2858,2865;2866,2868;2869,2874;2877,2878;2908,2910;2911,2914;2929,2930;2947,2948;2949,2955;2958,2961;2962,2966;2969,2971;2972,2973;2974,2976;2979,2981;2984,2987;2990,3002;3024,3025;3077,3085;3086,3089;3090,3113;3114,3130;3133,3134;3160,3163;3168,3170;3200,3201;3205,3213;3214,3217;3218,3241;3242,3252;3253,3258;3261,3262;3294,3295;3296,3298;3313,3315;3333,3341;3342,3345;3346,3387;3389,3390;3406,3407;3412,3415;3423,3426;3450,3456;3461,3479;3482,3506;3507,3516;3517,3518;3520,3527;3585,3633;3634,3636;3648,3655;3713,3715;3716,3717;3718,3723;3724,3748;3749,3750;3751,3761;3762,3764;3773,3774;3776,3781;3782,3783;3804,3808;3840,3841;3904,3912;3913,3949;3976,3981;4096,4139;4159,4160;4176,4182;4186,4190;4193,4194;4197,4199;4206,4209;4213,4226;4238,4239;4256,4294;4295,4296;4301,4302;4304,4347;4348,4681;4682,4686;4688,4695;4696,4697;4698,4702;4704,4745;4746,4750;4752,4785;4786,4790;4792,4799;4800,4801;4802,4806;4808,4823;4824,4881;4882,4886;4888,4955;4992,5008;5024,5110;5112,5118;5121,5741;5743,5760;5761,5787;5792,5867;5870,5881;5888,5901;5902,5906;5920,5938;5952,5970;5984,5997;5998,6001;6016,6068;6103,6104;6108,6109;6176,6265;6272,6313;6314,6315;6320,6390;6400,6431;6480,6510;6512,6517;6528,6572;6576,6602;6656,6679;6688,6741;6823,6824;6917,6964;6981,6988;7043,7073;7086,7088;7098,7142;7168,7204;7245,7248;7258,7294;7296,7305;7312,7355;7357,7360;7401,7405;7406,7412;7413,7415;7418,7419;7424,7616;7680,7958;7960,7966;7968,8006;8008,8014;8016,8024;8025,8026;8027,8028;8029,8030;8031,8062;8064,8117;8118,8125;8126,8127;8130,8133;8134,8141;8144,8148;8150,8156;8160,8173;8178,8181;8182,8189;8305,8306;8319,8320;8336,8349;8450,8451;8455,8456;8458,8468;8469,8470;8472,8478;8484,8485;8486,8487;8488,8489;8490,8506;8508,8512;8517,8522;8526,8527;8544,8585;11264,11311;11312,11359;11360,11493;11499,11503;11506,11508;11520,11558;11559,11560;11565,11566;11568,11624;11631,11632;11648,11671;11680,11687;11688,11695;11696,11703;11704,11711;11712,11719;11720,11727;11728,11735;11736,11743;12293,12296;12321,12330;12337,12342;12344,12349;12353,12439;12443,12448;12449,12539;12540,12544;12549,12592;12593,12687;12704,12731;12784,12800;13312,19894;19968,40944;40960,42125;42192,42238;42240,42509;42512,42528;42538,42540;42560,42607;42623,42654;42656,42736;42775,42784;42786,42889;42891,42944;42946,42951;42999,43010;43011,43014;43015,43019;43020,43043;43072,43124;43138,43188;43250,43256;43259,43260;43261,43263;43274,43302;43312,43335;43360,43389;43396,43443;43471,43472;43488,43493;43494,43504;43514,43519;43520,43561;43584,43587;43588,43596;43616,43639;43642,43643;43646,43696;43697,43698;43701,43703;43705,43710;43712,43713;43714,43715;43739,43742;43744,43755;43762,43765;43777,43783;43785,43791;43793,43799;43808,43815;43816,43823;43824,43867;43868,43880;43888,44003;44032,55204;55216,55239;55243,55292;63744,64110;64112,64218;64256,64263;64275,64280;64285,64286;64287,64297;64298,64311;64312,64317;64318,64319;64320,64322;64323,64325;64326,64434;64467,64830;64848,64912;64914,64968;65008,65020;65136,65141;65142,65277;65313,65339;65345,65371;65382,65471;65474,65480;65482,65488;65490,65496;65498,65501;65536,65548;65549,65575;65576,65595;65596,65598;65599,65614;65616,65630;65664,65787;65856,65909;66176,66205;66208,66257;66304,66336;66349,66379;66384,66422;66432,66462;66464,66500;66504,66512;66513,66518;66560,66718;66736,66772;66776,66812;66816,66856;66864,66916;67072,67383;67392,67414;67424,67432;67584,67590;67592,67593;67594,67638;67639,67641;67644,67645;67647,67670;67680,67703;67712,67743;67808,67827;67828,67830;67840,67862;67872,67898;67968,68024;68030,68032;68096,68097;68112,68116;68117,68120;68121,68150;68192,68221;68224,68253;68288,68296;68297,68325;68352,68406;68416,68438;68448,68467;68480,68498;68608,68681;68736,68787;68800,68851;68864,68900;69376,69405;69415,69416;69424,69446;69600,69623;69635,69688;69763,69808;69840,69865;69891,69927;69956,69957;69968,70003;70006,70007;70019,70067;70081,70085;70106,70107;70108,70109;70144,70162;70163,70188;70272,70279;70280,70281;70282,70286;70287,70302;70303,70313;70320,70367;70405,70413;70415,70417;70419,70441;70442,70449;70450,70452;70453,70458;70461,70462;70480,70481;70493,70498;70656,70709;70727,70731;70751,70752;70784,70832;70852,70854;70855,70856;71040,71087;71128,71132;71168,71216;71236,71237;71296,71339;71352,71353;71424,71451;71680,71724;71840,71904;71935,71936;72096,72104;72106,72145;72161,72162;72163,72164;72192,72193;72203,72243;72250,72251;72272,72273;72284,72330;72349,72350;72384,72441;72704,72713;72714,72751;72768,72769;72818,72848;72960,72967;72968,72970;72971,73009;73030,73031;73056,73062;73063,73065;73066,73098;73112,73113;73440,73459;73728,74650;74752,74863;74880,75076;77824,78895;82944,83527;92160,92729;92736,92767;92880,92910;92928,92976;92992,92996;93027,93048;93053,93072;93760,93824;93952,94027;94032,94033;94099,94112;94176,94178;94179,94180;94208,100344;100352,101107;110592,110879;110928,110931;110948,110952;110960,111356;113664,113771;113776,113789;113792,113801;113808,113818;119808,119893;119894,119965;119966,119968;119970,119971;119973,119975;119977,119981;119982,119994;119995,119996;119997,120004;120005,120070;120071,120075;120077,120085;120086,120093;120094,120122;120123,120127;120128,120133;120134,120135;120138,120145;120146,120486;120488,120513;120514,120539;120540,120571;120572,120597;120598,120629;120630,120655;120656,120687;120688,120713;120714,120745;120746,120771;120772,120780;123136,123181;123191,123198;123214,123215;123584,123628;124928,125125;125184,125252;125259,125260;126464,126468;126469,126496;126497,126499;126500,126501;126503,126504;126505,126515;126516,126520;126521,126522;126523,126524;126530,126531;126535,126536;126537,126538;126539,126540;126541,126544;126545,126547;126548,126549;126551,126552;126553,126554;126555,126556;126557,126558;126559,126560;126561,126563;126564,126565;126567,126571;126572,126579;126580,126584;126585,126589;126590,126591;126592,126602;126603,126620;126625,126628;126629,126634;126635,126652;131072,173783;173824,177973;177984,178206;178208,183970;183984,191457;194560,195102|] + +(* The followed ID restriction is relaxed, this one + is used in our customized unicode lexing. +*) +let id_continue = [|36,37;48,58;65,91;95,96;97,123;170,171;181,182;183,184;186,187;192,215;216,247;248,706;710,722;736,741;748,749;750,751;768,885;886,888;890,894;895,896;902,907;908,909;910,930;931,1014;1015,1154;1155,1160;1162,1328;1329,1367;1369,1370;1376,1417;1425,1470;1471,1472;1473,1475;1476,1478;1479,1480;1488,1515;1519,1523;1552,1563;1568,1642;1646,1748;1749,1757;1759,1769;1770,1789;1791,1792;1808,1867;1869,1970;1984,2038;2042,2043;2045,2046;2048,2094;2112,2140;2144,2155;2208,2229;2230,2238;2259,2274;2275,2404;2406,2416;2417,2436;2437,2445;2447,2449;2451,2473;2474,2481;2482,2483;2486,2490;2492,2501;2503,2505;2507,2511;2519,2520;2524,2526;2527,2532;2534,2546;2556,2557;2558,2559;2561,2564;2565,2571;2575,2577;2579,2601;2602,2609;2610,2612;2613,2615;2616,2618;2620,2621;2622,2627;2631,2633;2635,2638;2641,2642;2649,2653;2654,2655;2662,2678;2689,2692;2693,2702;2703,2706;2707,2729;2730,2737;2738,2740;2741,2746;2748,2758;2759,2762;2763,2766;2768,2769;2784,2788;2790,2800;2809,2816;2817,2820;2821,2829;2831,2833;2835,2857;2858,2865;2866,2868;2869,2874;2876,2885;2887,2889;2891,2894;2902,2904;2908,2910;2911,2916;2918,2928;2929,2930;2946,2948;2949,2955;2958,2961;2962,2966;2969,2971;2972,2973;2974,2976;2979,2981;2984,2987;2990,3002;3006,3011;3014,3017;3018,3022;3024,3025;3031,3032;3046,3056;3072,3085;3086,3089;3090,3113;3114,3130;3133,3141;3142,3145;3146,3150;3157,3159;3160,3163;3168,3172;3174,3184;3200,3204;3205,3213;3214,3217;3218,3241;3242,3252;3253,3258;3260,3269;3270,3273;3274,3278;3285,3287;3294,3295;3296,3300;3302,3312;3313,3315;3328,3332;3333,3341;3342,3345;3346,3397;3398,3401;3402,3407;3412,3416;3423,3428;3430,3440;3450,3456;3458,3460;3461,3479;3482,3506;3507,3516;3517,3518;3520,3527;3530,3531;3535,3541;3542,3543;3544,3552;3558,3568;3570,3572;3585,3643;3648,3663;3664,3674;3713,3715;3716,3717;3718,3723;3724,3748;3749,3750;3751,3774;3776,3781;3782,3783;3784,3790;3792,3802;3804,3808;3840,3841;3864,3866;3872,3882;3893,3894;3895,3896;3897,3898;3902,3912;3913,3949;3953,3973;3974,3992;3993,4029;4038,4039;4096,4170;4176,4254;4256,4294;4295,4296;4301,4302;4304,4347;4348,4681;4682,4686;4688,4695;4696,4697;4698,4702;4704,4745;4746,4750;4752,4785;4786,4790;4792,4799;4800,4801;4802,4806;4808,4823;4824,4881;4882,4886;4888,4955;4957,4960;4969,4978;4992,5008;5024,5110;5112,5118;5121,5741;5743,5760;5761,5787;5792,5867;5870,5881;5888,5901;5902,5909;5920,5941;5952,5972;5984,5997;5998,6001;6002,6004;6016,6100;6103,6104;6108,6110;6112,6122;6155,6158;6160,6170;6176,6265;6272,6315;6320,6390;6400,6431;6432,6444;6448,6460;6470,6510;6512,6517;6528,6572;6576,6602;6608,6619;6656,6684;6688,6751;6752,6781;6783,6794;6800,6810;6823,6824;6832,6846;6912,6988;6992,7002;7019,7028;7040,7156;7168,7224;7232,7242;7245,7294;7296,7305;7312,7355;7357,7360;7376,7379;7380,7419;7424,7674;7675,7958;7960,7966;7968,8006;8008,8014;8016,8024;8025,8026;8027,8028;8029,8030;8031,8062;8064,8117;8118,8125;8126,8127;8130,8133;8134,8141;8144,8148;8150,8156;8160,8173;8178,8181;8182,8189;8204,8206;8255,8257;8276,8277;8305,8306;8319,8320;8336,8349;8400,8413;8417,8418;8421,8433;8450,8451;8455,8456;8458,8468;8469,8470;8472,8478;8484,8485;8486,8487;8488,8489;8490,8506;8508,8512;8517,8522;8526,8527;8544,8585;11264,11311;11312,11359;11360,11493;11499,11508;11520,11558;11559,11560;11565,11566;11568,11624;11631,11632;11647,11671;11680,11687;11688,11695;11696,11703;11704,11711;11712,11719;11720,11727;11728,11735;11736,11743;11744,11776;12293,12296;12321,12336;12337,12342;12344,12349;12353,12439;12441,12448;12449,12539;12540,12544;12549,12592;12593,12687;12704,12731;12784,12800;13312,19894;19968,40944;40960,42125;42192,42238;42240,42509;42512,42540;42560,42608;42612,42622;42623,42738;42775,42784;42786,42889;42891,42944;42946,42951;42999,43048;43072,43124;43136,43206;43216,43226;43232,43256;43259,43260;43261,43310;43312,43348;43360,43389;43392,43457;43471,43482;43488,43519;43520,43575;43584,43598;43600,43610;43616,43639;43642,43715;43739,43742;43744,43760;43762,43767;43777,43783;43785,43791;43793,43799;43808,43815;43816,43823;43824,43867;43868,43880;43888,44011;44012,44014;44016,44026;44032,55204;55216,55239;55243,55292;63744,64110;64112,64218;64256,64263;64275,64280;64285,64297;64298,64311;64312,64317;64318,64319;64320,64322;64323,64325;64326,64434;64467,64830;64848,64912;64914,64968;65008,65020;65024,65040;65056,65072;65075,65077;65101,65104;65136,65141;65142,65277;65296,65306;65313,65339;65343,65344;65345,65371;65382,65471;65474,65480;65482,65488;65490,65496;65498,65501;65536,65548;65549,65575;65576,65595;65596,65598;65599,65614;65616,65630;65664,65787;65856,65909;66045,66046;66176,66205;66208,66257;66272,66273;66304,66336;66349,66379;66384,66427;66432,66462;66464,66500;66504,66512;66513,66518;66560,66718;66720,66730;66736,66772;66776,66812;66816,66856;66864,66916;67072,67383;67392,67414;67424,67432;67584,67590;67592,67593;67594,67638;67639,67641;67644,67645;67647,67670;67680,67703;67712,67743;67808,67827;67828,67830;67840,67862;67872,67898;67968,68024;68030,68032;68096,68100;68101,68103;68108,68116;68117,68120;68121,68150;68152,68155;68159,68160;68192,68221;68224,68253;68288,68296;68297,68327;68352,68406;68416,68438;68448,68467;68480,68498;68608,68681;68736,68787;68800,68851;68864,68904;68912,68922;69376,69405;69415,69416;69424,69457;69600,69623;69632,69703;69734,69744;69759,69819;69840,69865;69872,69882;69888,69941;69942,69952;69956,69959;69968,70004;70006,70007;70016,70085;70089,70093;70096,70107;70108,70109;70144,70162;70163,70200;70206,70207;70272,70279;70280,70281;70282,70286;70287,70302;70303,70313;70320,70379;70384,70394;70400,70404;70405,70413;70415,70417;70419,70441;70442,70449;70450,70452;70453,70458;70459,70469;70471,70473;70475,70478;70480,70481;70487,70488;70493,70500;70502,70509;70512,70517;70656,70731;70736,70746;70750,70752;70784,70854;70855,70856;70864,70874;71040,71094;71096,71105;71128,71134;71168,71233;71236,71237;71248,71258;71296,71353;71360,71370;71424,71451;71453,71468;71472,71482;71680,71739;71840,71914;71935,71936;72096,72104;72106,72152;72154,72162;72163,72165;72192,72255;72263,72264;72272,72346;72349,72350;72384,72441;72704,72713;72714,72759;72760,72769;72784,72794;72818,72848;72850,72872;72873,72887;72960,72967;72968,72970;72971,73015;73018,73019;73020,73022;73023,73032;73040,73050;73056,73062;73063,73065;73066,73103;73104,73106;73107,73113;73120,73130;73440,73463;73728,74650;74752,74863;74880,75076;77824,78895;82944,83527;92160,92729;92736,92767;92768,92778;92880,92910;92912,92917;92928,92983;92992,92996;93008,93018;93027,93048;93053,93072;93760,93824;93952,94027;94031,94088;94095,94112;94176,94178;94179,94180;94208,100344;100352,101107;110592,110879;110928,110931;110948,110952;110960,111356;113664,113771;113776,113789;113792,113801;113808,113818;113821,113823;119141,119146;119149,119155;119163,119171;119173,119180;119210,119214;119362,119365;119808,119893;119894,119965;119966,119968;119970,119971;119973,119975;119977,119981;119982,119994;119995,119996;119997,120004;120005,120070;120071,120075;120077,120085;120086,120093;120094,120122;120123,120127;120128,120133;120134,120135;120138,120145;120146,120486;120488,120513;120514,120539;120540,120571;120572,120597;120598,120629;120630,120655;120656,120687;120688,120713;120714,120745;120746,120771;120772,120780;120782,120832;121344,121399;121403,121453;121461,121462;121476,121477;121499,121504;121505,121520;122880,122887;122888,122905;122907,122914;122915,122917;122918,122923;123136,123181;123184,123198;123200,123210;123214,123215;123584,123642;124928,125125;125136,125143;125184,125260;125264,125274;126464,126468;126469,126496;126497,126499;126500,126501;126503,126504;126505,126515;126516,126520;126521,126522;126523,126524;126530,126531;126535,126536;126537,126538;126539,126540;126541,126544;126545,126547;126548,126549;126551,126552;126553,126554;126555,126556;126557,126558;126559,126560;126561,126563;126564,126565;126567,126571;126572,126579;126580,126584;126585,126589;126590,126591;126592,126602;126603,126620;126625,126628;126629,126634;126635,126652;131072,173783;173824,177973;177984,178206;178208,183970;183984,191457;194560,195102;917760,918000|] diff --git a/compiler/flow_parser/parser/jsdoc.ml b/compiler/flow_parser/parser/jsdoc.ml new file mode 100644 index 00000000000..4e3d1710809 --- /dev/null +++ b/compiler/flow_parser/parser/jsdoc.ml @@ -0,0 +1,274 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Sedlexing = Flow_sedlexing + +module Param = struct + type optionality = + | NotOptional + | Optional + | OptionalWithDefault of string + [@@deriving show, eq] + + type info = { + description: string option; + optional: optionality; + } + [@@deriving show, eq] + + type path = + | Name + | Element of path + | Member of path * string + [@@deriving show, eq] + + type t = (path * info) list [@@deriving show, eq] +end + +module Params = struct + type t = (string * Param.t) list [@@deriving show, eq] +end + +module Unrecognized_tags = struct + type t = (string * string option) list [@@deriving show, eq] +end + +type t = { + description: string option; + params: Params.t; + deprecated: string option; + unrecognized_tags: Unrecognized_tags.t; +} + +(*************) +(* accessors *) +(*************) + +let description { description; _ } = description + +let params { params; _ } = params + +let deprecated { deprecated; _ } = deprecated + +let unrecognized_tags { unrecognized_tags; _ } = unrecognized_tags + +(***********) +(* parsing *) +(***********) + +module Parser = struct + (* regexps copied from Flow_lexer since sedlex doesn't let us import them *) + + let whitespace = + [%sedlex.regexp? + ( 0x0009 | 0x000B | 0x000C | 0x0020 | 0x00A0 | 0xfeff | 0x1680 + | 0x2000 .. 0x200a + | 0x202f | 0x205f | 0x3000 )] + + let line_terminator_sequence = [%sedlex.regexp? '\n' | '\r' | "\r\n" | 0x2028 | 0x2029] + + let identifier = [%sedlex.regexp? Plus (Compl (white_space | '[' | '.' | ']' | '=' | '{'))] + + (* Helpers *) + + let empty = { description = None; params = []; deprecated = None; unrecognized_tags = [] } + + let trimmed_string_of_buffer buffer = buffer |> Buffer.contents |> String.trim + + let description_of_desc_buf desc_buf = + match trimmed_string_of_buffer desc_buf with + | "" -> None + | s -> Some s + + (* like Base.List.Assoc.add, but maintains ordering differently: + * - if k is already in the list, keeps it in that position and updates the value + * - if k isn't in the list, adds it to the end *) + let rec add_assoc ~equal k v = function + | [] -> [(k, v)] + | (k', v') :: xs -> + if equal k' k then + (k, v) :: xs + else + (k', v') :: add_assoc ~equal k v xs + + let add_param jsdoc name path description optional = + let old_param_infos = + match Base.List.Assoc.find ~equal:String.equal jsdoc.params name with + | None -> [] + | Some param_infos -> param_infos + in + let new_param_infos = + add_assoc ~equal:Param.equal_path path { Param.description; optional } old_param_infos + in + { jsdoc with params = add_assoc ~equal:String.equal name new_param_infos jsdoc.params } + + let add_unrecognized_tag jsdoc name description = + let { unrecognized_tags; _ } = jsdoc in + { jsdoc with unrecognized_tags = unrecognized_tags @ [(name, description)] } + + (* Parsing functions *) + + (* + `description`, `description_or_tag`, and `description_startline` are + helpers for parsing descriptions: a description is a possibly-multiline + string terminated by EOF or a new tag. The beginning of each line could + contain whitespace and asterisks, which are stripped out when parsing. + *) + let rec description desc_buf lexbuf = + match%sedlex lexbuf with + | line_terminator_sequence -> + Buffer.add_string desc_buf (Sedlexing.Utf8.lexeme lexbuf); + description_startline desc_buf lexbuf + | any -> + Buffer.add_string desc_buf (Sedlexing.Utf8.lexeme lexbuf); + description desc_buf lexbuf + | _ (* eof *) -> description_of_desc_buf desc_buf + + and description_or_tag desc_buf lexbuf = + match%sedlex lexbuf with + | '@' -> description_of_desc_buf desc_buf + | _ -> description desc_buf lexbuf + + and description_startline desc_buf lexbuf = + match%sedlex lexbuf with + | '*' + | whitespace -> + description_startline desc_buf lexbuf + | _ -> description_or_tag desc_buf lexbuf + + let rec param_path ?(path = Param.Name) lexbuf = + match%sedlex lexbuf with + | "[]" -> param_path ~path:(Param.Element path) lexbuf + | ('.', identifier) -> + let member = Sedlexing.Utf8.sub_lexeme lexbuf 1 (Sedlexing.lexeme_length lexbuf - 1) in + param_path ~path:(Param.Member (path, member)) lexbuf + | _ -> path + + let rec skip_tag jsdoc lexbuf = + match%sedlex lexbuf with + | Plus (Compl '@') -> skip_tag jsdoc lexbuf + | '@' -> tag jsdoc lexbuf + | _ (* eof *) -> jsdoc + + and param_tag_description jsdoc name path optional lexbuf = + let desc_buf = Buffer.create 127 in + let description = description desc_buf lexbuf in + let jsdoc = add_param jsdoc name path description optional in + tag jsdoc lexbuf + + and param_tag_pre_description jsdoc name path optional lexbuf = + match%sedlex lexbuf with + | ' ' -> param_tag_pre_description jsdoc name path optional lexbuf + | '-' -> param_tag_description jsdoc name path optional lexbuf + | _ -> param_tag_description jsdoc name path optional lexbuf + + and param_tag_optional_default jsdoc name path def_buf lexbuf = + match%sedlex lexbuf with + | ']' -> + let default = Buffer.contents def_buf in + param_tag_pre_description jsdoc name path (Param.OptionalWithDefault default) lexbuf + | Plus (Compl ']') -> + Buffer.add_string def_buf (Sedlexing.Utf8.lexeme lexbuf); + param_tag_optional_default jsdoc name path def_buf lexbuf + | _ -> + let default = Buffer.contents def_buf in + param_tag_pre_description jsdoc name path (Param.OptionalWithDefault default) lexbuf + + and param_tag_optional jsdoc lexbuf = + match%sedlex lexbuf with + | identifier -> + let name = Sedlexing.Utf8.lexeme lexbuf in + let path = param_path lexbuf in + (match%sedlex lexbuf with + | ']' -> param_tag_pre_description jsdoc name path Param.Optional lexbuf + | '=' -> + let def_buf = Buffer.create 127 in + param_tag_optional_default jsdoc name path def_buf lexbuf + | _ -> param_tag_pre_description jsdoc name path Param.Optional lexbuf) + | _ -> skip_tag jsdoc lexbuf + + (* ignore jsdoc type annotation *) + and param_tag_type jsdoc lexbuf = + match%sedlex lexbuf with + | '}' -> param_tag jsdoc lexbuf + | Plus (Compl '}') -> param_tag_type jsdoc lexbuf + | _ (* eof *) -> jsdoc + + and param_tag jsdoc lexbuf = + match%sedlex lexbuf with + | ' ' -> param_tag jsdoc lexbuf + | '{' -> param_tag_type jsdoc lexbuf + | '[' -> param_tag_optional jsdoc lexbuf + | identifier -> + let name = Sedlexing.Utf8.lexeme lexbuf in + let path = param_path lexbuf in + param_tag_pre_description jsdoc name path Param.NotOptional lexbuf + | _ -> skip_tag jsdoc lexbuf + + and description_tag jsdoc lexbuf = + let desc_buf = Buffer.create 127 in + let description = description desc_buf lexbuf in + let jsdoc = { jsdoc with description } in + tag jsdoc lexbuf + + and deprecated_tag jsdoc lexbuf = + let deprecated_tag_buf = Buffer.create 127 in + let deprecated = Some (Base.Option.value ~default:"" (description deprecated_tag_buf lexbuf)) in + { jsdoc with deprecated } + + and unrecognized_tag jsdoc name lexbuf = + let desc_buf = Buffer.create 127 in + let description = description desc_buf lexbuf in + let jsdoc = add_unrecognized_tag jsdoc name description in + tag jsdoc lexbuf + + and tag jsdoc lexbuf = + match%sedlex lexbuf with + | "param" + | "arg" + | "argument" -> + param_tag jsdoc lexbuf + | "description" + | "desc" -> + description_tag jsdoc lexbuf + | "deprecated" -> deprecated_tag jsdoc lexbuf + | identifier -> + let name = Sedlexing.Utf8.lexeme lexbuf in + unrecognized_tag jsdoc name lexbuf + | _ -> skip_tag jsdoc lexbuf + + let initial lexbuf = + match%sedlex lexbuf with + | ('*', Compl '*') -> + Sedlexing.rollback lexbuf; + let desc_buf = Buffer.create 127 in + let description = description_startline desc_buf lexbuf in + let jsdoc = { empty with description } in + Some (tag jsdoc lexbuf) + | _ -> None +end + +let parse str = + let lexbuf = Sedlexing.Utf8.from_string str in + Parser.initial lexbuf + +(* find and parse the last jsdoc-containing comment in the list if exists *) +let of_comments = + let open Flow_ast in + let of_comment = function + | (_, Comment.{ kind = Block; text; _ }) -> parse text + | (_, Comment.{ kind = Line; _ }) -> None + in + let rec of_comment_list = function + | [] -> None + | c :: cs -> + (match of_comment_list cs with + | Some _ as j -> j + | None -> of_comment c) + in + let of_syntax Syntax.{ leading; _ } = of_comment_list leading in + Base.Option.bind ~f:of_syntax diff --git a/compiler/flow_parser/parser/jsdoc.mli b/compiler/flow_parser/parser/jsdoc.mli new file mode 100644 index 00000000000..cdded0ab4f3 --- /dev/null +++ b/compiler/flow_parser/parser/jsdoc.mli @@ -0,0 +1,56 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Param : sig + type optionality = + | NotOptional + | Optional + | OptionalWithDefault of string + [@@deriving show, eq] + + type info = { + description: string option; + optional: optionality; + } + [@@deriving show, eq] + + type path = + | Name + | Element of path + | Member of path * string + [@@deriving show, eq] + + type t = (path * info) list [@@deriving show, eq] +end + +module Params : sig + type t = (string * Param.t) list [@@deriving show, eq] +end + +module Unrecognized_tags : sig + type t = (string * string option) list [@@deriving show, eq] +end + +type t + +(*************) +(* accessors *) +(*************) + +val description : t -> string option + +val params : t -> Params.t + +val deprecated : t -> string option + +val unrecognized_tags : t -> Unrecognized_tags.t + +(***********) +(* parsing *) +(***********) + +val of_comments : ('M, 'T) Flow_ast.Syntax.t option -> t option diff --git a/compiler/flow_parser/parser/jsx_parser.ml b/compiler/flow_parser/parser/jsx_parser.ml new file mode 100644 index 00000000000..2a29078dd91 --- /dev/null +++ b/compiler/flow_parser/parser/jsx_parser.ml @@ -0,0 +1,562 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Ast = Flow_ast +open Token +open Parser_common +open Parser_env +open Flow_ast + +module JSX (Parse : Parser_common.PARSER) (Expression : Parser_common.EXPRESSION) : + Parser_common.JSX = struct + (* Consumes and returns the trailing comments after the end of a JSX tag name, + attribute, or spread attribute. + + If the component is followed by the end of the JSX tag, then all trailing + comments are returned. If the component is instead followed by another tag + component on another line, only trailing comments on the same line are + returned. If the component is followed by another tag component on the same + line, all trailing comments will instead be leading the next component. *) + let tag_component_trailing_comments env = + match Peek.token env with + | T_EOF + | T_DIV + | T_GREATER_THAN -> + Eat.trailing_comments env + | _ when Peek.is_line_terminator env -> Eat.comments_until_next_line env + | _ -> [] + + let spread_attribute env = + let leading = Peek.comments env in + Eat.push_lex_mode env Lex_mode.NORMAL; + let (loc, argument) = + with_loc + (fun env -> + Expect.token env T_LCURLY; + Expect.token env T_ELLIPSIS; + let argument = Parse.assignment env in + Expect.token env T_RCURLY; + argument) + env + in + Eat.pop_lex_mode env; + let trailing = tag_component_trailing_comments env in + ( loc, + { + JSX.SpreadAttribute.argument; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + let expression_container_contents env = + if Peek.token env = T_RCURLY then + JSX.ExpressionContainer.EmptyExpression + else + JSX.ExpressionContainer.Expression (Parse.expression env) + + let expression_container env = + let leading = Peek.comments env in + Eat.push_lex_mode env Lex_mode.NORMAL; + let (loc, expression) = + with_loc + (fun env -> + Expect.token env T_LCURLY; + let expression = expression_container_contents env in + Expect.token env T_RCURLY; + expression) + env + in + Eat.pop_lex_mode env; + let trailing = tag_component_trailing_comments env in + ( loc, + { + JSX.ExpressionContainer.expression; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal:[] (); + } + ) + + let expression_container_or_spread_child env = + Eat.push_lex_mode env Lex_mode.NORMAL; + let (loc, result) = + with_loc + (fun env -> + Expect.token env T_LCURLY; + let result = + match Peek.token env with + | T_ELLIPSIS -> + let leading = Peek.comments env in + Expect.token env T_ELLIPSIS; + let expression = Parse.assignment env in + JSX.SpreadChild + { + JSX.SpreadChild.expression; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + | _ -> + let expression = expression_container_contents env in + let internal = + match expression with + | JSX.ExpressionContainer.EmptyExpression -> Peek.comments env + | _ -> [] + in + JSX.ExpressionContainer + { + JSX.ExpressionContainer.expression; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~internal (); + } + in + Expect.token env T_RCURLY; + result) + env + in + Eat.pop_lex_mode env; + (loc, result) + + let identifier env = + let loc = Peek.loc env in + let name = + match Peek.token env with + | T_JSX_IDENTIFIER { raw; _ } -> raw + | _ -> + error_unexpected ~expected:"an identifier" env; + "" + in + let leading = Peek.comments env in + Eat.token env; + (* Unless this identifier is the first part of a namespaced name, member + expression, or attribute name, it is the end of a tag component. *) + let trailing = + match Peek.token env with + (* Namespaced name *) + | T_COLON + (* Member expression *) + | T_PERIOD + (* Attribute name *) + | T_ASSIGN -> + Eat.trailing_comments env + | _ -> tag_component_trailing_comments env + in + (loc, JSX.Identifier.{ name; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () }) + + let name = + let rec member_expression env member = + match Peek.token env with + | T_PERIOD -> + let (start_loc, _) = member in + let member = + with_loc + ~start_loc + (fun env -> + Expect.token env T_PERIOD; + let property = identifier env in + { + JSX.MemberExpression._object = JSX.MemberExpression.MemberExpression member; + property; + }) + env + in + member_expression env member + | _ -> member + in + fun env -> + match Peek.ith_token ~i:1 env with + | T_COLON -> + let namespaced_name = + with_loc + (fun env -> + let namespace = identifier env in + Expect.token env T_COLON; + let name = identifier env in + { JSX.NamespacedName.namespace; name }) + env + in + JSX.NamespacedName namespaced_name + | T_PERIOD -> + let member = + with_loc + (fun env -> + let _object = JSX.MemberExpression.Identifier (identifier env) in + Expect.token env T_PERIOD; + let property = identifier env in + { JSX.MemberExpression._object; property }) + env + in + JSX.MemberExpression (member_expression env member) + | _ -> + let name = identifier env in + JSX.Identifier name + + let names_are_equal = + let identifiers_are_equal a b = + let (_, { JSX.Identifier.name = a; _ }) = a in + let (_, { JSX.Identifier.name = b; _ }) = b in + String.equal a b + in + let rec member_expressions_are_equal a b = + let (_, { JSX.MemberExpression._object = a_obj; property = a_prop }) = a in + let (_, { JSX.MemberExpression._object = b_obj; property = b_prop }) = b in + let objs_equal = + match (a_obj, b_obj) with + | (JSX.MemberExpression.Identifier a, JSX.MemberExpression.Identifier b) -> + identifiers_are_equal a b + | (JSX.MemberExpression.MemberExpression a, JSX.MemberExpression.MemberExpression b) -> + member_expressions_are_equal a b + | _ -> false + in + objs_equal && identifiers_are_equal a_prop b_prop + in + let namespaced_names_are_equal a b = + let (_, { JSX.NamespacedName.namespace = a_ns; name = a_name }) = a in + let (_, { JSX.NamespacedName.namespace = b_ns; name = b_name }) = b in + identifiers_are_equal a_ns b_ns && identifiers_are_equal a_name b_name + in + fun a b -> + match (a, b) with + | (JSX.Identifier a, JSX.Identifier b) -> identifiers_are_equal a b + | (JSX.MemberExpression a, JSX.MemberExpression b) -> member_expressions_are_equal a b + | (JSX.NamespacedName a, JSX.NamespacedName b) -> namespaced_names_are_equal a b + | _ -> false + + let loc_of_name = function + | JSX.Identifier (loc, _) -> loc + | JSX.NamespacedName (loc, _) -> loc + | JSX.MemberExpression (loc, _) -> loc + + let attribute env = + with_loc + (fun env -> + let name = + match Peek.ith_token ~i:1 env with + | T_COLON -> + let namespaced_name = + with_loc + (fun env -> + let namespace = identifier env in + Expect.token env T_COLON; + let name = identifier env in + { JSX.NamespacedName.namespace; name }) + env + in + JSX.Attribute.NamespacedName namespaced_name + | _ -> + let name = identifier env in + JSX.Attribute.Identifier name + in + let value = + match Peek.token env with + | T_ASSIGN -> + Expect.token env T_ASSIGN; + let leading = Peek.comments env in + let tkn = Peek.token env in + begin + match tkn with + | T_LCURLY -> + let (loc, expression_container) = expression_container env in + JSX.ExpressionContainer.( + match expression_container.expression with + | EmptyExpression -> + error_at env (loc, Parse_error.JSXAttributeValueEmptyExpression) + | _ -> () + ); + Some (JSX.Attribute.ExpressionContainer (loc, expression_container)) + | T_JSX_QUOTE_TEXT (loc, value, raw) as token -> + Expect.token env token; + let trailing = tag_component_trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Some (JSX.Attribute.StringLiteral (loc, { Ast.StringLiteral.value; raw; comments })) + | _ -> + error env Parse_error.InvalidJSXAttributeValue; + let loc = Peek.loc env in + Some + (JSX.Attribute.StringLiteral + (loc, { Ast.StringLiteral.value = ""; raw = ""; comments = None }) + ) + end + | _ -> None + in + { JSX.Attribute.name; value }) + env + + let opening_element = + let rec attributes env acc = + match Peek.token env with + | T_JSX_IDENTIFIER _ -> + let attribute = JSX.Opening.Attribute (attribute env) in + attributes env (attribute :: acc) + | T_LCURLY -> + let attribute = JSX.Opening.SpreadAttribute (spread_attribute env) in + attributes env (attribute :: acc) + | _ -> List.rev acc + in + fun env -> + with_loc + (fun env -> + Expect.token env T_LESS_THAN; + match Peek.token env with + | T_GREATER_THAN -> + Eat.token env; + Ok `Fragment + | T_JSX_IDENTIFIER _ -> + let name = name env in + let targs = + (* Don't attempt to parse type args if what follows is a closing tag. + E.g. in the situation of adding a child `` + Doing so would always be wrong, and having this check improves errors. + *) + if + should_parse_types env + && Peek.token env = T_LESS_THAN + && Peek.ith_token ~i:1 env <> T_DIV + then + Try.or_else env ~fallback:None Expression.call_type_args + else + None + in + let attributes = attributes env [] in + let self_closing = Eat.maybe env T_DIV in + let element = `Element { JSX.Opening.name; targs; self_closing; attributes } in + if Eat.maybe env T_GREATER_THAN then + Ok element + else ( + Expect.error env T_GREATER_THAN; + Error element + ) + | _ -> + (* TODO: also say that we could expect an identifier, or if we're in a JSX child + then suggest escaping the < as `{'<'}` *) + Expect.error env T_GREATER_THAN; + Error `Fragment) + env + + let closing_element env = + with_loc + (fun env -> + Expect.token env T_LESS_THAN; + Expect.token env T_DIV; + match Peek.token env with + | T_GREATER_THAN -> + Eat.token env; + `Fragment + | T_JSX_IDENTIFIER _ -> + let name = name env in + Expect.token_opt env T_GREATER_THAN; + `Element { JSX.Closing.name } + | _ -> + Expect.error env T_GREATER_THAN; + `Fragment) + env + + let child_is_unpaired opening_name = function + | ( _, + JSX.Element + { + JSX.opening_element = (_, { JSX.Opening.name = child_opening_name; _ }); + closing_element = Some (_, { JSX.Closing.name = child_closing_name; _ }); + _; + } + ) -> + (not (names_are_equal child_opening_name child_closing_name)) + && names_are_equal opening_name child_closing_name + | _ -> false + + let rec child ~parent_opening_name env = + match Peek.token env with + | T_LCURLY -> expression_container_or_spread_child env + | T_JSX_CHILD_TEXT (loc, value, raw) as token -> + Expect.token env token; + (loc, JSX.Text { JSX.Text.value; raw }) + | _ -> + (match element_or_fragment ~parent_opening_name env with + | (loc, `Element element) -> (loc, JSX.Element element) + | (loc, `Fragment fragment) -> (loc, JSX.Fragment fragment)) + + and element = + let children_and_closing = + let rec children_and_closing ~parent_opening_name ~opening_name env acc = + let previous_loc = last_loc env in + match (acc, opening_name) with + | (last_child :: rest, Some opening_name) when child_is_unpaired opening_name last_child -> + (* if the last child's opening and closing tags don't match, and the + child's closing tag matches ours, then we're in a situation like + , where opening_name = a and the child has opening + tag c and closing tag a. + + steal the closing tag from the last child, so that has no + closing tag, but ... is properly paired. *) + let (last_child, closing) = + match last_child with + | (loc, JSX.Element ({ JSX.closing_element = Some closing; children; _ } as child)) -> + let (child_loc, _) = children in + let loc = Loc.btwn loc child_loc in + let last_child = (loc, JSX.Element { child with JSX.closing_element = None }) in + (last_child, `Element closing) + | _ -> (last_child, `None) + in + Eat.pop_lex_mode env; + (List.rev (last_child :: rest), previous_loc, closing) + | _ -> + (match Peek.token env with + | T_LESS_THAN -> + Eat.push_lex_mode env Lex_mode.JSX_TAG; + begin + match (Peek.token env, Peek.ith_token ~i:1 env) with + | (T_LESS_THAN, T_EOF) + | (T_LESS_THAN, T_DIV) -> + let closing = + match closing_element env with + | (loc, `Element ec) -> `Element (loc, ec) + | (loc, `Fragment) -> `Fragment loc + in + (* We double pop to avoid going back to childmode and re-lexing the + * lookahead *) + Eat.double_pop_lex_mode env; + (List.rev acc, previous_loc, closing) + | _ -> + let child = + match element ~parent_opening_name:opening_name env with + | (loc, `Element e) -> (loc, JSX.Element e) + | (loc, `Fragment f) -> (loc, JSX.Fragment f) + in + children_and_closing ~parent_opening_name ~opening_name env (child :: acc) + end + | T_EOF -> + error_unexpected env; + (List.rev acc, previous_loc, `None) + | _ -> + let child = child ~parent_opening_name:opening_name env in + children_and_closing ~parent_opening_name ~opening_name env (child :: acc)) + in + fun ~parent_opening_name ~opening_name env -> + let start_loc = Peek.loc env in + let (children, last_child_loc, closing) = + children_and_closing ~parent_opening_name ~opening_name env [] + in + let last_child_loc = + match last_child_loc with + | Some x -> x + | None -> start_loc + in + (* It's a little bit tricky to untangle the parsing of the child elements from the parsing + * of the closing element, so we can't easily use `with_loc` here. Instead, we'll use the + * same logic that `with_loc` uses, but manipulate the locations explicitly. *) + let children_loc = Loc.btwn start_loc last_child_loc in + ((children_loc, children), closing) + in + let rec normalize name = + JSX.( + match name with + | Identifier (_, { Identifier.name; comments = _ }) -> name + | NamespacedName (_, { NamespacedName.namespace; name }) -> + (snd namespace).Identifier.name ^ ":" ^ (snd name).Identifier.name + | MemberExpression (_, { MemberExpression._object; property }) -> + let _object = + match _object with + | MemberExpression.Identifier (_, { Identifier.name = id; _ }) -> id + | MemberExpression.MemberExpression e -> normalize (JSX.MemberExpression e) + in + _object ^ "." ^ (snd property).Identifier.name + ) + in + let is_self_closing = function + | (_, Ok (`Element e)) -> e.JSX.Opening.self_closing + | (_, Ok `Fragment) -> false + | (_, Error _) -> true + in + let name_of_opening = function + | (_, Ok (`Element { JSX.Opening.name; _ })) + | (_, Error (`Element { JSX.Opening.name; _ })) -> + Some name + | (_, Ok `Fragment) + | (_, Error `Fragment) -> + None + in + fun ~parent_opening_name env -> + let leading = Peek.comments env in + let opening_element = opening_element env in + Eat.pop_lex_mode env; + let (children, closing_element) = + if is_self_closing opening_element then + (with_loc (fun _ -> []) env, `None) + else ( + Eat.push_lex_mode env Lex_mode.JSX_CHILD; + let opening_name = name_of_opening opening_element in + children_and_closing ~parent_opening_name ~opening_name env + ) + in + let trailing = Eat.trailing_comments env in + let end_loc = + match closing_element with + | `Element (loc, { JSX.Closing.name }) -> + (match snd opening_element with + | Ok (`Element { JSX.Opening.name = opening_name; _ }) -> + if not (names_are_equal name opening_name) then ( + match parent_opening_name with + | Some parent_opening_name when names_are_equal parent_opening_name name -> + (* the opening and closing tags don't match, but the closing + tag matches the parent's opening tag. the parent is going + to steal the closing tag away from this tag, so error on + the opening tag instead. *) + error_at + env + ( loc_of_name opening_name, + Parse_error.MissingJSXClosingTag (normalize opening_name) + ) + | _ -> + error_at + env + (loc_of_name name, Parse_error.ExpectedJSXClosingTag (normalize opening_name)) + ) + | Ok `Fragment -> + error_at env (loc_of_name name, Parse_error.ExpectedJSXClosingTag "JSX fragment") + | Error _ -> ()); + loc + | `Fragment loc -> + (match snd opening_element with + | Ok (`Element { JSX.Opening.name = opening_name; _ }) -> + error_at env (loc, Parse_error.ExpectedJSXClosingTag (normalize opening_name)) + | Ok `Fragment -> () + | Error _ -> ()); + loc + | _ -> fst opening_element + in + let result = + match opening_element with + | (start_loc, Ok (`Element e)) + | (start_loc, Error (`Element e)) -> + `Element + JSX. + { + opening_element = (start_loc, e); + closing_element = + (match closing_element with + | `Element e -> Some e + | _ -> None); + children; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + | (start_loc, Ok `Fragment) + | (start_loc, Error `Fragment) -> + `Fragment + { + JSX.frag_opening_element = start_loc; + frag_closing_element = + (match closing_element with + | `Fragment loc -> loc + (* the following are parse erros *) + | `Element (loc, _) -> loc + | _ -> end_loc); + frag_children = children; + frag_comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + in + + (Loc.btwn (fst opening_element) end_loc, result) + + and element_or_fragment ~parent_opening_name env = + Eat.push_lex_mode env Lex_mode.JSX_TAG; + element ~parent_opening_name env +end diff --git a/compiler/flow_parser/parser/jsx_parser.mli b/compiler/flow_parser/parser/jsx_parser.mli new file mode 100644 index 00000000000..33d91aa185d --- /dev/null +++ b/compiler/flow_parser/parser/jsx_parser.mli @@ -0,0 +1,8 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module JSX (_ : Parser_common.PARSER) (_ : Parser_common.EXPRESSION) : Parser_common.JSX diff --git a/compiler/flow_parser/parser/lex_env.ml b/compiler/flow_parser/parser/lex_env.ml new file mode 100644 index 00000000000..00c36c42845 --- /dev/null +++ b/compiler/flow_parser/parser/lex_env.ml @@ -0,0 +1,87 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Sedlexing = Flow_sedlexing + +(* bol = Beginning Of Line *) +type bol = { + line: int; + offset: int; +} + +type lex_state = { lex_errors_acc: (Loc.t * Parse_error.t) list } [@@ocaml.unboxed] + +type t = { + lex_source: File_key.t option; + lex_lb: Sedlexing.lexbuf; + lex_bol: bol; + lex_in_comment_syntax: bool; + lex_enable_comment_syntax: bool; + lex_state: lex_state; + lex_last_loc: Loc.t; +} + +let empty_lex_state = { lex_errors_acc = [] } + +(* The lex_last_loc should initially be set to the beginning of the first line, so that + comments on the first line are reported as not being on a new line. *) +let initial_last_loc = + { Loc.source = None; start = { Loc.line = 1; column = 0 }; _end = { Loc.line = 1; column = 0 } } + +let new_lex_env lex_source lex_lb ~enable_types_in_comments = + { + lex_source; + lex_lb; + lex_bol = { line = 1; offset = 0 }; + lex_in_comment_syntax = false; + lex_enable_comment_syntax = enable_types_in_comments; + lex_state = empty_lex_state; + lex_last_loc = initial_last_loc; + } + +(* copy all the mutable things so that we have a distinct lexing environment + that does not interfere with ordinary lexer operations *) +let clone env = + let lex_lb = Sedlexing.lexbuf_clone env.lex_lb in + { env with lex_lb } + +let lexbuf env = env.lex_lb + +let source env = env.lex_source + +let state env = env.lex_state + +let line env = env.lex_bol.line + +let bol_offset env = env.lex_bol.offset + +let is_in_comment_syntax env = env.lex_in_comment_syntax + +let is_comment_syntax_enabled env = env.lex_enable_comment_syntax + +let in_comment_syntax is_in env = + if is_in <> env.lex_in_comment_syntax then + { env with lex_in_comment_syntax = is_in } + else + env + +(* TODO *) +let debug_string_of_lexbuf _lb = "" + +let debug_string_of_lex_env (env : t) = + let source = + match source env with + | None -> "None" + | Some x -> Printf.sprintf "Some %S" (File_key.to_string x) + in + Printf.sprintf + "{\n lex_source = %s\n lex_lb = %s\n lex_in_comment_syntax = %b\n lex_enable_comment_syntax = %b\n lex_state = {errors = (count = %d)}\n}" + source + (debug_string_of_lexbuf env.lex_lb) + (is_in_comment_syntax env) + (is_comment_syntax_enabled env) + (List.length (state env).lex_errors_acc) diff --git a/compiler/flow_parser/parser/lex_result.ml b/compiler/flow_parser/parser/lex_result.ml new file mode 100644 index 00000000000..06ca8998453 --- /dev/null +++ b/compiler/flow_parser/parser/lex_result.ml @@ -0,0 +1,29 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type t = { + lex_token: Token.t; + lex_loc: Loc.t; + lex_errors: (Loc.t * Parse_error.t) list; + lex_comments: Loc.t Flow_ast.Comment.t list; +} + +let token result = result.lex_token + +let loc result = result.lex_loc + +let comments result = result.lex_comments + +let errors result = result.lex_errors + +let debug_string_of_lex_result lex_result = + Printf.sprintf + "{\n lex_token = %s\n lex_value = %S\n lex_errors = (length = %d)\n lex_comments = (length = %d)\n}" + (Token.token_to_string lex_result.lex_token) + (Token.value_of_token lex_result.lex_token) + (List.length lex_result.lex_errors) + (List.length lex_result.lex_comments) diff --git a/compiler/flow_parser/parser/loc.ml b/compiler/flow_parser/parser/loc.ml new file mode 100644 index 00000000000..e6786bd4efa --- /dev/null +++ b/compiler/flow_parser/parser/loc.ml @@ -0,0 +1,188 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* line numbers are 1-indexed; column numbers are 0-indexed *) +type position = { + line: int; + column: int; +} +[@@deriving eq, show] + +(* start is inclusive; end is exclusive *) +(* If you are modifying this record, go look at ALoc.ml and make sure you understand the + * representation there. *) +type t = { + source: File_key.t option; + start: position; + _end: position; +} +[@@deriving show] + +let none = { source = None; start = { line = 0; column = 0 }; _end = { line = 0; column = 0 } } + +let is_none (x : t) = + x == none + || + match x with + | { source = None; start = { line = 0; column = 0 }; _end = { line = 0; column = 0 } } -> true + | _ -> false + +let is_none_ignore_source (x : t) = + x == none + || + match x with + | { source = _; start = { line = 0; column = 0 }; _end = { line = 0; column = 0 } } -> true + | _ -> false + +let btwn loc1 loc2 = { source = loc1.source; start = loc1.start; _end = loc2._end } + +(* Returns the position immediately before the start of the given loc. If the + given loc is at the beginning of a line, return the position of the first + char on the same line. *) +let char_before loc = + let start = + let { line; column } = loc.start in + let column = + if column > 0 then + column - 1 + else + column + in + { line; column } + in + let _end = loc.start in + { loc with start; _end } + +(* Returns the location of the first character in the given loc. Not accurate if the + * first line is a newline character, but is still consistent with loc orderings. *) +let first_char loc = + let start = loc.start in + let _end = { start with column = start.column + 1 } in + { loc with _end } + +let pos_cmp a b = + let k = a.line - b.line in + if k = 0 then + a.column - b.column + else + k + +(** + * If `a` spans (completely contains) `b`, then returns 0. + * If `b` starts before `a` (even if it ends inside), returns < 0. + * If `b` ends after `a` (even if it starts inside), returns > 0. + *) +let span_compare a b = + let k = File_key.compare_opt a.source b.source in + if k = 0 then + let k = pos_cmp a.start b.start in + if k <= 0 then + let k = pos_cmp a._end b._end in + if k >= 0 then + 0 + else + -1 + else + 1 + else + k + +(** [contains loc1 loc2] returns true if [loc1] entirely overlaps [loc2] *) +let contains loc1 loc2 = span_compare loc1 loc2 = 0 + +(** [intersects loc1 loc2] returns true if [loc1] intersects [loc2] at all *) +let intersects loc1 loc2 = + File_key.compare_opt loc1.source loc2.source = 0 + && not (pos_cmp loc1._end loc2.start < 0 || pos_cmp loc1.start loc2._end > 0) + +(** [lines_intersect loc1 loc2] returns true if [loc1] and [loc2] cover any part of + the same line, even if they don't actually intersect. + + For example, if [loc1] ends and then [loc2] begins later on the same line, + [intersects loc1 loc2] is false, but [lines_intersect loc1 loc2] is true. *) +let lines_intersect loc1 loc2 = + File_key.compare_opt loc1.source loc2.source = 0 + && not (loc1._end.line < loc2.start.line || loc1.start.line > loc2._end.line) + +let compare_ignore_source loc1 loc2 = + match pos_cmp loc1.start loc2.start with + | 0 -> pos_cmp loc1._end loc2._end + | k -> k + +let compare loc1 loc2 = + let k = File_key.compare_opt loc1.source loc2.source in + if k = 0 then + compare_ignore_source loc1 loc2 + else + k + +let equal loc1 loc2 = compare loc1 loc2 = 0 + +(** + * This is mostly useful for debugging purposes. + * Please don't dead-code delete this! + *) +let debug_to_string ?(include_source = false) loc = + let source = + if include_source then + Printf.sprintf + "%S: " + (match loc.source with + | Some src -> File_key.to_string src + | None -> "") + else + "" + in + let pos = + Printf.sprintf + "(%d, %d) to (%d, %d)" + loc.start.line + loc.start.column + loc._end.line + loc._end.column + in + source ^ pos + +let to_string_no_source loc = + let line = loc.start.line in + let start = loc.start.column + 1 in + let end_ = loc._end.column in + if line <= 0 then + "0:0" + else if line = loc._end.line && start = end_ then + Printf.sprintf "%d:%d" line start + else if line != loc._end.line then + Printf.sprintf "%d:%d,%d:%d" line start loc._end.line end_ + else + Printf.sprintf "%d:%d-%d" line start end_ + +let start_pos_to_string_for_vscode_loc_uri_fragment loc = + let line = loc.start.line in + let start = loc.start.column + 1 in + let (line, start) = + if line <= 0 then + (0, 0) + else + (line, start) + in + Printf.sprintf "#L%d,%d" line start + +let mk_loc ?source (start_line, start_column) (end_line, end_column) = + { + source; + start = { line = start_line; column = start_column }; + _end = { line = end_line; column = end_column }; + } + +let source loc = loc.source + +(** Produces a zero-width Loc.t, where start = end *) +let cursor source line column = { source; start = { line; column }; _end = { line; column } } + +let start_loc loc = { loc with _end = loc.start } + +let end_loc loc = { loc with start = loc._end } diff --git a/compiler/flow_parser/parser/loc.mli b/compiler/flow_parser/parser/loc.mli new file mode 100644 index 00000000000..1a1a4419614 --- /dev/null +++ b/compiler/flow_parser/parser/loc.mli @@ -0,0 +1,76 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type position = { + line: int; + column: int; +} +[@@deriving eq, show] + +type t = { + source: File_key.t option; + start: position; + _end: position; +} +[@@deriving show] + +val none : t + +val is_none : t -> bool + +val is_none_ignore_source : t -> bool + +val btwn : t -> t -> t + +val char_before : t -> t + +val first_char : t -> t + +(** [contains loc1 loc2] returns true if [loc1] entirely overlaps [loc2] *) +val contains : t -> t -> bool + +(** [intersects loc1 loc2] returns true if [loc1] intersects [loc2] at all *) +val intersects : t -> t -> bool + +(** [lines_intersect loc1 loc2] returns true if [loc1] and [loc2] cover any part of + the same line, even if they don't actually intersect. + + For example, if [loc1] ends and then [loc2] begins later on the same line, + [intersects loc1 loc2] is false, but [lines_intersect loc1 loc2] is true. *) +val lines_intersect : t -> t -> bool + +val pos_cmp : position -> position -> int + +val span_compare : t -> t -> int + +val compare_ignore_source : t -> t -> int + +val compare : t -> t -> int + +val equal : t -> t -> bool + +val debug_to_string : ?include_source:bool -> t -> string + +(* Relatively compact; suitable for use as a unique string identifier *) +val to_string_no_source : t -> string + +(* In VSCode, clicking file://foo.js#L1,1 will jump the specific line and column. + * This function generates the fragment part (starting with #). *) +val start_pos_to_string_for_vscode_loc_uri_fragment : t -> string + +val mk_loc : ?source:File_key.t -> int * int -> int * int -> t + +val source : t -> File_key.t option + +(** Produces a zero-width Loc.t, where start = end *) +val cursor : File_key.t option -> int -> int -> t + +(* Produces a location at the start of the input location *) +val start_loc : t -> t + +(* Produces a location at the end of the input location *) +val end_loc : t -> t diff --git a/compiler/flow_parser/parser/match_pattern_parser.ml b/compiler/flow_parser/parser/match_pattern_parser.ml new file mode 100644 index 00000000000..4e446231ac3 --- /dev/null +++ b/compiler/flow_parser/parser/match_pattern_parser.ml @@ -0,0 +1,439 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Token +open Parser_env +open Parser_common +open Flow_ast.MatchPattern +module Ast = Flow_ast + +module Match_pattern (Parse : PARSER) : Parser_common.MATCH_PATTERN = struct + let rec match_pattern env = + let start_loc = Peek.loc env in + ignore @@ Eat.maybe env T_BIT_OR; + let pattern = subpattern env in + let pattern = + match Peek.token env with + | T_BIT_OR -> + let rec or_patterns env acc = + match Peek.token env with + | T_BIT_OR -> + Eat.token env; + let acc = subpattern env :: acc in + or_patterns env acc + | _ -> List.rev acc + in + let (or_loc, or_pattern) = + with_loc + ~start_loc + (fun env -> + let patterns = or_patterns env [pattern] in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~trailing () in + { OrPattern.patterns; comments }) + env + in + (or_loc, OrPattern or_pattern) + | _ -> pattern + in + match Peek.token env with + | T_IDENTIFIER { raw = "as"; _ } -> + let (as_loc, as_pattern) = + with_loc + ~start_loc + (fun env -> + Eat.token env; + let target = + match Peek.token env with + | T_CONST -> + let (loc, binding) = binding_pattern env ~kind:Ast.Variable.Const in + AsPattern.Binding (loc, binding) + | T_LET -> + let (loc, binding) = binding_pattern env ~kind:Ast.Variable.Let in + AsPattern.Binding (loc, binding) + | T_VAR -> + let (loc, binding) = binding_pattern env ~kind:Ast.Variable.Var in + AsPattern.Binding (loc, binding) + | _ -> AsPattern.Identifier (Parse.identifier env) + in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~trailing () in + { AsPattern.pattern; target; comments }) + env + in + (as_loc, AsPattern as_pattern) + | _ -> pattern + + and subpattern env = + match Peek.token env with + | T_IDENTIFIER { raw = "_"; _ } -> + let leading = Peek.comments env in + let loc = Peek.loc env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, WildcardPattern comments) + | T_LPAREN -> + let leading = Peek.comments env in + Expect.token env T_LPAREN; + let pattern = match_pattern env in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + add_comments ~leading ~trailing pattern + | T_NUMBER { kind; raw } -> + let leading = Peek.comments env in + let loc = Peek.loc env in + let value = Parse.number env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, NumberPattern { Ast.NumberLiteral.value; raw; comments }) + | T_BIGINT { kind; raw } -> + let leading = Peek.comments env in + let loc = Peek.loc env in + let value = Parse.bigint env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, BigIntPattern { Ast.BigIntLiteral.value; raw; comments }) + | T_STRING (loc, value, raw, octal) -> + let leading = Peek.comments env in + if octal then strict_error env Parse_error.StrictOctalLiteral; + Eat.token env; + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, StringPattern { Ast.StringLiteral.value; raw; comments }) + | (T_TRUE | T_FALSE) as token -> + let leading = Peek.comments env in + let loc = Peek.loc env in + Eat.token env; + let value = token = T_TRUE in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, BooleanPattern { Ast.BooleanLiteral.value; comments }) + | T_NULL -> + let leading = Peek.comments env in + let loc = Peek.loc env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, NullPattern comments) + | T_PLUS -> unary_pattern env ~operator:UnaryPattern.Plus + | T_MINUS -> unary_pattern env ~operator:UnaryPattern.Minus + | T_CONST -> + let (loc, binding) = binding_pattern env ~kind:Ast.Variable.Const in + (loc, BindingPattern binding) + | T_LET -> + let (loc, binding) = binding_pattern env ~kind:Ast.Variable.Let in + (loc, BindingPattern binding) + | T_VAR -> + let (loc, binding) = binding_pattern env ~kind:Ast.Variable.Var in + (loc, BindingPattern binding) + | T_LCURLY -> object_pattern env + | T_LBRACKET -> array_pattern env + | _ when Peek.is_identifier env -> + let start_loc = Peek.loc env in + let id = Parse.identifier env in + let rec member acc = + match Peek.token env with + | T_PERIOD -> + let mem = + with_loc + ~start_loc + (fun env -> + Eat.token env; + let property = MemberPattern.PropertyIdentifier (identifier_name env) in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~trailing () in + { MemberPattern.base = acc; property; comments }) + env + in + member (MemberPattern.BaseMember mem) + | T_LBRACKET -> + let mem = + with_loc + ~start_loc + (fun env -> + Expect.token env T_LBRACKET; + let leading = Peek.comments env in + let property = + match Peek.token env with + | T_STRING (loc, value, raw, octal) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + Expect.token env (T_STRING (loc, value, raw, octal)); + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + MemberPattern.PropertyString (loc, { Ast.StringLiteral.value; raw; comments }) + | T_NUMBER { kind; raw } -> + let loc = Peek.loc env in + let value = Parse.number env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + MemberPattern.PropertyNumber (loc, { Ast.NumberLiteral.value; raw; comments }) + | T_BIGINT { kind; raw } -> + let loc = Peek.loc env in + let value = Parse.bigint env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + MemberPattern.PropertyBigInt (loc, { Ast.BigIntLiteral.value; raw; comments }) + | _ -> + error_unexpected ~expected:"a numeric or string literal" env; + let loc = Peek.loc env in + MemberPattern.PropertyString + (loc, { Ast.StringLiteral.value = ""; raw = "\"\""; comments = None }) + in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~trailing () in + { MemberPattern.base = acc; property; comments }) + env + in + member (MemberPattern.BaseMember mem) + | _ -> + (match acc with + | MemberPattern.BaseIdentifier ((loc, _) as id) -> (loc, IdentifierPattern id) + | MemberPattern.BaseMember (loc, member) -> (loc, MemberPattern (loc, member))) + in + member (MemberPattern.BaseIdentifier id) + | t -> + let leading = Peek.comments env in + let loc = Peek.loc env in + error_unexpected env; + (* Let's get rid of the bad token *) + (match t with + | T_ERROR _ -> Eat.token env + | _ -> ()); + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing:[] () in + (loc, WildcardPattern comments) + + and unary_pattern env ~operator = + with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + let argument = + match Peek.token env with + | T_NUMBER { kind; raw } -> + let leading = Peek.comments env in + let loc = Peek.loc env in + let value = Parse.number env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, UnaryPattern.NumberLiteral { Ast.NumberLiteral.value; raw; comments }) + | T_BIGINT { kind; raw } -> + let leading = Peek.comments env in + let loc = Peek.loc env in + let value = Parse.bigint env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, UnaryPattern.BigIntLiteral { Ast.BigIntLiteral.value; raw; comments }) + | _ -> + let loc = Peek.loc env in + error_unexpected ~expected:"a number literal" env; + ( loc, + UnaryPattern.NumberLiteral + { Ast.NumberLiteral.value = 0.; raw = "0"; comments = None } + ) + in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + UnaryPattern { UnaryPattern.operator; argument; comments }) + env + + and binding_pattern env ~kind = + with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + let id = Parse.identifier ~restricted_error:Parse_error.StrictVarName env in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + { BindingPattern.kind; id; comments }) + env + + and object_pattern env = + let property_key env = + let open ObjectPattern.Property in + let leading = Peek.comments env in + match Peek.token env with + | T_STRING (loc, value, raw, octal) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + Expect.token env (T_STRING (loc, value, raw, octal)); + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + StringLiteral (loc, { Ast.StringLiteral.value; raw; comments }) + | T_NUMBER { kind; raw } -> + let loc = Peek.loc env in + let value = Parse.number env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + NumberLiteral (loc, { Ast.NumberLiteral.value; raw; comments }) + | T_BIGINT { kind; raw } -> + let loc = Peek.loc env in + let value = Parse.bigint env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + BigIntLiteral (loc, { Ast.BigIntLiteral.value; raw; comments }) + | _ -> + let id = identifier_name env in + Identifier id + in + let property = + with_loc (fun env -> + let leading = Peek.comments env in + let shorthand_prop (loc, binding) = + let { BindingPattern.id = (_, id); _ } = binding in + let key = ObjectPattern.Property.Identifier (loc, id) in + let pattern = (loc, BindingPattern binding) in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + ObjectPattern.Property.Valid + { ObjectPattern.Property.key; pattern; shorthand = true; comments } + in + match Peek.token env with + | T_CONST -> shorthand_prop (binding_pattern env ~kind:Ast.Variable.Const) + | T_LET -> shorthand_prop (binding_pattern env ~kind:Ast.Variable.Let) + | T_VAR -> shorthand_prop (binding_pattern env ~kind:Ast.Variable.Var) + | _ + when Peek.is_identifier env + && + match Peek.ith_token ~i:1 env with + | T_COMMA + | T_RCURLY -> + true + | _ -> false -> + ObjectPattern.Property.InvalidShorthand (identifier_name env) + | _ -> + let key = property_key env in + Expect.token env T_COLON; + let pattern = match_pattern env in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + ObjectPattern.Property.Valid + { ObjectPattern.Property.key; pattern; shorthand = false; comments } + ) + in + let rec properties env acc = + match Peek.token env with + | T_EOF + | T_RCURLY -> + (List.rev acc, None) + | T_ELLIPSIS -> + let rest = rest_pattern env in + if Peek.token env = T_COMMA then + error_at env (Peek.loc env, Parse_error.MatchNonLastRest `Object); + (List.rev acc, Some rest) + | _ -> + let prop = property env in + if not (Peek.token env = T_RCURLY) then Expect.token env T_COMMA; + properties env (prop :: acc) + in + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LCURLY; + let (properties, rest) = properties env [] in + let internal = Peek.comments env in + Expect.token env T_RCURLY; + let trailing = Eat.trailing_comments env in + ObjectPattern + { + ObjectPattern.properties; + rest; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }) + env + + and array_pattern env = + let rec elements env ~start_loc acc = + match Peek.token env with + | T_EOF + | T_RBRACKET -> + (List.rev acc, None) + | T_ELLIPSIS -> + let rest = rest_pattern env in + if Peek.token env = T_COMMA then + error_at env (Peek.loc env, Parse_error.MatchNonLastRest `Array); + (List.rev acc, Some rest) + | _ -> + let pattern = match_pattern env in + let index = Loc.btwn start_loc (Peek.loc env) in + if Peek.token env <> T_RBRACKET then Expect.token env T_COMMA; + let element = { ArrayPattern.Element.index; pattern } in + elements env ~start_loc (element :: acc) + in + with_loc + (fun env -> + let leading = Peek.comments env in + let start_loc = Peek.loc env in + Expect.token env T_LBRACKET; + let (elements, rest) = elements env ~start_loc [] in + let internal = Peek.comments env in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + let comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal () + in + ArrayPattern { ArrayPattern.elements; rest; comments }) + env + + and rest_pattern env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_ELLIPSIS; + let argument = + match Peek.token env with + | T_CONST -> Some (binding_pattern env ~kind:Ast.Variable.Const) + | T_LET -> Some (binding_pattern env ~kind:Ast.Variable.Let) + | T_VAR -> Some (binding_pattern env ~kind:Ast.Variable.Var) + | _ -> None + in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + { RestPattern.argument; comments }) + env + + and add_comments ?(leading = []) ?(trailing = []) (loc, pattern) = + let merge_comments inner = + Flow_ast_utils.merge_comments + ~inner + ~outer:(Flow_ast_utils.mk_comments_opt ~leading ~trailing ()) + in + let merge_comments_with_internal inner = + Flow_ast_utils.merge_comments_with_internal + ~inner + ~outer:(Flow_ast_utils.mk_comments_opt ~leading ~trailing ()) + in + ( loc, + match pattern with + | WildcardPattern comments -> WildcardPattern (merge_comments comments) + | NumberPattern ({ Ast.NumberLiteral.comments; _ } as p) -> + NumberPattern { p with Ast.NumberLiteral.comments = merge_comments comments } + | BigIntPattern ({ Ast.BigIntLiteral.comments; _ } as p) -> + BigIntPattern { p with Ast.BigIntLiteral.comments = merge_comments comments } + | StringPattern ({ Ast.StringLiteral.comments; _ } as p) -> + StringPattern { p with Ast.StringLiteral.comments = merge_comments comments } + | BooleanPattern ({ Ast.BooleanLiteral.comments; _ } as p) -> + BooleanPattern { p with Ast.BooleanLiteral.comments = merge_comments comments } + | NullPattern comments -> NullPattern (merge_comments comments) + | UnaryPattern ({ UnaryPattern.comments; _ } as p) -> + UnaryPattern { p with UnaryPattern.comments = merge_comments comments } + | BindingPattern ({ BindingPattern.comments; _ } as p) -> + BindingPattern { p with BindingPattern.comments = merge_comments comments } + | IdentifierPattern (id_loc, ({ Ast.Identifier.comments; _ } as p)) -> + IdentifierPattern (id_loc, { p with Ast.Identifier.comments = merge_comments comments }) + | MemberPattern (loc, ({ MemberPattern.comments; _ } as p)) -> + MemberPattern (loc, { p with MemberPattern.comments = merge_comments comments }) + | ObjectPattern ({ ObjectPattern.comments; _ } as p) -> + ObjectPattern { p with ObjectPattern.comments = merge_comments_with_internal comments } + | ArrayPattern ({ ArrayPattern.comments; _ } as p) -> + ArrayPattern { p with ArrayPattern.comments = merge_comments_with_internal comments } + | OrPattern ({ OrPattern.comments; _ } as p) -> + OrPattern { p with OrPattern.comments = merge_comments comments } + | AsPattern ({ AsPattern.comments; _ } as p) -> + AsPattern { p with AsPattern.comments = merge_comments comments } + ) +end diff --git a/compiler/flow_parser/parser/object_parser.ml b/compiler/flow_parser/parser/object_parser.ml new file mode 100644 index 00000000000..78b6ef4d728 --- /dev/null +++ b/compiler/flow_parser/parser/object_parser.ml @@ -0,0 +1,1147 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Token +open Parser_env +open Flow_ast +module SMap = Flow_map.Make (String) +open Parser_common +open Comment_attachment + +(* A module for parsing various object related things, like object literals + * and classes *) + +module Object + (Parse : Parser_common.PARSER) + (Type : Parser_common.TYPE) + (Declaration : Parser_common.DECLARATION) + (Expression : Parser_common.EXPRESSION) + (Pattern_cover : Parser_common.COVER) : Parser_common.OBJECT = struct + let decorator_list = + let expression env = + let expression = Expression.left_hand_side env in + let { remove_trailing; _ } = + if Peek.is_line_terminator env then + trailing_and_remover_after_last_line env + else + trailing_and_remover_after_last_loc env + in + remove_trailing expression (fun remover expression -> remover#expression expression) + in + let decorator env = + let leading = Peek.comments env in + Eat.token env; + { + Ast.Class.Decorator.expression = expression env; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + in + let rec decorator_list_helper env decorators = + match Peek.token env with + | T_AT -> decorator_list_helper env (with_loc decorator env :: decorators) + | _ -> decorators + in + fun env -> + if (parse_options env).esproposal_decorators then + List.rev (decorator_list_helper env []) + else + [] + + let key ?(class_body = false) env = + let open Ast.Expression.Object.Property in + let leading = Peek.comments env in + let tkn = Peek.token env in + match tkn with + | T_STRING (loc, value, raw, octal) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + Expect.token env (T_STRING (loc, value, raw, octal)); + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, StringLiteral (loc, { Ast.StringLiteral.value; raw; comments })) + | T_NUMBER { kind; raw } -> + let loc = Peek.loc env in + let value = Expression.number env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, NumberLiteral (loc, { Ast.NumberLiteral.value; raw; comments })) + | T_BIGINT { kind; raw } -> + let loc = Peek.loc env in + let value = Expression.bigint env kind raw in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, BigIntLiteral (loc, { Ast.BigIntLiteral.value; raw; comments })) + | T_LBRACKET -> + let (loc, key) = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LBRACKET; + let expr = Parse.assignment (env |> with_no_in false) in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + { + ComputedKey.expression = expr; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + in + (loc, Ast.Expression.Object.Property.Computed (loc, key)) + | T_POUND when class_body -> + let ((loc, { PrivateName.name; _ }) as id) = private_identifier env in + add_declared_private env name; + (loc, PrivateName id) + | T_POUND -> + let (loc, id) = + with_loc + (fun env -> + Eat.token env; + Identifier (identifier_name env)) + env + in + error_at env (loc, Parse_error.PrivateNotInClass); + (loc, id) + | _ -> + let ((loc, _) as id) = identifier_name env in + (loc, Identifier id) + + let getter_or_setter env ~in_class_body is_getter = + (* this is a getter or setter, it cannot be async *) + let async = false in + let (generator, leading) = Declaration.generator env in + let (key_loc, key) = key ~class_body:in_class_body env in + let key = object_key_remove_trailing env key in + let value = + with_loc + (fun env -> + (* #sec-function-definitions-static-semantics-early-errors *) + let env = env |> with_allow_super Super_prop in + let (sig_loc, (tparams, params, return)) = + with_loc + (fun env -> + (* It's not clear how type params on getters & setters would make sense + * in Flow's type system. Since this is a Flow syntax extension, we might + * as well disallow it until we need it *) + let tparams = None in + let params = + let params = Declaration.function_params ~await:false ~yield:false env in + if Peek.token env = T_COLON then + params + else + function_params_remove_trailing env params + in + begin + match (is_getter, params) with + | (true, (_, { Ast.Function.Params.this_ = Some _; _ })) -> + error_at env (key_loc, Parse_error.GetterMayNotHaveThisParam) + | (false, (_, { Ast.Function.Params.this_ = Some _; _ })) -> + error_at env (key_loc, Parse_error.SetterMayNotHaveThisParam) + | ( true, + ( _, + { Ast.Function.Params.params = []; rest = None; this_ = None; comments = _ } + ) + ) -> + () + | (false, (_, { Ast.Function.Params.rest = Some _; _ })) -> + (* rest params don't make sense on a setter *) + error_at env (key_loc, Parse_error.SetterArity) + | ( false, + ( _, + { + Ast.Function.Params.params = [_]; + rest = None; + this_ = None; + comments = _; + } + ) + ) -> + () + | (true, _) -> error_at env (key_loc, Parse_error.GetterArity) + | (false, _) -> error_at env (key_loc, Parse_error.SetterArity) + end; + let return = + return_annotation_remove_trailing env (Type.function_return_annotation_opt env) + in + (tparams, params, return)) + env + in + let simple_params = is_simple_parameter_list params in + let (body, contains_use_strict) = + Declaration.function_body env ~async ~generator ~expression:false ~simple_params + in + Declaration.strict_function_post_check env ~contains_use_strict None params; + { + Function.id = None; + params; + body; + generator; + async; + effect_ = Function.Arbitrary; + predicate = None; + (* setters/getter are not predicates *) + return; + tparams; + sig_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + (key, value) + + let _initializer = + let parse_assignment_cover env = + match Expression.assignment_cover env with + | Cover_expr expr -> (expr, Pattern_cover.empty_errors) + | Cover_patt (expr, errs) -> (expr, errs) + in + let get env start_loc leading = + let (loc, (key, value)) = + with_loc ~start_loc (fun env -> getter_or_setter env ~in_class_body:false true) env + in + let open Ast.Expression.Object in + Property + (loc, Property.Get { key; value; comments = Flow_ast_utils.mk_comments_opt ~leading () }) + in + let set env start_loc leading = + let (loc, (key, value)) = + with_loc ~start_loc (fun env -> getter_or_setter env ~in_class_body:false false) env + in + let open Ast.Expression.Object in + Property + (loc, Property.Set { key; value; comments = Flow_ast_utils.mk_comments_opt ~leading () }) + in + (* #prod-PropertyDefinition *) + let init = + let open Ast.Expression.Object.Property in + (* #prod-IdentifierReference *) + let parse_shorthand env key = + match key with + | StringLiteral (loc, lit) -> + error_at env (loc, Parse_error.LiteralShorthandProperty); + (loc, Ast.Expression.StringLiteral lit) + | NumberLiteral (loc, lit) -> + error_at env (loc, Parse_error.LiteralShorthandProperty); + (loc, Ast.Expression.NumberLiteral lit) + | BigIntLiteral (loc, lit) -> + error_at env (loc, Parse_error.LiteralShorthandProperty); + (loc, Ast.Expression.BigIntLiteral lit) + | Identifier ((loc, { Identifier.name; comments = _ }) as id) -> + (* #sec-identifiers-static-semantics-early-errors *) + if is_reserved name then + (* it is a syntax error if `name` is a reserved word other than await or yield *) + error_at env (loc, Parse_error.UnexpectedReserved) + else if is_strict_reserved name then + (* it is a syntax error if `name` is a strict reserved word, in strict mode *) + strict_error_at env (loc, Parse_error.StrictReservedWord); + (loc, Ast.Expression.Identifier id) + | PrivateName _ -> failwith "Internal Error: private name found in object props" + | Computed (_, { ComputedKey.expression = expr; comments = _ }) -> + error_at env (fst expr, Parse_error.ComputedShorthandProperty); + expr + in + (* #prod-MethodDefinition *) + let parse_method ~async ~generator ~leading = + with_loc (fun env -> + (* #sec-function-definitions-static-semantics-early-errors *) + let env = env |> with_allow_super Super_prop in + let (sig_loc, (tparams, params, return)) = + with_loc + (fun env -> + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.FunctionTP + (Type.type_params env) + in + let params = + let params = Declaration.function_params ~await:async ~yield:generator env in + if Peek.token env = T_COLON then + params + else + function_params_remove_trailing env params + in + let return = + return_annotation_remove_trailing env (Type.function_return_annotation_opt env) + in + (tparams, params, return)) + env + in + let simple_params = is_simple_parameter_list params in + let (body, contains_use_strict) = + Declaration.function_body env ~async ~generator ~expression:false ~simple_params + in + Declaration.strict_function_post_check env ~contains_use_strict None params; + { + Function.id = None; + params; + body; + generator; + effect_ = Function.Arbitrary; + async; + (* TODO: add support for object method predicates *) + predicate = None; + return; + tparams; + sig_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + in + (* PropertyName `:` AssignmentExpression *) + let parse_value env = + Expect.token env T_COLON; + parse_assignment_cover env + in + (* #prod-CoverInitializedName *) + let parse_assignment_pattern ~key env = + let open Ast.Expression.Object in + match key with + | Property.Identifier id -> + let assignment_loc = Peek.loc env in + let ast = + with_loc + ~start_loc:(fst id) + (fun env -> + let leading = Peek.comments env in + Expect.token env T_ASSIGN; + let trailing = Eat.trailing_comments env in + let left = Parse.pattern_from_expr env (fst id, Ast.Expression.Identifier id) in + let right = Parse.assignment env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Ast.Expression.Assignment + { Ast.Expression.Assignment.operator = None; left; right; comments }) + env + in + let errs = + { + if_expr = [(assignment_loc, Parse_error.Unexpected (Token.quote_token_value "="))]; + if_patt = []; + } + in + (ast, errs) + | Property.StringLiteral _ + | Property.NumberLiteral _ + | Property.BigIntLiteral _ + | Property.PrivateName _ + | Property.Computed _ -> + parse_value env + in + let parse_init ~key ~async ~generator ~leading env = + if async || generator then + let key = object_key_remove_trailing env key in + (* the `async` and `*` modifiers are only valid on methods *) + let value = parse_method env ~async ~generator ~leading in + let prop = Method { key; value } in + (prop, Pattern_cover.empty_errors) + else + match Peek.token env with + | T_RCURLY + | T_COMMA -> + let value = parse_shorthand env key in + let prop = Init { key; value; shorthand = true } in + (prop, Pattern_cover.empty_errors) + | T_LESS_THAN + | T_LPAREN -> + let key = object_key_remove_trailing env key in + let value = parse_method env ~async ~generator ~leading in + let prop = Method { key; value } in + (prop, Pattern_cover.empty_errors) + | T_ASSIGN -> + let (value, errs) = parse_assignment_pattern ~key env in + let prop = Init { key; value; shorthand = true } in + (prop, errs) + | T_COLON -> + let (value, errs) = parse_value env in + let prop = Init { key; value; shorthand = false } in + (prop, errs) + | _ -> + (* error. we recover by treating it as a shorthand property so as to not + consume any more tokens and make the error worse. we don't error here + because we'll expect a comma before the next token. *) + let value = parse_shorthand env key in + let prop = Init { key; value; shorthand = true } in + (prop, Pattern_cover.empty_errors) + in + fun env start_loc key async generator leading -> + let (loc, (prop, errs)) = + with_loc ~start_loc (parse_init ~key ~async ~generator ~leading) env + in + (Ast.Expression.Object.Property (loc, prop), errs) + in + let property env = + let open Ast.Expression.Object in + if Peek.token env = T_ELLIPSIS then + (* Spread property *) + let leading = Peek.comments env in + let (loc, (argument, errs)) = + with_loc + (fun env -> + Expect.token env T_ELLIPSIS; + parse_assignment_cover env) + env + in + ( SpreadProperty + (loc, { SpreadProperty.argument; comments = Flow_ast_utils.mk_comments_opt ~leading () }), + errs + ) + else + let start_loc = Peek.loc env in + let (async, leading_async) = + match Peek.ith_token ~i:1 env with + | T_ASSIGN + (* { async = true } (destructuring) *) + | T_COLON + (* { async: true } *) + | T_LESS_THAN + (* { async() {} } *) + | T_LPAREN + (* { async() {} } *) + | T_COMMA + (* { async, other, shorthand } *) + | T_RCURLY (* { async } *) -> + (false, []) + | _ -> Declaration.async env + in + let (generator, leading_generator) = Declaration.generator env in + let leading = leading_async @ leading_generator in + match (async, generator, Peek.token env) with + | (false, false, T_IDENTIFIER { raw = "get"; _ }) -> + let leading = Peek.comments env in + let (_, key) = key env in + begin + match Peek.token env with + | T_ASSIGN + | T_COLON + | T_LESS_THAN + | T_LPAREN + | T_COMMA + | T_RCURLY -> + init env start_loc key false false [] + | _ -> + ignore (Comment_attachment.object_key_remove_trailing env key); + (get env start_loc leading, Pattern_cover.empty_errors) + end + | (false, false, T_IDENTIFIER { raw = "set"; _ }) -> + let leading = Peek.comments env in + let (_, key) = key env in + begin + match Peek.token env with + | T_ASSIGN + | T_COLON + | T_LESS_THAN + | T_LPAREN + | T_COMMA + | T_RCURLY -> + init env start_loc key false false [] + | _ -> + ignore (Comment_attachment.object_key_remove_trailing env key); + (set env start_loc leading, Pattern_cover.empty_errors) + end + | (async, generator, _) -> + let (_, key) = key env in + init env start_loc key async generator leading + in + let rec properties env ~rest_trailing_comma (props, errs) = + match Peek.token env with + | T_EOF + | T_RCURLY -> + let errs = + match rest_trailing_comma with + | Some loc -> + { errs with if_patt = (loc, Parse_error.TrailingCommaAfterRestElement) :: errs.if_patt } + | None -> errs + in + (List.rev props, Pattern_cover.rev_errors errs) + | _ -> + let (prop, new_errs) = property env in + let rest_trailing_comma = + match prop with + | Ast.Expression.Object.SpreadProperty _ when Peek.token env = T_COMMA -> + Some (Peek.loc env) + | _ -> None + in + let errs = Pattern_cover.rev_append_errors new_errs errs in + let errs = + match Peek.token env with + | T_RCURLY + | T_EOF -> + errs + | T_COMMA -> + Eat.token env; + errs + | _ -> + (* we could use [Expect.error env T_COMMA], but we're in a weird + cover grammar situation where we're storing errors in + [Pattern_cover]. if we used [Expect.error], the errors would + end up out of order. *) + let err = Expect.get_error env T_COMMA in + (* if the unexpected token is a semicolon, consume it to aid + recovery. using a semicolon instead of a comma is a common + mistake. *) + let _ = Eat.maybe env T_SEMICOLON in + Pattern_cover.cons_error err errs + in + properties env ~rest_trailing_comma (prop :: props, errs) + in + fun env -> + let (loc, (expr, errs)) = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LCURLY; + let (props, errs) = + properties env ~rest_trailing_comma:None ([], Pattern_cover.empty_errors) + in + let internal = Peek.comments env in + Expect.token env T_RCURLY; + let trailing = Eat.trailing_comments env in + ( { + Ast.Expression.Object.properties = props; + comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }, + errs + )) + env + in + (loc, expr, errs) + + let check_property_name env loc name static = + if String.equal name "constructor" || (String.equal name "prototype" && static) then + error_at + env + (loc, Parse_error.InvalidClassMemberName { name; static; method_ = false; private_ = false }) + + let check_private_names + env seen_names private_name (kind : [ `Method | `Field | `Getter | `Setter ]) = + let (loc, { PrivateName.name; comments = _ }) = private_name in + if String.equal name "constructor" then + let () = + error_at + env + ( loc, + Parse_error.InvalidClassMemberName + { name; static = false; method_ = kind = `Method; private_ = true } + ) + in + seen_names + else + match SMap.find_opt name seen_names with + | Some seen -> + begin + match (kind, seen) with + | (`Getter, `Setter) + | (`Setter, `Getter) -> + (* one getter and one setter are allowed as long as it's not used as a field *) + () + | _ -> error_at env (loc, Parse_error.DuplicatePrivateFields name) + end; + SMap.add name `Field seen_names + | None -> SMap.add name kind seen_names + + let class_implements env ~attach_leading = + let rec interfaces env acc = + let interface = + with_loc + (fun env -> + let id = + let id = Type.type_identifier env in + if Peek.token env <> T_LESS_THAN then + id + else + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing id (fun remover id -> remover#identifier id) + in + let targs = Type.type_args env in + { Ast.Class.Implements.Interface.id; targs }) + env + in + let acc = interface :: acc in + match Peek.token env with + | T_COMMA -> + Expect.token env T_COMMA; + interfaces env acc + | _ -> List.rev acc + in + with_loc + (fun env -> + let leading = + if attach_leading then + Peek.comments env + else + [] + in + Expect.token env T_IMPLEMENTS; + let interfaces = interfaces env [] in + { Ast.Class.Implements.interfaces; comments = Flow_ast_utils.mk_comments_opt ~leading () }) + env + + let class_extends ~leading = + with_loc (fun env -> + let expr = + let expr = Expression.left_hand_side (env |> with_allow_yield false) in + if Peek.token env <> T_LESS_THAN then + expr + else + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing expr (fun remover expr -> remover#expression expr) + in + let targs = Type.type_args env in + { Class.Extends.expr; targs; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + + (* https://tc39.es/ecma262/#prod-ClassHeritage *) + let class_heritage env = + let extends = + let leading = Peek.comments env in + if Eat.maybe env T_EXTENDS then + let (loc, extends) = class_extends ~leading env in + let { remove_trailing; _ } = trailing_and_remover env in + Some + (loc, remove_trailing extends (fun remover extends -> remover#class_extends loc extends)) + else + None + in + let implements = + if Peek.token env = T_IMPLEMENTS then ( + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeInterface; + Some (class_implements_remove_trailing env (class_implements env ~attach_leading:true)) + ) else + None + in + (extends, implements) + + let string_value_of_key key = + match key with + | Ast.Expression.Object.Property.Identifier (key_loc, { Identifier.name; comments = _ }) + | Ast.Expression.Object.Property.StringLiteral (key_loc, { StringLiteral.value = name; _ }) -> + Some (key_loc, name) + | _ -> None + + (* In the ES6 draft, all elements are methods. No properties (though there + * are getter and setters allowed *) + let class_element = + let get env start_loc decorators static leading = + let (loc, (key, value)) = + with_loc ~start_loc (fun env -> getter_or_setter env ~in_class_body:true true) env + in + (match (static, string_value_of_key key) with + | (false, Some (key_loc, "constructor")) -> + error_at env (key_loc, Parse_error.ConstructorCannotBeAccessor) + | (true, Some (key_loc, "prototype")) -> + error_at + env + ( key_loc, + Parse_error.InvalidClassMemberName + { name = "prototype"; static; method_ = false; private_ = false } + ) + | _ -> ()); + let open Ast.Class in + Body.Method + ( loc, + { + Method.key; + value; + kind = Method.Get; + static; + decorators; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + in + let set env start_loc decorators static leading = + let (loc, (key, value)) = + with_loc ~start_loc (fun env -> getter_or_setter env ~in_class_body:true false) env + in + (match (static, string_value_of_key key) with + | (false, Some (key_loc, "constructor")) -> + error_at env (key_loc, Parse_error.ConstructorCannotBeAccessor) + | (true, Some (key_loc, "prototype")) -> + error_at + env + ( key_loc, + Parse_error.InvalidClassMemberName + { name = "prototype"; static; method_ = false; private_ = false } + ) + | _ -> ()); + let open Ast.Class in + Body.Method + ( loc, + { + Method.key; + value; + kind = Method.Set; + static; + decorators; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + in + let error_unsupported_variance env = function + | Some (loc, _) -> error_at env (loc, Parse_error.UnexpectedVariance) + | None -> () + (* Class property with annotation *) + in + let error_unsupported_declare env = function + | Some loc -> error_at env (loc, Parse_error.DeclareClassElement) + | None -> () + in + let property_end_and_semicolon env key annot value = + match Peek.token env with + | T_LBRACKET + | T_LPAREN -> + error_unexpected env; + (key, annot, value, []) + | T_SEMICOLON -> + Eat.token env; + let trailing = + match Peek.token env with + | T_EOF + | T_RCURLY -> + Eat.trailing_comments env + | _ when Peek.is_line_terminator env -> Eat.comments_until_next_line env + | _ -> [] + in + (key, annot, value, trailing) + | _ -> + let remover = + match Peek.token env with + | T_EOF + | T_RCURLY -> + { trailing = []; remove_trailing = (fun x _ -> x) } + | _ when Peek.is_line_terminator env -> + Comment_attachment.trailing_and_remover_after_last_line env + | _ -> Comment_attachment.trailing_and_remover_after_last_loc env + in + (* Remove trailing comments from the last node in this property *) + let (key, annot, value) = + match (annot, value) with + (* prop = init *) + | (_, Class.Property.Initialized expr) -> + ( key, + annot, + Class.Property.Initialized + (remover.remove_trailing expr (fun remover expr -> remover#expression expr)) + ) + (* prop: annot *) + | (Ast.Type.Available annot, _) -> + ( key, + Ast.Type.Available + (remover.remove_trailing annot (fun remover annot -> remover#type_annotation annot)), + value + ) + (* prop *) + | _ -> + (remover.remove_trailing key (fun remover key -> remover#object_key key), annot, value) + in + (key, annot, value, []) + in + let property env start_loc decorators key static declare variance leading = + let (loc, (key, annot, value, comments)) = + with_loc + ~start_loc + (fun env -> + let annot = Type.annotation_opt env in + let value = + match (declare, Peek.token env) with + | (None, T_ASSIGN) -> + Eat.token env; + Ast.Class.Property.Initialized + (Parse.expression (env |> with_allow_super Super_prop)) + | (Some _, T_ASSIGN) -> + error env Parse_error.DeclareClassFieldInitializer; + Eat.token env; + Ast.Class.Property.Declared + | (None, _) -> Ast.Class.Property.Uninitialized + | (Some _, _) -> Ast.Class.Property.Declared + in + let (key, annot, value, trailing) = property_end_and_semicolon env key annot value in + (key, annot, value, Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + env + in + let open Ast.Class in + match key with + | Ast.Expression.Object.Property.PrivateName key -> + Body.PrivateField + (loc, { PrivateField.key; value; annot; static; variance; decorators; comments }) + | _ -> + Body.Property (loc, { Property.key; value; annot; static; variance; decorators; comments }) + in + let is_asi env = + match Peek.token env with + | T_LESS_THAN -> false + | T_LPAREN -> false + | _ when Peek.is_implicit_semicolon env -> true + | _ -> false + in + let rec init env start_loc decorators key ~async ~generator ~static ~declare variance leading = + match Peek.token env with + | T_COLON + | T_ASSIGN + | T_SEMICOLON + | T_RCURLY + when (not async) && not generator -> + property env start_loc decorators key static declare variance leading + | T_PLING -> + (* TODO: add support for optional class properties *) + error_unexpected env; + Eat.token env; + init env start_loc decorators key ~async ~generator ~static ~declare variance leading + | _ when is_asi env -> + (* an uninitialized, unannotated property *) + property env start_loc decorators key static declare variance leading + | _ -> + error_unsupported_declare env declare; + error_unsupported_variance env variance; + let (kind, env) = + match (static, string_value_of_key key) with + | (false, Some (key_loc, "constructor")) -> + if async then error_at env (key_loc, Parse_error.ConstructorCannotBeAsync); + if generator then error_at env (key_loc, Parse_error.ConstructorCannotBeGenerator); + (Ast.Class.Method.Constructor, env |> with_allow_super Super_prop_or_call) + | (true, Some (key_loc, "prototype")) -> + error_at + env + ( key_loc, + Parse_error.InvalidClassMemberName + { name = "prototype"; static; method_ = true; private_ = false } + ); + (Ast.Class.Method.Method, env |> with_allow_super Super_prop) + | _ -> (Ast.Class.Method.Method, env |> with_allow_super Super_prop) + in + let key = object_key_remove_trailing env key in + let value = + with_loc + (fun env -> + let (sig_loc, (tparams, params, return)) = + with_loc + (fun env -> + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.FunctionTP + (Type.type_params env) + in + let params = + let params = Declaration.function_params ~await:async ~yield:generator env in + let params = + if Peek.token env = T_COLON then + params + else + function_params_remove_trailing env params + in + Ast.Function.Params.( + match params with + | (loc, ({ this_ = Some (this_loc, _); _ } as params)) + when kind = Ast.Class.Method.Constructor -> + (* Disallow this param annotations for constructors *) + error_at env (this_loc, Parse_error.ThisParamBannedInConstructor); + (loc, { params with this_ = None }) + | params -> params + ) + in + let return = + return_annotation_remove_trailing env (Type.function_return_annotation_opt env) + in + (tparams, params, return)) + env + in + let simple_params = is_simple_parameter_list params in + let (body, contains_use_strict) = + Declaration.function_body env ~async ~generator ~expression:false ~simple_params + in + Declaration.strict_function_post_check env ~contains_use_strict None params; + { + Function.id = None; + params; + body; + generator; + async; + effect_ = Function.Arbitrary; + (* TODO: add support for method predicates *) + predicate = None; + return; + tparams; + sig_loc; + comments = None; + }) + env + in + let open Ast.Class in + Body.Method + ( Loc.btwn start_loc (fst value), + { + Method.key; + value; + kind; + static; + decorators; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + in + let ith_implies_identifier ~i env = + match Peek.ith_token ~i env with + | T_LESS_THAN + | T_COLON + | T_ASSIGN + | T_SEMICOLON + | T_LPAREN + | T_RCURLY -> + true + | _ -> false + in + let implies_identifier = ith_implies_identifier ~i:0 in + fun env -> + let start_loc = Peek.loc env in + let decorators = decorator_list env in + let (declare, leading_declare) = + match Peek.token env with + | T_DECLARE when not (ith_implies_identifier ~i:1 env) -> + let ret = Some (Peek.loc env) in + let leading = Peek.comments env in + Eat.token env; + (ret, leading) + | _ -> (None, []) + in + (* Error on TS class visibility modifiers. *) + (match Peek.token env with + | (T_PUBLIC as t) + | (T_PRIVATE as t) + | (T_PROTECTED as t) + when Peek.ith_is_identifier ~i:1 env -> + let kind = + match t with + | T_PUBLIC -> `Public + | T_PRIVATE -> `Private + | T_PROTECTED -> `Protected + | _ -> failwith "Must be one of the above" + in + error env (Parse_error.TSClassVisibility kind); + Eat.token env + | _ -> ()); + let static = + Peek.token env = T_STATIC + && + match Peek.ith_token ~i:1 env with + | T_ASSIGN (* static = 123 *) + | T_COLON (* static: T *) + | T_EOF (* incomplete property *) + | T_LESS_THAN (* static() {} *) + | T_LPAREN (* static() {} *) + | T_RCURLY (* end of class *) + | T_SEMICOLON (* explicit semicolon *) -> + false + | _ -> true + in + let leading_static = + if static then ( + let leading = Peek.comments env in + Eat.token env; + leading + ) else + [] + in + let async = + Peek.token env = T_ASYNC + && (not (ith_implies_identifier ~i:1 env)) + && not (Peek.ith_is_line_terminator ~i:1 env) + in + (* consume `async` *) + let leading_async = + if async then ( + let leading = Peek.comments env in + Eat.token env; + leading + ) else + [] + in + let (generator, leading_generator) = Declaration.generator env in + let parse_readonly = + Peek.ith_is_identifier ~i:1 env || Peek.ith_token ~i:1 env = T_LBRACKET + in + let variance = Declaration.variance env ~parse_readonly async generator in + let (generator, leading_generator) = + match (generator, variance) with + | (false, Some _) -> Declaration.generator env + | _ -> (generator, leading_generator) + in + let leading = + List.concat [leading_declare; leading_static; leading_async; leading_generator] + in + match (async, generator, Peek.token env) with + | (false, false, T_IDENTIFIER { raw = "get"; _ }) -> + let leading_get = Peek.comments env in + let (_, key) = key ~class_body:true env in + if implies_identifier env then + init env start_loc decorators key ~async ~generator ~static ~declare variance leading + else ( + error_unsupported_declare env declare; + error_unsupported_variance env variance; + ignore (object_key_remove_trailing env key); + get env start_loc decorators static (leading @ leading_get) + ) + | (false, false, T_IDENTIFIER { raw = "set"; _ }) -> + let leading_set = Peek.comments env in + let (_, key) = key ~class_body:true env in + if implies_identifier env then + init env start_loc decorators key ~async ~generator ~static ~declare variance leading + else ( + error_unsupported_declare env declare; + error_unsupported_variance env variance; + ignore (object_key_remove_trailing env key); + set env start_loc decorators static (leading @ leading_set) + ) + | (_, _, _) -> + let (_, key) = key ~class_body:true env in + init env start_loc decorators key ~async ~generator ~static ~declare variance leading + + let class_body = + let rec elements env seen_constructor private_names acc = + match Peek.token env with + | T_EOF + | T_RCURLY -> + List.rev acc + | T_SEMICOLON -> + (* Skip empty elements *) + Expect.token env T_SEMICOLON; + elements env seen_constructor private_names acc + | _ -> + let element = class_element env in + let (seen_constructor', private_names') = + match element with + | Ast.Class.Body.Method (loc, m) -> + let open Ast.Class.Method in + (match m.kind with + | Constructor -> + if m.static then + (seen_constructor, private_names) + else ( + if seen_constructor then error_at env (loc, Parse_error.DuplicateConstructor); + (true, private_names) + ) + | Method -> + let private_names = + match m.key with + | Ast.Expression.Object.Property.PrivateName name -> + check_private_names env private_names name `Method + | _ -> private_names + in + (seen_constructor, private_names) + | Get -> + let open Ast.Expression.Object.Property in + let private_names = + match m.key with + | PrivateName name -> check_private_names env private_names name `Getter + | _ -> private_names + in + (seen_constructor, private_names) + | Set -> + let open Ast.Expression.Object.Property in + let private_names = + match m.key with + | PrivateName name -> check_private_names env private_names name `Setter + | _ -> private_names + in + (seen_constructor, private_names)) + | Ast.Class.Body.Property (_, { Ast.Class.Property.key; static; _ }) -> + let open Ast.Expression.Object.Property in + begin + match key with + | Identifier (loc, { Identifier.name; comments = _ }) + | StringLiteral (loc, { StringLiteral.value = name; _ }) -> + check_property_name env loc name static + | NumberLiteral _ + | BigIntLiteral _ + | Computed _ -> + () + | PrivateName _ -> + failwith "unexpected PrivateName in Property, expected a PrivateField" + end; + (seen_constructor, private_names) + | Ast.Class.Body.PrivateField (_, { Ast.Class.PrivateField.key; _ }) -> + let private_names = check_private_names env private_names key `Field in + (seen_constructor, private_names) + in + elements env seen_constructor' private_names' (element :: acc) + in + fun ~expression env -> + with_loc + (fun env -> + let leading = Peek.comments env in + if Eat.maybe env T_LCURLY then ( + enter_class env; + let body = elements env false SMap.empty [] in + exit_class env; + Expect.token env T_RCURLY; + let trailing = + match (expression, Peek.token env) with + | (true, _) + | (_, (T_RCURLY | T_EOF)) -> + Eat.trailing_comments env + | _ when Peek.is_line_terminator env -> Eat.comments_until_next_line env + | _ -> [] + in + { Ast.Class.Body.body; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) else ( + Expect.error env T_LCURLY; + { Ast.Class.Body.body = []; comments = None } + )) + env + + let _class ?(decorators = []) env ~optional_id ~expression = + (* 10.2.1 says all parts of a class definition are strict *) + let env = env |> with_strict true in + let decorators = decorators @ decorator_list env in + let leading = Peek.comments env in + (match Peek.token env with + | T_IDENTIFIER { raw = "abstract"; _ } -> + error env Parse_error.TSAbstractClass; + Eat.token env + | _ -> ()); + Expect.token env T_CLASS; + let id = + let tmp_env = env |> with_no_let true in + match (optional_id, Peek.token tmp_env) with + | (true, (T_EXTENDS | T_IMPLEMENTS | T_LESS_THAN | T_LCURLY)) -> None + | _ when Peek.is_identifier env -> + let id = Parse.identifier tmp_env in + let { remove_trailing; _ } = trailing_and_remover env in + let id = remove_trailing id (fun remover id -> remover#identifier id) in + Some id + | _ -> + (* error, but don't consume a token like Parse.identifier does. this helps + with recovery, and the parser won't get stuck because we consumed the + `class` token above. *) + error_nameless_declaration env "class"; + Some (Peek.loc env, { Identifier.name = ""; comments = None }) + in + let tparams = + match Type.type_params env with + | None -> None + | Some tparams -> + let { remove_trailing; _ } = trailing_and_remover env in + Some + (remove_trailing tparams (fun remover tparams -> + remover#type_params ~kind:Flow_ast_mapper.ClassTP tparams + ) + ) + in + let (extends, implements) = class_heritage env in + let body = class_body env ~expression in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + { Class.id; body; tparams; extends; implements; class_decorators = decorators; comments } + + let class_declaration env decorators = + with_loc + (fun env -> + let optional_id = in_export_default env in + Ast.Statement.ClassDeclaration (_class env ~decorators ~optional_id ~expression:false)) + env + + let class_expression = + with_loc (fun env -> Ast.Expression.Class (_class env ~optional_id:true ~expression:true)) +end diff --git a/compiler/flow_parser/parser/object_parser.mli b/compiler/flow_parser/parser/object_parser.mli new file mode 100644 index 00000000000..a344deea703 --- /dev/null +++ b/compiler/flow_parser/parser/object_parser.mli @@ -0,0 +1,16 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* A module for parsing various object related things, like object literals + * and classes *) + +module Object + (_ : Parser_common.PARSER) + (_ : Parser_common.TYPE) + (_ : Parser_common.DECLARATION) + (_ : Parser_common.EXPRESSION) + (_ : Parser_common.COVER) : Parser_common.OBJECT diff --git a/compiler/flow_parser/parser/offset_utils.ml b/compiler/flow_parser/parser/offset_utils.ml new file mode 100644 index 00000000000..aaf64def1e4 --- /dev/null +++ b/compiler/flow_parser/parser/offset_utils.ml @@ -0,0 +1,172 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* table from 0-based line number and 0-based column number to the offset at that point *) +type t = int array array + +type offset_kind = + | Utf8 + | JavaScript + +(* Classify each codepoint. We care about how many bytes each codepoint takes, in order to + compute offsets in terms of bytes instead of codepoints. We also care about various kinds of + newlines. To reduce memory, it is important that this is a basic variant with no parameters + (so, don't make it `Chars of int`). *) +type kind = + (* Char has a codepoint greater than or equal to 0x0 but less than 0x80 *) + | Chars_0x0 + (* Char has a codepoint greater than or equal to 0x80 but less than 0x800 *) + | Chars_0x80 + | Chars_0x800 + | Chars_0x10000 + | Malformed + | Cr + | Nl + | Ls + +(* Gives the size in bytes of the character's UTF-8 encoding *) +let utf8_size_of_kind = function + | Chars_0x0 -> 1 + | Chars_0x80 -> 2 + | Chars_0x800 -> 3 + | Chars_0x10000 -> 4 + | Malformed -> 1 + | Cr -> 1 + | Nl -> 1 + | Ls -> 3 + +(* Gives the size in code units (16-bit blocks) of the character's UTF-16 encoding *) +let js_size_of_kind = function + | Chars_0x0 + | Chars_0x80 + | Chars_0x800 -> + 1 + | Chars_0x10000 -> 2 + | Malformed -> 1 + | Cr -> 1 + | Nl -> 1 + | Ls -> 1 + +let make = + (* Using Wtf8 allows us to properly track multi-byte characters, so that we increment the column + * by 1 for a multi-byte character, but increment the offset by the number of bytes in the + * character. It also keeps us from incrementing the line number if a multi-byte character happens + * to include e.g. the codepoint for '\n' as a second-fourth byte. *) + let fold_codepoints acc _offset chr = + let kind = + match chr with + | Wtf8.Point code -> + if code == 0x2028 || code == 0x2029 then + Ls + else if code == 0xA then + Nl + else if code == 0xD then + Cr + else if code >= 0x10000 then + Chars_0x10000 + else if code >= 0x800 then + Chars_0x800 + else if code >= 0x80 then + Chars_0x80 + else + Chars_0x0 + | Wtf8.Malformed -> Malformed + in + kind :: acc + in + (* Traverses a `kind list`, breaking it up into an `int array array`, where each `int array` + contains the offsets at each character (aka codepoint) of a line. *) + let rec build_table size_of_kind (offset, rev_line, acc) = function + | [] -> Array.of_list (List.rev acc) + | Cr :: Nl :: rest -> + (* https://www.ecma-international.org/ecma-262/5.1/#sec-7.3 says that "\r\n" should be treated + like a single line terminator, even though both '\r' and '\n' are line terminators in their + own right. *) + let line = Array.of_list (List.rev (offset :: rev_line)) in + build_table size_of_kind (offset + 2, [], line :: acc) rest + | ((Cr | Nl | Ls) as kind) :: rest -> + let line = Array.of_list (List.rev (offset :: rev_line)) in + build_table size_of_kind (offset + size_of_kind kind, [], line :: acc) rest + | ((Chars_0x0 | Chars_0x80 | Chars_0x800 | Chars_0x10000 | Malformed) as kind) :: rest -> + build_table size_of_kind (offset + size_of_kind kind, offset :: rev_line, acc) rest + in + fun ~kind text -> + let rev_kinds = Wtf8.fold_wtf_8 fold_codepoints [] text in + (* Add a phantom line at the end of the file. Since end positions are reported exclusively, it + * is possible for the lexer to output an end position with a line number one higher than the + * last line, to indicate something such as "the entire last line." For this purpose, we can + * return the offset that is one higher than the last legitimate offset, since it could only be + * correctly used as an exclusive index. *) + let rev_kinds = Nl :: rev_kinds in + let size_of_kind = + match kind with + | Utf8 -> utf8_size_of_kind + | JavaScript -> js_size_of_kind + in + build_table size_of_kind (0, [], []) (List.rev rev_kinds) + +exception Offset_lookup_failed of Loc.position * string + +let lookup arr i pos context_string = + try arr.(i) with + | Invalid_argument _ -> + let msg = + Printf.sprintf + "Failure while looking up %s. Index: %d. Length: %d." + context_string + i + (Array.length arr) + in + raise (Offset_lookup_failed (pos, msg)) + +let offset table pos = + Loc.( + (* Special-case `Loc.none` so we don't try to look up line -1. *) + if pos.line = 0 && pos.column = 0 then + (* Loc.none sets the offset as 0, so that's what we'll return here. *) + 0 + else + (* lines are 1-indexed, columns are zero-indexed *) + let line_table = lookup table (pos.line - 1) pos "line" in + lookup line_table pos.column pos "column" + ) + +let debug_string table = + let buf = Buffer.create 4096 in + Array.iteri + (fun line_num line -> + Printf.bprintf buf "%6d: " line_num; + Array.iter (fun offset -> Printf.bprintf buf "%8d " offset) line; + Buffer.add_char buf '\n') + table; + Buffer.contents buf + +let line_lengths table = + Array.fold_left + (fun (prev_line_end, lengths_rev) line -> + let line_end = line.(Array.length line - 1) in + (line_end, (line_end - prev_line_end) :: lengths_rev)) + (-1, []) + table + |> snd + |> List.rev + +let contains_multibyte_character table = + let exception FoundMultibyte in + try + Array.iter + (fun line -> + Array.iteri + (fun i offset -> + if i > 0 then + let offset_before = line.(i - 1) in + if offset - offset_before > 1 then raise FoundMultibyte) + line) + table; + false + with + | FoundMultibyte -> true diff --git a/compiler/flow_parser/parser/offset_utils.mli b/compiler/flow_parser/parser/offset_utils.mli new file mode 100644 index 00000000000..a5fc4bc4c97 --- /dev/null +++ b/compiler/flow_parser/parser/offset_utils.mli @@ -0,0 +1,56 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* Note on character encodings: + * + * Throughout Flow, we assume that program text uses a UTF-8 encoding. OCaml strings are just a + * sequence of bytes, so any handling of multi-byte characters needs to be done explicitly. + * + * Column numbers in `Loc.position`s are based on the number of characters into a line the position + * appears, not the number of bytes. Single-byte and multi-byte characters are treated the same for + * the purposes of counting columns. + * + * However, offsets are most useful (at least when working with OCaml's string representation) when + * they represent the number of bytes into the text a given position is. + * + * In contrast, JavaScript strings must behave as if they have a UTF-16 encoding, and each element + * is a single 16-bit entry. So, each character occupies either one or two elements of a JavaScript + * string. Esprima, for example, returns ranges based on index into a JS string. + * + * Clients can choose between byte offsets and UTF-16 code unit offsets when building the offset + * table. + * + * For example, with the Utf8 offset kind selected, this utility would consider the smiley emoji + * (code point 0x1f603) to have width 4 (because its UTF-8 encoding is 4 8-bit elements), but with + * the JavaScript offset kind selected, it (and Esprima) would consider it to have width 2 (because + * its UTF-16 encoding is 2 16-bit elements). + *) + +(* A structure that allows for quick computation of offsets when given a Loc.position *) +type t + +type offset_kind = + | Utf8 + | JavaScript + +(* Create a table for offsets in the given file. Takes O(n) time and returns an object that takes + * O(n) space, where `n` is the size of the given program text. *) +val make : kind:offset_kind -> string (* program text *) -> t + +exception Offset_lookup_failed of Loc.position * string + +(* Returns the offset for the given location. This is the offset in bytes (not characters!) into the + * file where the given position can be found. Constant time operation. Raises + * `Offset_lookup_failed` if the given position does not exist in the file contents which were used + * to construct the table. *) +val offset : t -> Loc.position -> int + +val debug_string : t -> string + +val line_lengths : t -> int list + +val contains_multibyte_character : t -> bool diff --git a/compiler/flow_parser/parser/parse_error.ml b/compiler/flow_parser/parser/parse_error.ml new file mode 100644 index 00000000000..905d3928dcf --- /dev/null +++ b/compiler/flow_parser/parser/parse_error.ml @@ -0,0 +1,510 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type t = + | AccessorDataProperty + | AccessorGetSet + | AdjacentJSXElements + | AmbiguousLetBracket + | AsyncFunctionAsStatement + | AwaitAsIdentifierReference + | AwaitInAsyncFormalParameters + | ComputedShorthandProperty + | ConstructorCannotBeAccessor + | ConstructorCannotBeAsync + | ConstructorCannotBeGenerator + | DeclareAsync + | DeclareClassElement + | DeclareClassFieldInitializer + | DeclareOpaqueTypeInitializer + | DuplicateConstructor + | DuplicateExport of string + | DuplicatePrivateFields of string + | ElementAfterRestElement + | EnumBigIntMemberNotInitialized of { + enum_name: string; + member_name: string; + } + | EnumBooleanMemberNotInitialized of { + enum_name: string; + member_name: string; + } + | EnumDuplicateMemberName of { + enum_name: string; + member_name: string; + } + | EnumInconsistentMemberValues of { enum_name: string } + | EnumInvalidEllipsis of { trailing_comma: bool } + | EnumInvalidExplicitType of { + enum_name: string; + supplied_type: string option; + } + | EnumInvalidExport + | EnumInvalidInitializerSeparator of { member_name: string } + | EnumInvalidMemberInitializer of { + enum_name: string; + explicit_type: Enum_common.explicit_type option; + member_name: string; + } + | EnumInvalidMemberName of { + enum_name: string; + member_name: string; + } + | EnumInvalidMemberSeparator + | EnumNumberMemberNotInitialized of { + enum_name: string; + member_name: string; + } + | EnumStringMemberInconsistentlyInitialized of { enum_name: string } + | EnumInvalidConstPrefix + | ExpectedJSXClosingTag of string + | ExpectedPatternFoundExpression + | ExportSpecifierMissingComma + | FunctionAsStatement of { in_strict_mode: bool } + | GeneratorFunctionAsStatement + | GetterArity + | GetterMayNotHaveThisParam + | IllegalBreak + | IllegalContinue + | IllegalReturn + | IllegalUnicodeEscape + | ImportSpecifierMissingComma + | ImportTypeShorthandOnlyInPureImport + | InexactInsideExact + | InexactInsideNonObject + | InvalidClassMemberName of { + name: string; + static: bool; + method_: bool; + private_: bool; + } + | InvalidComponentParamName + | InvalidComponentRenderAnnotation of { has_nested_render: bool } + | InvalidComponentStringParameterBinding of { + optional: bool; + name: string; + } + | InvalidFloatBigInt + | InvalidIndexedAccess of { has_bracket: bool } + | InvalidJSXAttributeValue + | InvalidLHSInAssignment + | InvalidLHSInExponentiation + | InvalidLHSInForIn + | InvalidLHSInForOf + | InvalidOptionalIndexedAccess + | InvalidRegExp + | InvalidRegExpFlags of string + | InvalidSciBigInt + | InvalidTupleOptionalSpread + | InvalidTupleVariance + | InvalidTypeof + | JSXAttributeValueEmptyExpression + | LiteralShorthandProperty + | MalformedUnicode + | MatchNonLastRest of [ `Object | `Array ] + | MatchEmptyArgument + | MatchSpreadArgument + | MethodInDestructuring + | MissingJSXClosingTag of string + | MissingTypeParam + | MissingTypeParamDefault + | MultipleDefaultsInSwitch + | NewlineAfterThrow + | NewlineBeforeArrow + | NoCatchOrFinally + | NoUninitializedConst + | NoUninitializedDestructuring + | NullishCoalescingUnexpectedLogical of string + | OptionalChainNew + | OptionalChainTemplate + | ParameterAfterRestParameter + | PrivateDelete + | PrivateNotInClass + | PropertyAfterRestElement + | Redeclaration of string * string + | SetterArity + | SetterMayNotHaveThisParam + | StrictCatchVariable + | StrictDelete + | StrictDuplicateProperty + | StrictFunctionName + | StrictLHSAssignment + | StrictLHSPostfix + | StrictLHSPrefix + | StrictModeWith + | StrictNonOctalLiteral + | StrictOctalLiteral + | StrictParamDupe + | StrictParamName + | StrictParamNotSimple + | StrictReservedWord + | StrictVarName + | SuperPrivate + | TSAbstractClass + | TSClassVisibility of [ `Public | `Private | `Protected ] + | TSTemplateLiteralType + | ThisParamAnnotationRequired + | ThisParamBannedInArrowFunctions + | ThisParamBannedInConstructor + | ThisParamMayNotBeOptional + | ThisParamMustBeFirst + | TrailingCommaAfterRestElement + | UnboundPrivate of string + | Unexpected of string + | UnexpectedEOS + | UnexpectedExplicitInexactInObject + | UnexpectedOpaqueTypeAlias + | UnexpectedProto + | UnexpectedReserved + | UnexpectedReservedType + | UnexpectedSpreadType + | UnexpectedStatic + | UnexpectedSuper + | UnexpectedSuperCall + | UnexpectedTokenWithSuggestion of string * string + | UnexpectedTypeAlias + | UnexpectedTypeAnnotation + | UnexpectedTypeDeclaration + | UnexpectedTypeExport + | UnexpectedTypeImport + | UnexpectedTypeInterface + | UnexpectedVariance + | UnexpectedWithExpected of string * string + | UnknownLabel of string + | UnsupportedDecorator + | UnterminatedRegExp + | WhitespaceInPrivateName + | YieldAsIdentifierReference + | YieldInFormalParameters +[@@deriving ord] + +exception Error of (Loc.t * t) * (Loc.t * t) list + +let error loc e = raise (Error ((loc, e), [])) + +module PP = struct + let error = function + | AccessorDataProperty -> + "Object literal may not have data and accessor property with the same name" + | AccessorGetSet -> "Object literal may not have multiple get/set accessors with the same name" + | AdjacentJSXElements -> + "Unexpected token <. Remember, adjacent JSX elements must be wrapped in an enclosing parent tag" + | AmbiguousLetBracket -> + "`let [` is ambiguous in this position because it is either a `let` binding pattern, or a member expression." + | AsyncFunctionAsStatement -> + "Async functions can only be declared at top level or immediately within another function." + | AwaitAsIdentifierReference -> "`await` is an invalid identifier in async functions" + | AwaitInAsyncFormalParameters -> "`await` is not allowed in async function parameters." + | ComputedShorthandProperty -> "Computed properties must have a value." + | ConstructorCannotBeAccessor -> "Constructor can't be an accessor." + | ConstructorCannotBeAsync -> "Constructor can't be an async function." + | ConstructorCannotBeGenerator -> "Constructor can't be a generator." + | DeclareAsync -> + "async is an implementation detail and isn't necessary for your declare function statement. " + ^ "It is sufficient for your declare function to just have a Promise return type." + | DeclareClassElement -> "`declare` modifier can only appear on class fields." + | DeclareClassFieldInitializer -> + "Unexpected token `=`. Initializers are not allowed in a `declare`." + | DeclareOpaqueTypeInitializer -> + "Unexpected token `=`. Initializers are not allowed in a `declare opaque type`." + | DuplicateConstructor -> "Classes may only have one constructor" + | DuplicateExport export -> Printf.sprintf "Duplicate export for `%s`" export + | DuplicatePrivateFields name -> + Printf.sprintf + "Private fields may only be declared once. `#%s` is declared more than once." + name + | ElementAfterRestElement -> "Rest element must be final element of an array pattern" + | EnumBigIntMemberNotInitialized { enum_name; member_name } -> + Printf.sprintf + "bigint enum members need to be initialized, e.g. `%s = 1n,` in enum `%s`." + member_name + enum_name + | EnumBooleanMemberNotInitialized { enum_name; member_name } -> + Printf.sprintf + "Boolean enum members need to be initialized. Use either `%s = true,` or `%s = false,` in enum `%s`." + member_name + member_name + enum_name + | EnumDuplicateMemberName { enum_name; member_name } -> + Printf.sprintf + "Enum member names need to be unique, but the name `%s` has already been used before in enum `%s`." + member_name + enum_name + | EnumInconsistentMemberValues { enum_name } -> + Printf.sprintf + "Enum `%s` has inconsistent member initializers. Either use no initializers, or consistently use literals (either booleans, numbers, or strings) for all member initializers." + enum_name + | EnumInvalidEllipsis { trailing_comma } -> + if trailing_comma then + "The `...` must come at the end of the enum body. Remove the trailing comma." + else + "The `...` must come after all enum members. Move it to the end of the enum body." + | EnumInvalidExplicitType { enum_name; supplied_type } -> + let suggestion = + Printf.sprintf + "Use one of `boolean`, `number`, `string`, `symbol`, or `bigint` in enum `%s`." + enum_name + in + begin + match supplied_type with + | Some supplied_type -> + Printf.sprintf "Enum type `%s` is not valid. %s" supplied_type suggestion + | None -> Printf.sprintf "Supplied enum type is not valid. %s" suggestion + end + | EnumInvalidExport -> + "Cannot export an enum with `export type`, try `export enum E {}` or `module.exports = E;` instead." + | EnumInvalidInitializerSeparator { member_name } -> + Printf.sprintf + "Enum member names and initializers are separated with `=`. Replace `%s:` with `%s =`." + member_name + member_name + | EnumInvalidMemberInitializer { enum_name; explicit_type; member_name } -> begin + match explicit_type with + | Some (Enum_common.Boolean as explicit_type) + | Some (Enum_common.Number as explicit_type) + | Some (Enum_common.String as explicit_type) + | Some (Enum_common.BigInt as explicit_type) -> + let explicit_type_str = Enum_common.string_of_explicit_type explicit_type in + Printf.sprintf + "Enum `%s` has type `%s`, so the initializer of `%s` needs to be a %s literal." + enum_name + explicit_type_str + member_name + explicit_type_str + | Some Enum_common.Symbol -> + Printf.sprintf + "Symbol enum members cannot be initialized. Use `%s,` in enum `%s`." + member_name + enum_name + | None -> + Printf.sprintf + "The enum member initializer for `%s` needs to be a literal (either a boolean, number, or string) in enum `%s`." + member_name + enum_name + end + | EnumInvalidMemberName { enum_name; member_name } -> + (* Based on the error condition, we will only receive member names starting with [a-z] *) + let suggestion = String.capitalize_ascii member_name in + Printf.sprintf + "Enum member names cannot start with lowercase 'a' through 'z'. Instead of using `%s`, consider using `%s`, in enum `%s`." + member_name + suggestion + enum_name + | EnumInvalidMemberSeparator -> "Enum members are separated with `,`. Replace `;` with `,`." + | EnumNumberMemberNotInitialized { enum_name; member_name } -> + Printf.sprintf + "Number enum members need to be initialized, e.g. `%s = 1,` in enum `%s`." + member_name + enum_name + | EnumStringMemberInconsistentlyInitialized { enum_name } -> + Printf.sprintf + "String enum members need to consistently either all use initializers, or use no initializers, in enum %s." + enum_name + | EnumInvalidConstPrefix -> + "`const` enums are not supported. Flow Enums are designed to allow for inlining, however the inlining itself needs to be part of the build system (whatever you use) rather than Flow itself." + | ExpectedJSXClosingTag name -> + Printf.sprintf "Expected corresponding JSX closing tag for %s" name + | ExpectedPatternFoundExpression -> + "Expected an object pattern, array pattern, or an identifier but found an expression instead" + | ExportSpecifierMissingComma -> "Missing comma between export specifiers" + | FunctionAsStatement { in_strict_mode } -> + if in_strict_mode then + "In strict mode code, functions can only be declared at top level or " + ^ "immediately within another function." + else + "In non-strict mode code, functions can only be declared at top level, " + ^ "inside a block, or as the body of an if statement." + | GeneratorFunctionAsStatement -> + "Generators can only be declared at top level or immediately within another function." + | GetterArity -> "Getter should have zero parameters" + | GetterMayNotHaveThisParam -> "A getter cannot have a `this` parameter." + | IllegalBreak -> "Illegal break statement" + | IllegalContinue -> "Illegal continue statement" + | IllegalReturn -> "Illegal return statement" + | IllegalUnicodeEscape -> "Illegal Unicode escape" + | ImportSpecifierMissingComma -> "Missing comma between import specifiers" + | ImportTypeShorthandOnlyInPureImport -> + "The `type` and `typeof` keywords on named imports can only be used on regular `import` statements. " + ^ "It cannot be used with `import type` or `import typeof` statements" + | InexactInsideExact -> + "Explicit inexact syntax cannot appear inside an explicit exact object type" + | InexactInsideNonObject -> "Explicit inexact syntax can only appear inside an object type" + | InvalidClassMemberName { name; static; method_; private_ } -> + let static_modifier = + if static then + "static " + else + "" + in + let category = + if method_ then + "methods" + else + "fields" + in + let name = + if private_ then + "#" ^ name + else + name + in + Printf.sprintf "Classes may not have %s%s named `%s`." static_modifier category name + | InvalidComponentParamName -> + "Component params must be an identifier. If you'd like to destructure, you should use `name as {destructure}`" + | InvalidComponentRenderAnnotation _ -> + "Components use `renders` instead of `:` to annotate the render type of a component." + | InvalidComponentStringParameterBinding { optional; name } -> + let camelized_name = Parse_error_utils.camelize name in + Printf.sprintf + "String params require local bindings using `as` renaming. You can use `'%s' as %s%s: ` " + name + camelized_name + ( if optional then + "?" + else + "" + ) + | InvalidFloatBigInt -> "A bigint literal must be an integer" + | InvalidIndexedAccess { has_bracket } -> + let msg = + if has_bracket then + "Remove the period." + else + "Indexed access uses bracket notation." + in + Printf.sprintf "Invalid indexed access. %s Use the format `T[K]`." msg + | InvalidJSXAttributeValue -> "JSX value should be either an expression or a quoted JSX text" + | InvalidLHSInAssignment -> "Invalid left-hand side in assignment" + | InvalidLHSInExponentiation -> "Invalid left-hand side in exponentiation expression" + | InvalidLHSInForIn -> "Invalid left-hand side in for-in" + | InvalidLHSInForOf -> "Invalid left-hand side in for-of" + | InvalidOptionalIndexedAccess -> + "Invalid optional indexed access. Indexed access uses bracket notation. Use the format `T?.[K]`." + | InvalidRegExp -> "Invalid regular expression" + | InvalidRegExpFlags flags -> + Printf.sprintf "Invalid flags supplied to RegExp constructor '%s'" flags + | InvalidSciBigInt -> "A bigint literal cannot use exponential notation" + | InvalidTypeof -> "`typeof` can only be used to get the type of variables." + | InvalidTupleOptionalSpread -> "Tuple spread elements cannot be optional." + | InvalidTupleVariance -> + "Tuple variance annotations can only be used with labeled tuple elements, e.g. `[+foo: number]`" + | JSXAttributeValueEmptyExpression -> + "JSX attributes must only be assigned a non-empty expression" + | LiteralShorthandProperty -> "Literals cannot be used as shorthand properties." + | MalformedUnicode -> "Malformed unicode" + | MatchNonLastRest kind -> + let kind = + match kind with + | `Object -> "object" + | `Array -> "array" + in + Printf.sprintf "In match %s pattern, the rest must be the last element in the pattern" kind + | MatchEmptyArgument -> "`match` argument must not be empty" + | MatchSpreadArgument -> "`match` argument cannot contain spread elements" + | MethodInDestructuring -> "Object pattern can't contain methods" + | MissingJSXClosingTag name -> + Printf.sprintf "JSX element %s has no corresponding closing tag." name + | MissingTypeParam -> "Expected at least one type parameter." + | MissingTypeParamDefault -> + "Type parameter declaration needs a default, since a preceding type parameter declaration has a default." + | MultipleDefaultsInSwitch -> "More than one default clause in switch statement" + | NewlineAfterThrow -> "Illegal newline after throw" + | NewlineBeforeArrow -> "Illegal newline before arrow" + | NoCatchOrFinally -> "Missing catch or finally after try" + | NoUninitializedConst -> "Const must be initialized" + | NoUninitializedDestructuring -> "Destructuring assignment must be initialized" + | NullishCoalescingUnexpectedLogical operator -> + Printf.sprintf + "Unexpected token `%s`. Parentheses are required to combine `??` with `&&` or `||` expressions." + operator + | OptionalChainNew -> "An optional chain may not be used in a `new` expression." + | OptionalChainTemplate -> "Template literals may not be used in an optional chain." + | ParameterAfterRestParameter -> "Rest parameter must be final parameter of an argument list" + | PrivateDelete -> "Private fields may not be deleted." + | PrivateNotInClass -> "Private fields can only be referenced from within a class." + | PropertyAfterRestElement -> "Rest property must be final property of an object pattern" + | Redeclaration (what, name) -> Printf.sprintf "%s '%s' has already been declared" what name + | SetterArity -> "Setter should have exactly one parameter" + | SetterMayNotHaveThisParam -> "A setter cannot have a `this` parameter." + | StrictCatchVariable -> "Catch variable may not be eval or arguments in strict mode" + | StrictDelete -> "Delete of an unqualified identifier in strict mode." + | StrictDuplicateProperty -> + "Duplicate data property in object literal not allowed in strict mode" + | StrictFunctionName -> "Function name may not be eval or arguments in strict mode" + | StrictLHSAssignment -> "Assignment to eval or arguments is not allowed in strict mode" + | StrictLHSPostfix -> + "Postfix increment/decrement may not have eval or arguments operand in strict mode" + | StrictLHSPrefix -> + "Prefix increment/decrement may not have eval or arguments operand in strict mode" + | StrictModeWith -> "Strict mode code may not include a with statement" + | StrictNonOctalLiteral -> "Number literals with leading zeros are not allowed in strict mode." + | StrictOctalLiteral -> "Octal literals are not allowed in strict mode." + | StrictParamDupe -> "Strict mode function may not have duplicate parameter names" + | StrictParamName -> "Parameter name eval or arguments is not allowed in strict mode" + | StrictParamNotSimple -> + "Illegal \"use strict\" directive in function with non-simple parameter list" + | StrictReservedWord -> "Use of reserved word in strict mode" + | StrictVarName -> "Variable name may not be eval or arguments in strict mode" + | SuperPrivate -> "You may not access a private field through the `super` keyword." + | TSAbstractClass -> "Flow does not support abstract classes." + | TSClassVisibility kind -> + let (keyword, append) = + match kind with + | `Private -> + ( "private", + " You can try using JavaScript private fields by prepending `#` to the field name." + ) + | `Public -> + ( "public", + " Fields and methods are public by default. You can simply omit the `public` keyword." + ) + | `Protected -> ("protected", "") + in + Printf.sprintf "Flow does not support using `%s` in classes.%s" keyword append + | TSTemplateLiteralType -> "Flow does not support template literal types." + | ThisParamAnnotationRequired -> "A type annotation is required for the `this` parameter." + | ThisParamBannedInArrowFunctions -> + "Arrow functions cannot have a `this` parameter; arrow functions automatically bind `this` when declared." + | ThisParamBannedInConstructor -> + "Constructors cannot have a `this` parameter; constructors don't bind `this` like other functions." + | ThisParamMayNotBeOptional -> "The `this` parameter cannot be optional." + | ThisParamMustBeFirst -> "The `this` parameter must be the first function parameter." + | TrailingCommaAfterRestElement -> "A trailing comma is not permitted after the rest element" + | UnboundPrivate name -> + Printf.sprintf + "Private fields must be declared before they can be referenced. `#%s` has not been declared." + name + | Unexpected unexpected -> Printf.sprintf "Unexpected %s" unexpected + | UnexpectedEOS -> "Unexpected end of input" + | UnexpectedExplicitInexactInObject -> + "Explicit inexact syntax must come at the end of an object type" + | UnexpectedOpaqueTypeAlias -> "Opaque type aliases are not allowed in untyped mode" + | UnexpectedProto -> "Unexpected proto modifier" + | UnexpectedReserved -> "Unexpected reserved word" + | UnexpectedReservedType -> "Unexpected reserved type" + | UnexpectedSpreadType -> "Spreading a type is only allowed inside an object type" + | UnexpectedStatic -> "Unexpected static modifier" + | UnexpectedSuper -> "Unexpected `super` outside of a class method" + | UnexpectedSuperCall -> "`super()` is only valid in a class constructor" + | UnexpectedTokenWithSuggestion (token, suggestion) -> + Printf.sprintf "Unexpected token `%s`. Did you mean `%s`?" token suggestion + | UnexpectedTypeAlias -> "Type aliases are not allowed in untyped mode" + | UnexpectedTypeAnnotation -> "Type annotations are not allowed in untyped mode" + | UnexpectedTypeDeclaration -> "Type declarations are not allowed in untyped mode" + | UnexpectedTypeExport -> "Type exports are not allowed in untyped mode" + | UnexpectedTypeImport -> "Type imports are not allowed in untyped mode" + | UnexpectedTypeInterface -> "Interfaces are not allowed in untyped mode" + | UnexpectedVariance -> "Unexpected variance sigil" + | UnexpectedWithExpected (unexpected, expected) -> + Printf.sprintf "Unexpected %s, expected %s" unexpected expected + | UnknownLabel label -> Printf.sprintf "Undefined label '%s'" label + | UnsupportedDecorator -> "Found a decorator in an unsupported position." + | UnterminatedRegExp -> "Invalid regular expression: missing /" + | WhitespaceInPrivateName -> "Unexpected whitespace between `#` and identifier" + | YieldAsIdentifierReference -> "`yield` is an invalid identifier in generators" + | YieldInFormalParameters -> "Yield expression not allowed in formal parameter" +end diff --git a/compiler/flow_parser/parser/parse_error_utils.ml b/compiler/flow_parser/parser/parse_error_utils.ml new file mode 100644 index 00000000000..b15d74344ee --- /dev/null +++ b/compiler/flow_parser/parser/parse_error_utils.ml @@ -0,0 +1,14 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +let camelize str = + match String.split_on_char '-' str with + | [] -> str + | [str] -> str + | hd :: rest -> + let parts = hd :: List.map String.capitalize_ascii rest in + String.concat "" parts diff --git a/compiler/flow_parser/parser/parser_common.ml b/compiler/flow_parser/parser/parser_common.ml new file mode 100644 index 00000000000..ad67acf6a81 --- /dev/null +++ b/compiler/flow_parser/parser/parser_common.ml @@ -0,0 +1,535 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Parser_env +open Flow_ast + +type pattern_errors = { + if_expr: (Loc.t * Parse_error.t) list; + if_patt: (Loc.t * Parse_error.t) list; +} + +type pattern_cover = + | Cover_expr of (Loc.t, Loc.t) Expression.t + | Cover_patt of (Loc.t, Loc.t) Expression.t * pattern_errors + +module type PARSER = sig + val program : env -> (Loc.t, Loc.t) Program.t + + val statement : ?allow_sequence:bool -> env -> (Loc.t, Loc.t) Statement.t + + val statement_list_item : + ?decorators:(Loc.t, Loc.t) Class.Decorator.t list -> env -> (Loc.t, Loc.t) Statement.t + + val statement_list : term_fn:(Token.t -> bool) -> env -> (Loc.t, Loc.t) Statement.t list + + val statement_list_with_directives : + term_fn:(Token.t -> bool) -> env -> (Loc.t, Loc.t) Statement.t list * bool + + val module_body : term_fn:(Token.t -> bool) -> env -> (Loc.t, Loc.t) Statement.t list + + val expression : env -> (Loc.t, Loc.t) Expression.t + + val expression_or_pattern : env -> pattern_cover + + val conditional : env -> (Loc.t, Loc.t) Expression.t + + val assignment : env -> (Loc.t, Loc.t) Expression.t + + val left_hand_side : env -> (Loc.t, Loc.t) Expression.t + + val object_initializer : env -> Loc.t * (Loc.t, Loc.t) Expression.Object.t * pattern_errors + + val identifier : ?restricted_error:Parse_error.t -> env -> (Loc.t, Loc.t) Identifier.t + + val identifier_with_type : + env -> ?no_optional:bool -> Parse_error.t -> Loc.t * (Loc.t, Loc.t) Pattern.Identifier.t + + val block_body : env -> Loc.t * (Loc.t, Loc.t) Statement.Block.t + + val function_block_body : + expression:bool -> env -> (Loc.t * (Loc.t, Loc.t) Statement.Block.t) * bool + + val jsx_element_or_fragment : + env -> + Loc.t * [ `Element of (Loc.t, Loc.t) JSX.element | `Fragment of (Loc.t, Loc.t) JSX.fragment ] + + val pattern : env -> Parse_error.t -> (Loc.t, Loc.t) Pattern.t + + val pattern_from_expr : env -> (Loc.t, Loc.t) Expression.t -> (Loc.t, Loc.t) Pattern.t + + val object_key : ?class_body:bool -> env -> Loc.t * (Loc.t, Loc.t) Expression.Object.Property.key + + val class_declaration : env -> (Loc.t, Loc.t) Class.Decorator.t list -> (Loc.t, Loc.t) Statement.t + + val class_expression : env -> (Loc.t, Loc.t) Expression.t + + val is_assignable_lhs : (Loc.t, Loc.t) Expression.t -> bool + + val number : env -> Token.number_type -> string -> float + + val annot : env -> (Loc.t, Loc.t) Type.annotation + + val bigint : env -> Token.bigint_type -> string -> int64 option + + val match_pattern : env -> (Loc.t, Loc.t) MatchPattern.t +end + +module type TYPE = sig + val _type : env -> (Loc.t, Loc.t) Type.t + + val type_identifier : env -> (Loc.t, Loc.t) Identifier.t + + val type_params : env -> (Loc.t, Loc.t) Type.TypeParams.t option + + val type_args : env -> (Loc.t, Loc.t) Type.TypeArgs.t option + + val generic : env -> Loc.t * (Loc.t, Loc.t) Type.Generic.t + + val _object : is_class:bool -> env -> Loc.t * (Loc.t, Loc.t) Type.Object.t + + val interface_helper : + env -> (Loc.t * (Loc.t, Loc.t) Type.Generic.t) list * (Loc.t * (Loc.t, Loc.t) Type.Object.t) + + val function_param_list : env -> (Loc.t, Loc.t) Type.Function.Params.t + + val component_param_list : env -> (Loc.t, Loc.t) Type.Component.Params.t + + val annotation : env -> (Loc.t, Loc.t) Type.annotation + + val annotation_opt : env -> (Loc.t, Loc.t) Type.annotation_or_hint + + val renders_annotation_opt : env -> (Loc.t, Loc.t) Type.component_renders_annotation + + val function_return_annotation_opt : env -> (Loc.t, Loc.t) Function.ReturnAnnot.t + + val predicate_opt : env -> (Loc.t, Loc.t) Type.Predicate.t option + + val function_return_annotation_and_predicate_opt : + env -> (Loc.t, Loc.t) Function.ReturnAnnot.t * (Loc.t, Loc.t) Type.Predicate.t option + + val type_guard : env -> (Loc.t, Loc.t) Type.TypeGuard.t +end + +module type COVER = sig + val as_expression : env -> pattern_cover -> (Loc.t, Loc.t) Expression.t + + val as_pattern : ?err:Parse_error.t -> env -> pattern_cover -> (Loc.t, Loc.t) Pattern.t + + val empty_errors : pattern_errors + + val cons_error : Loc.t * Parse_error.t -> pattern_errors -> pattern_errors + + val rev_append_errors : pattern_errors -> pattern_errors -> pattern_errors + + val rev_errors : pattern_errors -> pattern_errors +end + +module type PATTERN = sig + val from_expr : Parser_env.env -> (Loc.t, Loc.t) Expression.t -> (Loc.t, Loc.t) Pattern.t + + val pattern : Parser_env.env -> Parse_error.t -> (Loc.t, Loc.t) Pattern.t +end + +module type OBJECT = sig + val key : ?class_body:bool -> env -> Loc.t * (Loc.t, Loc.t) Expression.Object.Property.key + + val _initializer : env -> Loc.t * (Loc.t, Loc.t) Expression.Object.t * pattern_errors + + val class_declaration : env -> (Loc.t, Loc.t) Class.Decorator.t list -> (Loc.t, Loc.t) Statement.t + + val class_expression : env -> (Loc.t, Loc.t) Expression.t + + val class_implements : env -> attach_leading:bool -> (Loc.t, Loc.t) Class.Implements.t + + val decorator_list : env -> (Loc.t, Loc.t) Class.Decorator.t list +end + +module type JSX = sig + val element_or_fragment : + parent_opening_name:(Loc.t, Loc.t) JSX.name option -> + env -> + Loc.t * [ `Element of (Loc.t, Loc.t) JSX.element | `Fragment of (Loc.t, Loc.t) JSX.fragment ] +end + +module type EXPRESSION = sig + val arguments : env -> (Loc.t, Loc.t) Expression.ArgList.t + + val assignment : env -> (Loc.t, Loc.t) Expression.t + + val assignment_cover : env -> pattern_cover + + val conditional : env -> (Loc.t, Loc.t) Expression.t + + val is_assignable_lhs : (Loc.t, Loc.t) Expression.t -> bool + + val left_hand_side : env -> (Loc.t, Loc.t) Expression.t + + val number : env -> Token.number_type -> string -> float + + val bigint : env -> Token.bigint_type -> string -> int64 option + + val sequence : + env -> start_loc:Loc.t -> (Loc.t, Loc.t) Expression.t list -> (Loc.t, Loc.t) Expression.t + + val call_type_args : env -> (Loc.t, Loc.t) Expression.CallTypeArgs.t option + + val call_cover : + ?allow_optional_chain:bool -> + ?in_optional_chain:bool -> + env -> + Loc.t -> + pattern_cover -> + pattern_cover +end + +module type STATEMENT = sig + val for_ : env -> (Loc.t, Loc.t) Statement.t + + val if_ : env -> (Loc.t, Loc.t) Statement.t + + val let_ : env -> (Loc.t, Loc.t) Statement.t + + val try_ : env -> (Loc.t, Loc.t) Statement.t + + val while_ : env -> (Loc.t, Loc.t) Statement.t + + val with_ : env -> (Loc.t, Loc.t) Statement.t + + val block : env -> (Loc.t, Loc.t) Statement.t + + val break : env -> (Loc.t, Loc.t) Statement.t + + val continue : env -> (Loc.t, Loc.t) Statement.t + + val debugger : env -> (Loc.t, Loc.t) Statement.t + + val declare : ?in_module_or_namespace:bool -> env -> (Loc.t, Loc.t) Statement.t + + val declare_export_declaration : env -> (Loc.t, Loc.t) Statement.t + + val declare_opaque_type : env -> (Loc.t, Loc.t) Statement.t + + val do_while : env -> (Loc.t, Loc.t) Statement.t + + val empty : env -> (Loc.t, Loc.t) Statement.t + + val export_declaration : + decorators:(Loc.t, Loc.t) Class.Decorator.t list -> env -> (Loc.t, Loc.t) Statement.t + + val expression : ?allow_sequence:bool -> env -> (Loc.t, Loc.t) Statement.t + + val import_declaration : env -> (Loc.t, Loc.t) Statement.t + + val interface : env -> (Loc.t, Loc.t) Statement.t + + val match_statement : env -> (Loc.t, Loc.t) Statement.t + + val maybe_labeled : env -> (Loc.t, Loc.t) Statement.t + + val opaque_type : env -> (Loc.t, Loc.t) Statement.t + + val return : env -> (Loc.t, Loc.t) Statement.t + + val switch : env -> (Loc.t, Loc.t) Statement.t + + val throw : env -> (Loc.t, Loc.t) Statement.t + + val type_alias : env -> (Loc.t, Loc.t) Statement.t + + val var : env -> (Loc.t, Loc.t) Statement.t + + val const : env -> (Loc.t, Loc.t) Statement.t +end + +module type DECLARATION = sig + val async : env -> bool * Loc.t Comment.t list + + val generator : env -> bool * Loc.t Comment.t list + + val variance : env -> parse_readonly:bool -> bool -> bool -> Loc.t Variance.t option + + val function_params : await:bool -> yield:bool -> env -> (Loc.t, Loc.t) Function.Params.t + + val function_body : + env -> + async:bool -> + generator:bool -> + expression:bool -> + simple_params:bool -> + (Loc.t, Loc.t) Function.body * bool + + val check_unique_formal_parameters : env -> (Loc.t, Loc.t) Function.Params.t -> unit + + val check_unique_component_formal_parameters : + env -> (Loc.t, Loc.t) Statement.ComponentDeclaration.Params.t -> unit + + val strict_function_post_check : + env -> + contains_use_strict:bool -> + (Loc.t, Loc.t) Identifier.t option -> + (Loc.t, Loc.t) Function.Params.t -> + unit + + val strict_component_post_check : + env -> + contains_use_strict:bool -> + (Loc.t, Loc.t) Identifier.t -> + (Loc.t, Loc.t) Statement.ComponentDeclaration.Params.t -> + unit + + val let_ : + env -> + (Loc.t, Loc.t) Statement.VariableDeclaration.Declarator.t list + * Loc.t Comment.t list + * (Loc.t * Parse_error.t) list + + val const : + env -> + (Loc.t, Loc.t) Statement.VariableDeclaration.Declarator.t list + * Loc.t Comment.t list + * (Loc.t * Parse_error.t) list + + val var : + env -> + (Loc.t, Loc.t) Statement.VariableDeclaration.Declarator.t list + * Loc.t Comment.t list + * (Loc.t * Parse_error.t) list + + val _function : env -> (Loc.t, Loc.t) Statement.t + + val enum_declaration : ?leading:Loc.t Comment.t list -> env -> (Loc.t, Loc.t) Statement.t + + val component : env -> (Loc.t, Loc.t) Statement.t +end + +module type MATCH_PATTERN = sig + val match_pattern : env -> (Loc.t, Loc.t) MatchPattern.t +end + +let identifier_name_raw env = + let open Token in + let name = + match Peek.token env with + (* obviously, Identifier is a valid IdentifierName *) + | T_IDENTIFIER { value; _ } -> value + (* keywords are also IdentifierNames *) + | T_AWAIT -> "await" + | T_BREAK -> "break" + | T_CASE -> "case" + | T_CATCH -> "catch" + | T_CLASS -> "class" + | T_CONST -> "const" + | T_CONTINUE -> "continue" + | T_DEBUGGER -> "debugger" + | T_DEFAULT -> "default" + | T_DELETE -> "delete" + | T_DO -> "do" + | T_ELSE -> "else" + | T_EXPORT -> "export" + | T_EXTENDS -> "extends" + | T_FINALLY -> "finally" + | T_FOR -> "for" + | T_FUNCTION -> "function" + | T_IF -> "if" + | T_IMPORT -> "import" + | T_IN -> "in" + | T_INSTANCEOF -> "instanceof" + | T_NEW -> "new" + | T_RETURN -> "return" + | T_SUPER -> "super" + | T_SWITCH -> "switch" + | T_THIS -> "this" + | T_THROW -> "throw" + | T_TRY -> "try" + | T_TYPEOF -> "typeof" + | T_VAR -> "var" + | T_VOID -> "void" + | T_WHILE -> "while" + | T_WITH -> "with" + | T_YIELD -> "yield" + (* FutureReservedWord *) + | T_ENUM -> "enum" + | T_LET -> "let" + | T_STATIC -> "static" + | T_INTERFACE -> "interface" + | T_IMPLEMENTS -> "implements" + | T_PACKAGE -> "package" + | T_PRIVATE -> "private" + | T_PROTECTED -> "protected" + | T_PUBLIC -> "public" + (* NullLiteral *) + | T_NULL -> "null" + (* BooleanLiteral *) + | T_TRUE -> "true" + | T_FALSE -> "false" + (* Flow-specific stuff *) + | T_ASSERTS -> "asserts" + | T_IMPLIES -> "implies" + | T_IS -> "is" + | T_DECLARE -> "declare" + | T_TYPE -> "type" + | T_OPAQUE -> "opaque" + | T_ANY_TYPE -> "any" + | T_MATCH -> "match" + | T_MIXED_TYPE -> "mixed" + | T_EMPTY_TYPE -> "empty" + | T_BOOLEAN_TYPE BOOL -> "bool" + | T_BOOLEAN_TYPE BOOLEAN -> "boolean" + | T_NUMBER_TYPE -> "number" + | T_BIGINT_TYPE -> "bigint" + | T_STRING_TYPE -> "string" + | T_VOID_TYPE -> "void" + | T_SYMBOL_TYPE -> "symbol" + | T_UNKNOWN_TYPE -> "unknown" + | T_NEVER_TYPE -> "never" + | T_UNDEFINED_TYPE -> "undefined" + | T_KEYOF -> "keyof" + | T_READONLY -> "readonly" + (* Contextual stuff *) + | T_OF -> "of" + | T_ASYNC -> "async" + (* punctuators, types, literals, etc are not identifiers *) + | _ -> + error_unexpected ~expected:"an identifier" env; + "" + in + Eat.token env; + name + +(* IdentifierName - https://tc39.github.io/ecma262/#prod-IdentifierName *) +let identifier_name env = + let loc = Peek.loc env in + let leading = Peek.comments env in + let name = identifier_name_raw env in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, { Identifier.name; comments }) + +(** PrivateIdentifier - https://tc39.es/ecma262/#prod-PrivateIdentifier + + N.B.: whitespace, line terminators, and comments are not allowed + between the # and IdentifierName because PrivateIdentifier is a + CommonToken which is considered a single token. See also + https://tc39.es/ecma262/#prod-InputElementDiv *) +let private_identifier env = + let start_loc = Peek.loc env in + let leading = Peek.comments env in + Expect.token env Token.T_POUND; + let name_loc = Peek.loc env in + let name = identifier_name_raw env in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + let loc = Loc.btwn start_loc name_loc in + if not (Loc.equal_position start_loc.Loc._end name_loc.Loc.start) then + error_at env (loc, Parse_error.WhitespaceInPrivateName); + (loc, { PrivateName.name; comments }) + +(** The operation IsSimpleParamterList + https://tc39.es/ecma262/#sec-static-semantics-issimpleparameterlist *) +let is_simple_parameter_list = + let is_simple_param = function + | (_, { Flow_ast.Function.Param.argument = (_, Pattern.Identifier _); default = None }) -> true + | _ -> false + in + fun (_, { Flow_ast.Function.Params.params; rest; comments = _; this_ = _ }) -> + rest = None && List.for_all is_simple_param params + +(** + * The abstract operation IsLabelledFunction + * + * https://tc39.github.io/ecma262/#sec-islabelledfunction + *) +let rec is_labelled_function = function + | (_, Flow_ast.Statement.Labeled { Flow_ast.Statement.Labeled.body; _ }) -> begin + match body with + | (_, Flow_ast.Statement.FunctionDeclaration _) -> true + | _ -> is_labelled_function body + end + | _ -> false + +(** https://tc39.es/ecma262/#sec-exports-static-semantics-early-errors *) +let assert_identifier_name_is_identifier + ?restricted_error env (loc, { Flow_ast.Identifier.name; comments = _ }) = + match name with + | "let" when no_let env -> + error_at env (loc, Parse_error.Unexpected (Token.quote_token_value name)) + | "await" -> + (* `allow_await` means that `await` is allowed to be a keyword, + which makes it illegal to use as an identifier. + https://tc39.github.io/ecma262/#sec-identifiers-static-semantics-early-errors *) + if allow_await env then error_at env (loc, Parse_error.AwaitAsIdentifierReference) + | "yield" -> + (* `allow_yield` means that `yield` is allowed to be a keyword, + which makes it illegal to use as an identifier. + https://tc39.github.io/ecma262/#sec-identifiers-static-semantics-early-errors *) + if allow_yield env then + error_at env (loc, Parse_error.UnexpectedReserved) + else + strict_error_at env (loc, Parse_error.StrictReservedWord) + | _ when is_strict_reserved name -> strict_error_at env (loc, Parse_error.StrictReservedWord) + | _ when is_reserved name -> error_at env (loc, Parse_error.UnexpectedReserved) + | _ -> begin + match restricted_error with + | Some err when is_restricted name -> strict_error_at env (loc, err) + | _ -> () + end + +let with_loc ?start_loc fn env = + let start_loc = + match start_loc with + | Some x -> x + | None -> Peek.loc env + in + let result = fn env in + let loc = + match last_loc env with + | Some end_loc -> Loc.btwn start_loc end_loc + | None -> start_loc + in + (loc, result) + +let with_loc_opt ?start_loc fn env = + match with_loc ?start_loc fn env with + | (loc, Some x) -> Some (loc, x) + | (_, None) -> None + +let with_loc_extra ?start_loc fn env = + let (loc, (x, extra)) = with_loc ?start_loc fn env in + ((loc, x), extra) + +let is_start_of_type_guard env = + let open Token in + (* Parse the identifier part as normal code, since this can be any name that + * a parameter can be. *) + Eat.push_lex_mode env Lex_mode.NORMAL; + let token_1 = Peek.token env in + Eat.pop_lex_mode env; + let token_2 = Peek.ith_token ~i:1 env in + match (token_1, token_2) with + | (T_IDENTIFIER { raw = "asserts"; _ }, (T_IDENTIFIER _ | T_THIS)) + | (T_IDENTIFIER { raw = "implies"; _ }, (T_IDENTIFIER _ | T_THIS)) + | ((T_IDENTIFIER _ | T_THIS), (T_IS | T_IDENTIFIER { raw = "is"; _ })) -> + true + | _ -> false + +let reparse_arguments_as_match_argument env (args_loc, args) = + let { Expression.ArgList.arguments; _ } = args in + if Base.List.is_empty arguments then Parser_env.error_at env (args_loc, Parse_error.MatchEmptyArgument); + let filtered_args = + List.filter_map + (function + | Expression.Spread (loc, _) -> + Parser_env.error_at env (loc, Parse_error.MatchSpreadArgument); + None + | Expression.Expression e -> Some e) + arguments + in + match filtered_args with + | [expr] -> expr + | expressions -> + (args_loc, Expression.Sequence { Expression.Sequence.expressions; comments = None }) diff --git a/compiler/flow_parser/parser/parser_env.ml b/compiler/flow_parser/parser/parser_env.ml new file mode 100644 index 00000000000..bc40743638c --- /dev/null +++ b/compiler/flow_parser/parser/parser_env.ml @@ -0,0 +1,1327 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Sedlexing = Flow_sedlexing +open Flow_ast +module SSet = Flow_set.Make (String) + +module Lex_mode = struct + type t = + | NORMAL + | TYPE + | JSX_TAG + | JSX_CHILD + | TEMPLATE + | REGEXP + + let debug_string_of_lex_mode (mode : t) = + match mode with + | NORMAL -> "NORMAL" + | TYPE -> "TYPE" + | JSX_TAG -> "JSX_TAG" + | JSX_CHILD -> "JSX_CHILD" + | TEMPLATE -> "TEMPLATE" + | REGEXP -> "REGEXP" +end + +(* READ THIS BEFORE YOU MODIFY: + * + * The current implementation for lookahead beyond a single token is + * inefficient. If you believe you need to increase this constant, do one of the + * following: + * - Find another way + * - Benchmark your change and provide convincing evidence that it doesn't + * actually have a significant perf impact. + * - Refactor this to memoize all requested lookahead, so we aren't lexing the + * same token multiple times. + *) + +module Lookahead : sig + type t + + val create : Lex_env.t -> Lex_mode.t -> t + + val peek_0 : t -> Lex_result.t + + val peek_1 : t -> Lex_result.t + + val lex_env_0 : t -> Lex_env.t + + val junk : t -> unit +end = struct + type la_result = (Lex_env.t * Lex_result.t) option + + type t = { + mutable la_results_0: la_result; + mutable la_results_1: la_result; + la_lex_mode: Lex_mode.t; + mutable la_lex_env: Lex_env.t; + } + + let create lex_env mode = + let lex_env = Lex_env.clone lex_env in + { la_results_0 = None; la_results_1 = None; la_lex_mode = mode; la_lex_env = lex_env } + + (* precondition: there is enough room in t.la_results for the result *) + let lex t = + let lex_env = t.la_lex_env in + let (lex_env, lex_result) = + match t.la_lex_mode with + | Lex_mode.NORMAL -> Flow_lexer.token lex_env + | Lex_mode.TYPE -> Flow_lexer.type_token lex_env + | Lex_mode.JSX_TAG -> Flow_lexer.jsx_tag lex_env + | Lex_mode.JSX_CHILD -> Flow_lexer.jsx_child lex_env + | Lex_mode.TEMPLATE -> Flow_lexer.template_tail lex_env + | Lex_mode.REGEXP -> Flow_lexer.regexp lex_env + in + let cloned_env = Lex_env.clone lex_env in + let result = (cloned_env, lex_result) in + t.la_lex_env <- lex_env; + begin + match t.la_results_0 with + | None -> t.la_results_0 <- Some result + | Some _ -> t.la_results_1 <- Some result + end; + result + + let peek_0 t = + match t.la_results_0 with + | Some (_, result) -> result + | None -> snd (lex t) + + let peek_1 t = + (match t.la_results_0 with + | None -> ignore (lex t) + | Some _ -> ()); + match t.la_results_1 with + | None -> snd (lex t) + | Some (_, result) -> result + + let lex_env_0 t = + match t.la_results_0 with + | Some (lex_env, _) -> lex_env + | None -> fst (lex t) + + (* Throws away the first peeked-at token, shifting any subsequent tokens up *) + let junk t = + match t.la_results_1 with + | None -> + ignore (peek_0 t); + t.la_results_0 <- None + | Some _ -> + t.la_results_0 <- t.la_results_1; + t.la_results_1 <- None +end + +type token_sink_result = { + token_loc: Loc.t; + token: Token.t; + token_context: Lex_mode.t; +} + +type parse_options = { + components: bool; (* enable parsing of Flow component syntax *) + enums: bool; (** enable parsing of Flow enums *) + pattern_matching: bool; + esproposal_decorators: bool; (** enable parsing of decorators *) + types: bool; (** enable parsing of Flow types *) + use_strict: bool; (** treat the file as strict, without needing a "use strict" directive *) + module_ref_prefix: string option; + module_ref_prefix_LEGACY_INTEROP: string option; +} + +let default_parse_options = + { + components = false; + enums = false; + pattern_matching = false; + esproposal_decorators = false; + types = true; + use_strict = false; + module_ref_prefix = None; + module_ref_prefix_LEGACY_INTEROP = None; + } + +let permissive_parse_options = + { + components = true; + enums = true; + pattern_matching = true; + esproposal_decorators = true; + types = true; + use_strict = false; + module_ref_prefix = None; + module_ref_prefix_LEGACY_INTEROP = None; + } + +type allowed_super = + | No_super + | Super_prop + | Super_prop_or_call + +type env = { + errors: (Loc.t * Parse_error.t) list ref; + comments: Loc.t Comment.t list ref; + labels: SSet.t; + last_lex_result: Lex_result.t option ref; + in_strict_mode: bool; + in_export: bool; + in_export_default: bool; + in_loop: bool; + in_switch: bool; + in_formal_parameters: bool; + in_function: bool; + no_in: bool; + no_call: bool; + no_let: bool; + no_anon_function_type: bool; + no_conditional_type: bool; + no_new: bool; + allow_yield: bool; + allow_await: bool; + allow_directive: bool; + has_simple_parameters: bool; + allow_super: allowed_super; + error_callback: (env -> Parse_error.t -> unit) option; + lex_mode_stack: Lex_mode.t list ref; + (* lex_env is the lex_env after the single lookahead has been lexed *) + lex_env: Lex_env.t ref; + (* This needs to be cleared whenever we advance. *) + lookahead: Lookahead.t ref; + token_sink: (token_sink_result -> unit) option ref; + parse_options: parse_options; + source: File_key.t option; + (* It is a syntax error to reference private fields not in scope. In order to enforce this, + * we keep track of the privates we've seen declared and used. *) + privates: (SSet.t * (string * Loc.t) list) list ref; + (* The position up to which comments have been consumed, exclusive. *) + consumed_comments_pos: Loc.position ref; +} + +(* constructor *) +let init_env ?(token_sink = None) ?(parse_options = None) source content = + (* let lb = Sedlexing.Utf16.from_string + content (Some Sedlexing.Utf16.Little_endian) in *) + let (lb, errors) = + try (Sedlexing.Utf8.from_string content, []) with + | Sedlexing.MalFormed -> + (Sedlexing.Utf8.from_string "", [({ Loc.none with Loc.source }, Parse_error.MalformedUnicode)]) + in + let parse_options = + match parse_options with + | Some opts -> opts + | None -> default_parse_options + in + let enable_types_in_comments = parse_options.types in + let lex_env = Lex_env.new_lex_env source lb ~enable_types_in_comments in + { + errors = ref errors; + comments = ref []; + labels = SSet.empty; + last_lex_result = ref None; + has_simple_parameters = true; + in_strict_mode = parse_options.use_strict; + in_export = false; + in_export_default = false; + in_loop = false; + in_switch = false; + in_formal_parameters = false; + in_function = false; + no_in = false; + no_call = false; + no_let = false; + no_anon_function_type = false; + no_conditional_type = false; + no_new = false; + allow_yield = false; + allow_await = false; + allow_directive = false; + allow_super = No_super; + error_callback = None; + lex_mode_stack = ref [Lex_mode.NORMAL]; + lex_env = ref lex_env; + lookahead = ref (Lookahead.create lex_env Lex_mode.NORMAL); + token_sink = ref token_sink; + parse_options; + source; + privates = ref []; + consumed_comments_pos = ref { Loc.line = 0; column = 0 }; + } + +(* getters: *) +let in_strict_mode env = env.in_strict_mode + +let lex_mode env = List.hd !(env.lex_mode_stack) + +let in_export env = env.in_export + +let in_export_default env = env.in_export_default + +let comments env = !(env.comments) + +let labels env = env.labels + +let in_loop env = env.in_loop + +let in_switch env = env.in_switch + +let in_formal_parameters env = env.in_formal_parameters + +let in_function env = env.in_function + +let allow_yield env = env.allow_yield + +let allow_await env = env.allow_await + +let allow_directive env = env.allow_directive + +let allow_super env = env.allow_super + +let has_simple_parameters env = env.has_simple_parameters + +let no_in env = env.no_in + +let no_call env = env.no_call + +let no_let env = env.no_let + +let no_anon_function_type env = env.no_anon_function_type + +let no_conditional_type env = env.no_conditional_type + +let no_new env = env.no_new + +let errors env = !(env.errors) + +let parse_options env = env.parse_options + +let source env = env.source + +let should_parse_types env = env.parse_options.types + +(* mutators: *) +let error_at env (loc, e) = + env.errors := (loc, e) :: !(env.errors); + match env.error_callback with + | None -> () + | Some callback -> callback env e + +(* Since private fields out of scope are a parse error, we keep track of the declared and used + * private fields. + * + * Whenever we enter a class, we push new empty lists of declared and used privates. + * When we encounter a new declared private, we add it to the top of the declared_privates list + * via add_declared_private. We do the same with used_privates via add_used_private. + * + * When we exit a class, we look for all the unbound private variables. Since class fields + * are hoisted to the scope of the class, we may need to look further before we conclude that + * a field is out of scope. To do that, we add all of the unbound private fields to the + * next used_private list. Once we run out of declared private lists, any leftover used_privates + * are unbound private variables. *) +let enter_class env = env.privates := (SSet.empty, []) :: !(env.privates) + +let exit_class env = + let get_unbound_privates declared_privates used_privates = + List.filter (fun x -> not (SSet.mem (fst x) declared_privates)) used_privates + in + match !(env.privates) with + | [(declared_privates, used_privates)] -> + let unbound_privates = get_unbound_privates declared_privates used_privates in + List.iter + (fun (name, loc) -> error_at env (loc, Parse_error.UnboundPrivate name)) + unbound_privates; + env.privates := [] + | (loc_declared_privates, loc_used_privates) :: privates -> + let unbound_privates = get_unbound_privates loc_declared_privates loc_used_privates in + let (decl_head, used_head) = List.hd privates in + env.privates := (decl_head, used_head @ unbound_privates) :: List.tl privates + | _ -> failwith "Internal Error: `exit_class` called before a matching `enter_class`" + +let add_declared_private env name = + match !(env.privates) with + | [] -> failwith "Internal Error: Tried to add_declared_private with outside of class scope." + | (declared, used) :: xs -> env.privates := (SSet.add name declared, used) :: xs + +let add_used_private env name loc = + match !(env.privates) with + | [] -> error_at env (loc, Parse_error.PrivateNotInClass) + | (declared, used) :: xs -> env.privates := (declared, (name, loc) :: used) :: xs + +let consume_comments_until env pos = env.consumed_comments_pos := pos + +(* lookahead: *) +let lookahead_0 env = Lookahead.peek_0 !(env.lookahead) + +let lookahead_1 env = Lookahead.peek_1 !(env.lookahead) + +let lookahead ~i env = + match i with + | 0 -> lookahead_0 env + | 1 -> lookahead_1 env + | _ -> assert false + +(* functional operations: *) +let with_strict in_strict_mode env = + if in_strict_mode = env.in_strict_mode then + env + else + { env with in_strict_mode } + +let with_in_formal_parameters in_formal_parameters env = + if in_formal_parameters = env.in_formal_parameters then + env + else + { env with in_formal_parameters } + +let with_in_function in_function env = + if in_function = env.in_function then + env + else + { env with in_function } + +let with_allow_yield allow_yield env = + if allow_yield = env.allow_yield then + env + else + { env with allow_yield } + +let with_allow_await allow_await env = + if allow_await = env.allow_await then + env + else + { env with allow_await } + +let with_allow_directive allow_directive env = + if allow_directive = env.allow_directive then + env + else + { env with allow_directive } + +let with_allow_super allow_super env = + if allow_super = env.allow_super then + env + else + { env with allow_super } + +let with_no_let no_let env = + if no_let = env.no_let then + env + else + { env with no_let } + +let with_in_loop in_loop env = + if in_loop = env.in_loop then + env + else + { env with in_loop } + +let with_no_in no_in env = + if no_in = env.no_in then + env + else + { env with no_in } + +let with_no_anon_function_type no_anon_function_type env = + if no_anon_function_type = env.no_anon_function_type then + env + else + { env with no_anon_function_type } + +let with_no_conditional_type no_conditional_type env = + if no_conditional_type = env.no_conditional_type then + env + else + { env with no_conditional_type } + +let with_no_new no_new env = + if no_new = env.no_new then + env + else + { env with no_new } + +let with_in_switch in_switch env = + if in_switch = env.in_switch then + env + else + { env with in_switch } + +let with_in_export in_export env = + if in_export = env.in_export then + env + else + { env with in_export } + +let with_in_export_default in_export_default env = + if in_export_default = env.in_export_default then + env + else + { env with in_export_default } + +let with_no_call no_call env = + if no_call = env.no_call then + env + else + { env with no_call } + +let with_error_callback error_callback env = { env with error_callback = Some error_callback } + +(* other helper functions: *) +let error_list env = List.iter (error_at env) + +let last_loc env = + match !(env.last_lex_result) with + | Some lex_result -> Some (Lex_result.loc lex_result) + | None -> None + +let last_token env = + match !(env.last_lex_result) with + | Some lex_result -> Some (Lex_result.token lex_result) + | None -> None + +let without_error_callback env = { env with error_callback = None } + +let add_label env label = { env with labels = SSet.add label env.labels } + +let enter_function env ~async ~generator ~simple_params = + { + env with + in_formal_parameters = false; + has_simple_parameters = simple_params; + in_function = true; + in_loop = false; + in_switch = false; + in_export = false; + in_export_default = false; + labels = SSet.empty; + allow_await = async; + allow_yield = generator; + } + +(** IdentifierNames that can't be used as Identifiers in strict mode. + + https://tc39.es/ecma262/#sec-strict-mode-of-ecmascript *) +let is_strict_reserved = function + | "implements" + | "interface" + | "let" + | "package" + | "private" + | "protected" + | "public" + | "static" + | "yield" -> + true + | _ -> false + +(** Tokens which, if parsed as an identifier, are reserved words in strict mode. *) +let token_is_strict_reserved = + let open Token in + function + | T_IDENTIFIER { value; _ } -> is_strict_reserved value + | T_INTERFACE + | T_IMPLEMENTS + | T_LET + | T_PACKAGE + | T_PRIVATE + | T_PROTECTED + | T_PUBLIC + | T_STATIC + | T_YIELD -> + true + | _ -> false + +(* #sec-strict-mode-of-ecmascript *) +let is_restricted = function + | "eval" + | "arguments" -> + true + | _ -> false + +(** Words that are sometimes reserved, and sometimes allowed as identifiers + (namely "await" and "yield") + + https://tc39.es/ecma262/#sec-keywords-and-reserved-words *) +let is_contextually_reserved str_val = + match str_val with + | "await" + | "yield" -> + true + | _ -> false + +(** Words that are sometimes reserved, and sometimes allowed as identifiers + (namely "await" and "yield") + + https://tc39.es/ecma262/#sec-keywords-and-reserved-words *) +let token_is_contextually_reserved t = + let open Token in + match t with + | T_IDENTIFIER { raw; _ } -> is_contextually_reserved raw + | T_AWAIT + | T_YIELD -> + true + | _ -> false + +(** Words that are always reserved (mostly keywords) + + https://tc39.es/ecma262/#sec-keywords-and-reserved-words *) +let is_reserved str_val = + match str_val with + | "break" + | "case" + | "catch" + | "class" + | "const" + | "continue" + | "debugger" + | "default" + | "delete" + | "do" + | "else" + | "enum" + | "export" + | "extends" + | "false" + | "finally" + | "for" + | "function" + | "if" + | "import" + | "in" + | "instanceof" + | "new" + | "null" + | "return" + | "super" + | "switch" + | "this" + | "throw" + | "true" + | "try" + | "typeof" + | "var" + | "void" + | "while" + | "with" -> + true + | _ -> false + +(** Words that are always reserved (mostly keywords) + + https://tc39.es/ecma262/#sec-keywords-and-reserved-words *) +let token_is_reserved t = + let open Token in + match t with + | T_IDENTIFIER { raw; _ } -> is_reserved raw + | T_BREAK + | T_CASE + | T_CATCH + | T_CLASS + | T_CONST + | T_CONTINUE + | T_DEBUGGER + | T_DEFAULT + | T_DELETE + | T_DO + | T_ELSE + | T_ENUM + | T_EXPORT + | T_EXTENDS + | T_FALSE + | T_FINALLY + | T_FOR + | T_FUNCTION + | T_IF + | T_IMPORT + | T_IN + | T_INSTANCEOF + | T_NEW + | T_NULL + | T_RETURN + | T_SUPER + | T_SWITCH + | T_THIS + | T_THROW + | T_TRUE + | T_TRY + | T_TYPEOF + | T_VAR + | T_VOID + | T_WHILE + | T_WITH -> + true + | _ -> false + +let is_reserved_type str_val = + match str_val with + | "any" + | "bigint" + | "bool" + | "boolean" + | "const" + | "empty" + | "extends" + | "false" + | "function" + | "interface" + | "keyof" + | "mixed" + | "never" + | "null" + | "number" + | "readonly" + | "static" + | "string" + | "symbol" + | "true" + | "typeof" + | "undefined" + | "unknown" + | "void" + | "_" -> + true + | _ -> false + +let token_is_reserved_type t = + let open Token in + match t with + | T_IDENTIFIER { raw; _ } when is_reserved_type raw -> true + | T_ANY_TYPE + | T_BIGINT_TYPE + | T_BOOLEAN_TYPE _ + | T_CONST + | T_EMPTY_TYPE + | T_EXTENDS + | T_FALSE + | T_FUNCTION + | T_INTERFACE + | T_KEYOF + | T_MIXED_TYPE + | T_NEVER_TYPE + | T_NULL + | T_NUMBER_TYPE + | T_READONLY + | T_STATIC + | T_STRING_TYPE + | T_SYMBOL_TYPE + | T_TRUE + | T_TYPEOF + | T_UNDEFINED_TYPE + | T_UNKNOWN_TYPE + | T_VOID_TYPE -> + true + | _ -> false + +let token_is_type_identifier env t = + let open Token in + match lex_mode env with + | Lex_mode.TYPE -> begin + match t with + | T_IDENTIFIER _ -> true + | _ -> false + end + | Lex_mode.NORMAL -> begin + (* Sometimes we peek at type identifiers while in normal lex mode. For + example, when deciding whether a `type` token is an identifier or the + start of a type declaration, based on whether the following token + `is_type_identifier`. *) + match t with + | T_IDENTIFIER { raw; _ } when is_reserved_type raw -> false + (* reserved type identifiers, but these don't appear in NORMAL mode *) + | T_ANY_TYPE + | T_MIXED_TYPE + | T_EMPTY_TYPE + | T_NUMBER_TYPE + | T_BIGINT_TYPE + | T_STRING_TYPE + | T_VOID_TYPE + | T_SYMBOL_TYPE + | T_UNKNOWN_TYPE + | T_NEVER_TYPE + | T_UNDEFINED_TYPE + | T_BOOLEAN_TYPE _ + | T_NUMBER_SINGLETON_TYPE _ + | T_BIGINT_SINGLETON_TYPE _ + (* identifier-ish *) + | T_ASYNC + | T_AWAIT + | T_BREAK + | T_CASE + | T_CATCH + | T_CLASS + | T_CONST + | T_CONTINUE + | T_DEBUGGER + | T_DECLARE + | T_DEFAULT + | T_DELETE + | T_DO + | T_ELSE + | T_ENUM + | T_EXPORT + | T_EXTENDS + | T_FALSE + | T_FINALLY + | T_FOR + | T_IDENTIFIER _ + | T_IF + | T_IMPLEMENTS + | T_IMPORT + | T_IN + | T_INSTANCEOF + | T_INTERFACE + | T_LET + | T_MATCH + | T_NEW + | T_NULL + | T_OF + | T_OPAQUE + | T_PACKAGE + | T_PRIVATE + | T_PROTECTED + | T_PUBLIC + | T_RETURN + | T_SUPER + | T_SWITCH + | T_THIS + | T_THROW + | T_TRUE + | T_TRY + | T_TYPE + | T_VAR + | T_WHILE + | T_WITH + | T_YIELD -> + true + (* identifier-ish, but not valid types *) + | T_STATIC + | T_TYPEOF + | T_FUNCTION + | T_KEYOF + | T_READONLY + | T_INFER + | T_IS + | T_ASSERTS + | T_IMPLIES + | T_VOID + | T_RENDERS_QUESTION + | T_RENDERS_STAR -> + false + (* syntax *) + | T_LCURLY + | T_RCURLY + | T_LCURLYBAR + | T_RCURLYBAR + | T_LPAREN + | T_RPAREN + | T_LBRACKET + | T_RBRACKET + | T_SEMICOLON + | T_COMMA + | T_PERIOD + | T_ARROW + | T_ELLIPSIS + | T_AT + | T_POUND + | T_CHECKS + | T_RSHIFT3_ASSIGN + | T_RSHIFT_ASSIGN + | T_LSHIFT_ASSIGN + | T_BIT_XOR_ASSIGN + | T_BIT_OR_ASSIGN + | T_BIT_AND_ASSIGN + | T_MOD_ASSIGN + | T_DIV_ASSIGN + | T_MULT_ASSIGN + | T_EXP_ASSIGN + | T_MINUS_ASSIGN + | T_PLUS_ASSIGN + | T_NULLISH_ASSIGN + | T_AND_ASSIGN + | T_OR_ASSIGN + | T_ASSIGN + | T_PLING_PERIOD + | T_PLING_PLING + | T_PLING + | T_COLON + | T_OR + | T_AND + | T_BIT_OR + | T_BIT_XOR + | T_BIT_AND + | T_EQUAL + | T_NOT_EQUAL + | T_STRICT_EQUAL + | T_STRICT_NOT_EQUAL + | T_LESS_THAN_EQUAL + | T_GREATER_THAN_EQUAL + | T_LESS_THAN + | T_GREATER_THAN + | T_LSHIFT + | T_RSHIFT + | T_RSHIFT3 + | T_PLUS + | T_MINUS + | T_DIV + | T_MULT + | T_EXP + | T_MOD + | T_NOT + | T_BIT_NOT + | T_INCR + | T_DECR + | T_INTERPRETER _ + | T_EOF -> + false + (* literals *) + | T_NUMBER _ + | T_BIGINT _ + | T_STRING _ + | T_TEMPLATE_PART _ + | T_REGEXP _ + (* misc that shouldn't appear in NORMAL mode *) + | T_JSX_IDENTIFIER _ + | T_JSX_CHILD_TEXT _ + | T_JSX_QUOTE_TEXT _ + | T_ERROR _ -> + false + end + | Lex_mode.JSX_TAG + | Lex_mode.JSX_CHILD + | Lex_mode.TEMPLATE + | Lex_mode.REGEXP -> + false + +let token_is_variance token = + let open Token in + match token with + | T_PLUS + | T_MINUS -> + true + | _ -> false + +(* Answer questions about what comes next *) +module Peek = struct + open Loc + open Token + + let ith_token ~i env = Lex_result.token (lookahead ~i env) + + let ith_loc ~i env = Lex_result.loc (lookahead ~i env) + + let ith_errors ~i env = Lex_result.errors (lookahead ~i env) + + let ith_comments ~i env = + let comments = Lex_result.comments (lookahead ~i env) in + match comments with + | [] -> [] + | _ -> + List.filter + (fun ({ Loc.start; _ }, _) -> Loc.pos_cmp !(env.consumed_comments_pos) start <= 0) + comments + + let token env = ith_token ~i:0 env + + let loc env = ith_loc ~i:0 env + + (* loc_skip_lookahead is used to give a loc hint to optional tokens such as type annotations *) + let loc_skip_lookahead env = + let loc = + match last_loc env with + | Some loc -> loc + | None -> failwith "Peeking current location when not available" + in + Loc.{ loc with start = loc._end } + + let errors env = ith_errors ~i:0 env + + let comments env = ith_comments ~i:0 env + + let has_eaten_comments env = + let comments = Lex_result.comments (lookahead ~i:0 env) in + List.exists + (fun ({ Loc.start; _ }, _) -> Loc.pos_cmp start !(env.consumed_comments_pos) < 0) + comments + + let lex_env env = Lookahead.lex_env_0 !(env.lookahead) + + (* True if there is a line terminator before the next token *) + let ith_is_line_terminator ~i env = + let loc = + if i > 0 then + Some (ith_loc ~i:(i - 1) env) + else + last_loc env + in + match loc with + | None -> false + | Some loc' -> (ith_loc ~i env).start.line > loc'.start.line + + let is_line_terminator env = ith_is_line_terminator ~i:0 env + + let ith_is_implicit_semicolon ~i env = + match ith_token ~i env with + | T_EOF + | T_RCURLY -> + true + | T_SEMICOLON -> false + | _ -> ith_is_line_terminator ~i env + + let is_implicit_semicolon env = ith_is_implicit_semicolon ~i:0 env + + let ith_is_identifier ~i env = + match ith_token ~i env with + | t when token_is_strict_reserved t -> true + | T_TYPE + | T_OPAQUE + | T_OF + | T_DECLARE + | T_ASYNC + | T_AWAIT + | T_ENUM + | T_MATCH + | T_POUND + | T_IDENTIFIER _ + | T_READONLY -> + true + | _ -> false + + let ith_is_type_identifier ~i env = token_is_type_identifier env (ith_token ~i env) + + let ith_is_identifier_name ~i env = ith_is_identifier ~i env || ith_is_type_identifier ~i env + + (* This returns true if the next token is identifier-ish (even if it is an + error) *) + let is_identifier env = ith_is_identifier ~i:0 env + + let is_identifier_name env = ith_is_identifier_name ~i:0 env + + let is_type_identifier env = ith_is_type_identifier ~i:0 env + + let is_function env = + token env = T_FUNCTION + || token env = T_ASYNC + && ith_token ~i:1 env = T_FUNCTION + && (loc env)._end.line = (ith_loc ~i:1 env).start.line + + let is_hook env = + match token env with + | T_IDENTIFIER { raw = "hook"; _ } -> + (parse_options env).components + && ith_is_identifier ~i:1 env + && (loc env)._end.line = (ith_loc ~i:1 env).start.line + | _ -> false + + let is_class env = + match token env with + | T_CLASS + | T_AT -> + true + | T_IDENTIFIER { raw = "abstract"; _ } when ith_token ~i:1 env = T_CLASS -> true + | _ -> false + + let is_component env = + (parse_options env).components + && + match token env with + | T_IDENTIFIER { raw = "component"; _ } when ith_is_identifier ~i:1 env -> true + | _ -> false + + let is_renders_ident env = + match token env with + | T_IDENTIFIER { raw = "renders"; _ } -> true + | _ -> false +end + +(*****************************************************************************) +(* Errors *) +(*****************************************************************************) + +(* Complains about an error at the location of the lookahead *) +let error env e = + let loc = Peek.loc env in + error_at env (loc, e) + +let get_unexpected_error ?expected token = + let unexpected = Token.explanation_of_token token in + match expected with + | Some expected_msg -> Parse_error.UnexpectedWithExpected (unexpected, expected_msg) + | None -> Parse_error.Unexpected unexpected + +let error_unexpected ?expected env = + (* So normally we consume the lookahead lex result when Eat.token calls + * Parser_env.advance, which will add any lexing errors to our list of errors. + * However, raising an unexpected error for a lookahead is kind of like + * consuming that token, so we should process any lexing errors before + * complaining about the unexpected token *) + error_list env (Peek.errors env); + error env (get_unexpected_error ?expected (Peek.token env)) + +let error_on_decorators env = + List.iter (fun decorator -> error_at env (fst decorator, Parse_error.UnsupportedDecorator)) + +let error_nameless_declaration env kind = + let expected = + if in_export env then + Printf.sprintf + "an identifier. When exporting a %s as a named export, you must specify a %s name. Did you mean `export default %s ...`?" + kind + kind + kind + else + "an identifier" + in + error_unexpected ~expected env + +let strict_error env e = if in_strict_mode env then error env e + +let strict_error_at env (loc, e) = if in_strict_mode env then error_at env (loc, e) + +let function_as_statement_error_at env loc = + error_at env (loc, Parse_error.FunctionAsStatement { in_strict_mode = in_strict_mode env }) + +(* Consume zero or more tokens *) +module Eat = struct + (* Consume a single token *) + let token env = + (* If there's a token_sink, emit the lexed token before moving forward *) + (match !(env.token_sink) with + | None -> () + | Some token_sink -> + token_sink + { + token_loc = Peek.loc env; + token = Peek.token env; + (* + * The lex mode is useful because it gives context to some + * context-sensitive tokens. + * + * Some examples of such tokens include: + * + * `=>` - Part of an arrow function? or part of a type annotation? + * `<` - A less-than? Or an opening to a JSX element? + * ...etc... + *) + token_context = lex_mode env; + }); + + env.lex_env := Peek.lex_env env; + + error_list env (Peek.errors env); + env.comments := List.rev_append (Lex_result.comments (lookahead ~i:0 env)) !(env.comments); + env.last_lex_result := Some (lookahead ~i:0 env); + + Lookahead.junk !(env.lookahead) + + (** [maybe env t] eats the next token and returns [true] if it is [t], else return [false] *) + let maybe env t = + let is_t = Token.equal (Peek.token env) t in + if is_t then token env; + is_t + + let push_lex_mode env mode = + env.lex_mode_stack := mode :: !(env.lex_mode_stack); + env.lookahead := Lookahead.create !(env.lex_env) (lex_mode env) + + let pop_lex_mode env = + let new_stack = + match !(env.lex_mode_stack) with + | _mode :: stack -> stack + | _ -> failwith "Popping lex mode from empty stack" + in + env.lex_mode_stack := new_stack; + env.lookahead := Lookahead.create !(env.lex_env) (lex_mode env) + + let double_pop_lex_mode env = + let new_stack = + match !(env.lex_mode_stack) with + | _ :: _ :: stack -> stack + | _ -> failwith "Popping lex mode from empty stack" + in + env.lex_mode_stack := new_stack; + env.lookahead := Lookahead.create !(env.lex_env) (lex_mode env) + + let trailing_comments env = + let open Loc in + let loc = Peek.loc env in + if Peek.token env = Token.T_COMMA && Peek.ith_is_line_terminator ~i:1 env then ( + let trailing_before_comma = Peek.comments env in + let trailing_after_comma = + List.filter + (fun (comment_loc, _) -> comment_loc.start.line <= loc._end.line) + (Lex_result.comments (lookahead ~i:1 env)) + in + let trailing = trailing_before_comma @ trailing_after_comma in + consume_comments_until env { Loc.line = loc._end.line + 1; column = 0 }; + trailing + ) else + let trailing = Peek.comments env in + consume_comments_until env loc._end; + trailing + + let comments_until_next_line env = + let open Loc in + match !(env.last_lex_result) with + | None -> [] + | Some { Lex_result.lex_loc = last_loc; _ } -> + let comments = Peek.comments env in + let comments = List.filter (fun (loc, _) -> loc.start.line <= last_loc._end.line) comments in + consume_comments_until env { line = last_loc._end.line + 1; column = 0 }; + comments + + let program_comments env = + let open Flow_ast.Comment in + let comments = Peek.comments env in + let flow_directive = "@flow" in + let flow_directive_length = String.length flow_directive in + let contains_flow_directive { text; _ } = + let text_length = String.length text in + let rec contains_flow_directive_after_offset off = + if off + flow_directive_length > text_length then + false + else + String.sub text off flow_directive_length = flow_directive + || contains_flow_directive_after_offset (off + 1) + in + contains_flow_directive_after_offset 0 + in + (* Comments up through the last comment with an @flow directive are considered program comments *) + let rec flow_directive_comments comments = + match comments with + | [] -> [] + | (loc, comment) :: rest -> + if contains_flow_directive comment then ( + (env.consumed_comments_pos := Loc.(loc._end)); + List.rev ((loc, comment) :: rest) + ) else + flow_directive_comments rest + in + let program_comments = flow_directive_comments (List.rev comments) in + let program_comments = + if program_comments <> [] then + program_comments + else + (* If there is no @flow directive, consider the first block comment a program comment if + it starts with "/**" *) + match comments with + | ((loc, { kind = Block; text; _ }) as first_comment) :: _ + when String.length text >= 1 && text.[0] = '*' -> + (env.consumed_comments_pos := Loc.(loc._end)); + [first_comment] + | _ -> [] + in + program_comments +end + +module Expect = struct + let get_error env t = + let expected = Token.explanation_of_token ~use_article:true t in + (Peek.loc env, get_unexpected_error ~expected (Peek.token env)) + + let error env t = + let expected = Token.explanation_of_token ~use_article:true t in + error_unexpected ~expected env + + let token env t = + if not (Token.equal (Peek.token env) t) then error env t; + Eat.token env + + (** [token_maybe env T_FOO] eats a token if it is [T_FOO], and errors without consuming if + not. Returns whether it consumed a token, like [Eat.maybe]. *) + let token_maybe env t = + let ate = Eat.maybe env t in + if not ate then error env t; + ate + + (** [token_opt env T_FOO] eats a token if it is [T_FOO], and errors without consuming if not. + This differs from [token], which always consumes. Only use [token_opt] when it's ok for + the parser to not advance, like if you are guaranteed that something else has eaten a + token. *) + let token_opt env t = ignore (token_maybe env t) + + let identifier env name = + let t = Peek.token env in + begin + match t with + | Token.T_IDENTIFIER { raw; _ } when raw = name -> () + | _ -> + let expected = Printf.sprintf "the identifier `%s`" name in + error_unexpected ~expected env + end; + Eat.token env +end + +(* This module allows you to try parsing and rollback if you need. This is not + * cheap and its usage is strongly discouraged *) +module Try = struct + type 'a parse_result = + | ParsedSuccessfully of 'a + | FailedToParse + + exception Rollback + + type saved_state = { + saved_errors: (Loc.t * Parse_error.t) list; + saved_comments: Loc.t Flow_ast.Comment.t list; + saved_last_lex_result: Lex_result.t option; + saved_lex_mode_stack: Lex_mode.t list; + saved_lex_env: Lex_env.t; + saved_consumed_comments_pos: Loc.position; + token_buffer: ((token_sink_result -> unit) * token_sink_result Queue.t) option; + } + + let save_state env = + let token_buffer = + match !(env.token_sink) with + | None -> None + | Some orig_token_sink -> + let buffer = Queue.create () in + env.token_sink := Some (fun token_data -> Queue.add token_data buffer); + Some (orig_token_sink, buffer) + in + { + saved_errors = !(env.errors); + saved_comments = !(env.comments); + saved_last_lex_result = !(env.last_lex_result); + saved_lex_mode_stack = !(env.lex_mode_stack); + saved_lex_env = !(env.lex_env); + saved_consumed_comments_pos = !(env.consumed_comments_pos); + token_buffer; + } + + let reset_token_sink ~flush env token_buffer_info = + match token_buffer_info with + | None -> () + | Some (orig_token_sink, token_buffer) -> + env.token_sink := Some orig_token_sink; + if flush then Queue.iter orig_token_sink token_buffer + + let rollback_state env saved_state = + reset_token_sink ~flush:false env saved_state.token_buffer; + env.errors := saved_state.saved_errors; + env.comments := saved_state.saved_comments; + env.last_lex_result := saved_state.saved_last_lex_result; + env.lex_mode_stack := saved_state.saved_lex_mode_stack; + env.lex_env := saved_state.saved_lex_env; + env.consumed_comments_pos := saved_state.saved_consumed_comments_pos; + env.lookahead := Lookahead.create !(env.lex_env) (lex_mode env); + + FailedToParse + + let success env saved_state result = + reset_token_sink ~flush:true env saved_state.token_buffer; + ParsedSuccessfully result + + let to_parse env parse = + let saved_state = save_state env in + try success env saved_state (parse env) with + | Rollback -> rollback_state env saved_state + + let or_else env ~fallback parse = + match to_parse env parse with + | ParsedSuccessfully result -> result + | FailedToParse -> fallback +end diff --git a/compiler/flow_parser/parser/parser_env.mli b/compiler/flow_parser/parser/parser_env.mli new file mode 100644 index 00000000000..0286af980c3 --- /dev/null +++ b/compiler/flow_parser/parser/parser_env.mli @@ -0,0 +1,303 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* This module provides a layer between the lexer and the parser which includes + * some parser state and some lexer state *) + +module SSet : Flow_set.S with type elt = string + +module Lex_mode : sig + type t = + | NORMAL + | TYPE + | JSX_TAG + | JSX_CHILD + | TEMPLATE + | REGEXP + + val debug_string_of_lex_mode : t -> string +end + +type token_sink_result = { + token_loc: Loc.t; + token: Token.t; + token_context: Lex_mode.t; +} + +type parse_options = { + components: bool; (* enable parsing of Flow component syntax *) + enums: bool; (** enable parsing of Flow enums *) + pattern_matching: bool; + esproposal_decorators: bool; (** enable parsing of decorators *) + types: bool; (** enable parsing of Flow types *) + use_strict: bool; (** treat the file as strict, without needing a "use strict" directive *) + module_ref_prefix: string option; + module_ref_prefix_LEGACY_INTEROP: string option; +} + +val default_parse_options : parse_options + +val permissive_parse_options : parse_options + +type env + +type allowed_super = + | No_super + | Super_prop + | Super_prop_or_call + +(* constructor: *) +val init_env : + ?token_sink:(token_sink_result -> unit) option -> + ?parse_options:parse_options option -> + File_key.t option -> + string -> + env + +(* getters: *) +val in_strict_mode : env -> bool + +val last_loc : env -> Loc.t option + +val last_token : env -> Token.t option + +val in_export : env -> bool + +val in_export_default : env -> bool + +val labels : env -> SSet.t + +val comments : env -> Loc.t Flow_ast.Comment.t list + +val in_loop : env -> bool + +val in_switch : env -> bool + +val in_formal_parameters : env -> bool + +val in_function : env -> bool + +val allow_yield : env -> bool + +val allow_await : env -> bool + +val allow_directive : env -> bool + +val allow_super : env -> allowed_super + +val has_simple_parameters : env -> bool + +val no_in : env -> bool + +val no_call : env -> bool + +val no_let : env -> bool + +val no_anon_function_type : env -> bool + +val no_conditional_type : env -> bool + +val no_new : env -> bool + +val errors : env -> (Loc.t * Parse_error.t) list + +val parse_options : env -> parse_options + +val source : env -> File_key.t option + +val should_parse_types : env -> bool + +(* mutators: *) +val error_at : env -> Loc.t * Parse_error.t -> unit + +val error : env -> Parse_error.t -> unit + +val error_unexpected : ?expected:string -> env -> unit + +val error_on_decorators : env -> (Loc.t * 'a) list -> unit + +val error_nameless_declaration : env -> string -> unit + +val strict_error : env -> Parse_error.t -> unit + +val strict_error_at : env -> Loc.t * Parse_error.t -> unit + +val function_as_statement_error_at : env -> Loc.t -> unit + +val error_list : env -> (Loc.t * Parse_error.t) list -> unit + +val enter_class : env -> unit + +val exit_class : env -> unit + +val add_declared_private : env -> string -> unit + +val add_used_private : env -> string -> Loc.t -> unit + +val consume_comments_until : env -> Loc.position -> unit + +(* functional operations -- these return shallow copies, so future mutations to + * the returned env will also affect the original: *) +val with_strict : bool -> env -> env + +val with_in_formal_parameters : bool -> env -> env + +val with_in_function : bool -> env -> env + +val with_allow_yield : bool -> env -> env + +val with_allow_await : bool -> env -> env + +val with_allow_directive : bool -> env -> env + +val with_allow_super : allowed_super -> env -> env + +val with_no_let : bool -> env -> env + +val with_in_loop : bool -> env -> env + +val with_no_in : bool -> env -> env + +val with_no_anon_function_type : bool -> env -> env + +val with_no_conditional_type : bool -> env -> env + +val with_no_new : bool -> env -> env + +val with_in_switch : bool -> env -> env + +val with_in_export : bool -> env -> env + +val with_in_export_default : bool -> env -> env + +val with_no_call : bool -> env -> env + +val with_error_callback : (env -> Parse_error.t -> unit) -> env -> env + +val without_error_callback : env -> env + +val add_label : env -> string -> env + +val enter_function : env -> async:bool -> generator:bool -> simple_params:bool -> env + +val is_contextually_reserved : string -> bool + +val is_reserved : string -> bool + +val token_is_contextually_reserved : Token.t -> bool + +val token_is_reserved : Token.t -> bool + +val token_is_reserved_type : Token.t -> bool + +val token_is_type_identifier : env -> Token.t -> bool + +val token_is_variance : Token.t -> bool + +val is_strict_reserved : string -> bool + +val token_is_strict_reserved : Token.t -> bool + +val is_restricted : string -> bool + +val is_reserved_type : string -> bool + +module Peek : sig + val token : env -> Token.t + + val loc : env -> Loc.t + + val loc_skip_lookahead : env -> Loc.t + + val errors : env -> (Loc.t * Parse_error.t) list + + val comments : env -> Loc.t Flow_ast.Comment.t list + + val has_eaten_comments : env -> bool + + val is_line_terminator : env -> bool + + val is_implicit_semicolon : env -> bool + + val is_identifier : env -> bool + + val is_type_identifier : env -> bool + + val is_identifier_name : env -> bool + + val is_function : env -> bool + + val is_hook : env -> bool + + val is_class : env -> bool + + val is_component : env -> bool + + val is_renders_ident : env -> bool + + val ith_token : i:int -> env -> Token.t + + val ith_loc : i:int -> env -> Loc.t + + val ith_errors : i:int -> env -> (Loc.t * Parse_error.t) list + + val ith_comments : i:int -> env -> Loc.t Flow_ast.Comment.t list + + val ith_is_line_terminator : i:int -> env -> bool + + val ith_is_implicit_semicolon : i:int -> env -> bool + + val ith_is_identifier : i:int -> env -> bool + + val ith_is_identifier_name : i:int -> env -> bool + + val ith_is_type_identifier : i:int -> env -> bool +end + +module Eat : sig + val token : env -> unit + + val maybe : env -> Token.t -> bool + + val push_lex_mode : env -> Lex_mode.t -> unit + + val pop_lex_mode : env -> unit + + val double_pop_lex_mode : env -> unit + + val trailing_comments : env -> Loc.t Flow_ast.Comment.t list + + val comments_until_next_line : env -> Loc.t Flow_ast.Comment.t list + + val program_comments : env -> Loc.t Flow_ast.Comment.t list +end + +module Expect : sig + val get_error : env -> Token.t -> Loc.t * Parse_error.t + + val error : env -> Token.t -> unit + + val token : env -> Token.t -> unit + + val token_opt : env -> Token.t -> unit + + val token_maybe : env -> Token.t -> bool + + val identifier : env -> string -> unit +end + +module Try : sig + type 'a parse_result = + | ParsedSuccessfully of 'a + | FailedToParse + + exception Rollback + + val to_parse : env -> (env -> 'a) -> 'a parse_result + + val or_else : env -> fallback:'a -> (env -> 'a) -> 'a +end diff --git a/compiler/flow_parser/parser/parser_flow.ml b/compiler/flow_parser/parser/parser_flow.ml new file mode 100644 index 00000000000..ef0b64a8680 --- /dev/null +++ b/compiler/flow_parser/parser/parser_flow.ml @@ -0,0 +1,614 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Sedlexing = Flow_sedlexing +module Ast = Flow_ast +open Token +open Parser_env +open Parser_common + +(* Sometimes we add the same error for multiple different reasons. This is hard + to avoid, so instead we just filter the duplicates out. This function takes + a reversed list of errors and returns the list in forward order with dupes + removed. This differs from a set because the original order is preserved. *) +let filter_duplicate_errors = + let module PrintableErrorSet = Flow_set.Make (struct + type t = Loc.t * Parse_error.t + + let compare (a_loc, a_error) (b_loc, b_error) = + let loc = Loc.compare a_loc b_loc in + if loc = 0 then + Parse_error.compare a_error b_error + else + loc + end) in + fun errs -> + let errs = List.rev errs in + let (_, deduped) = + List.fold_left + (fun (set, deduped) err -> + if PrintableErrorSet.mem err set then + (set, deduped) + else + (PrintableErrorSet.add err set, err :: deduped)) + (PrintableErrorSet.empty, []) + errs + in + List.rev deduped + +let check_for_duplicate_exports = + let open Ast in + let record_export env seen (loc, { Identifier.name = export_name; comments = _ }) = + if export_name = "" then + (* empty identifiers signify an error, don't export it *) + seen + else if SSet.mem export_name seen then ( + error_at env (loc, Parse_error.DuplicateExport export_name); + seen + ) else + SSet.add export_name seen + in + let extract_pattern_binding_names = + let rec fold acc = + let open Pattern in + function + | (_, Object { Object.properties; _ }) -> + List.fold_left + (fun acc prop -> + match prop with + | Object.Property (_, { Object.Property.pattern; _ }) + | Object.RestElement (_, { RestElement.argument = pattern; comments = _ }) -> + fold acc pattern) + acc + properties + | (_, Array { Array.elements; _ }) -> + List.fold_left + (fun acc elem -> + match elem with + | Array.Element (_, { Array.Element.argument = pattern; default = _ }) + | Array.RestElement (_, { RestElement.argument = pattern; comments = _ }) -> + fold acc pattern + | Array.Hole _ -> acc) + acc + elements + | (_, Identifier { Pattern.Identifier.name; _ }) -> name :: acc + | (_, Expression _) -> failwith "Parser error: No such thing as an expression pattern!" + in + List.fold_left fold + in + let record_export_of_statement env seen decl = + match decl with + | (_, Statement.ExportDefaultDeclaration { Statement.ExportDefaultDeclaration.default; _ }) -> + record_export env seen (Flow_ast_utils.ident_of_source (default, "default")) + | ( _, + Statement.ExportNamedDeclaration + { Statement.ExportNamedDeclaration.specifiers = Some specifiers; declaration = None; _ } + ) -> + let open Statement.ExportNamedDeclaration in + (match specifiers with + | ExportSpecifiers specifiers -> + List.fold_left + (fun seen + ( _, + { + Statement.ExportNamedDeclaration.ExportSpecifier.local; + exported; + from_remote = _; + imported_name_def_loc = _; + } + ) -> + match exported with + | Some exported -> record_export env seen exported + | None -> record_export env seen local) + seen + specifiers + | ExportBatchSpecifier _ -> + (* doesn't export specific names *) + seen) + | ( _, + Statement.ExportNamedDeclaration + { Statement.ExportNamedDeclaration.specifiers = None; declaration = Some declaration; _ } + ) -> + (match declaration with + | ( loc, + ( Statement.TypeAlias { Statement.TypeAlias.id; _ } + | Statement.OpaqueType { Statement.OpaqueType.id; _ } + | Statement.InterfaceDeclaration { Statement.Interface.id; _ } + | Statement.ClassDeclaration { Class.id = Some id; _ } + | Statement.FunctionDeclaration { Function.id = Some id; _ } + | Statement.EnumDeclaration { Statement.EnumDeclaration.id; _ } + | Statement.ComponentDeclaration { Statement.ComponentDeclaration.id; _ } ) + ) -> + record_export + env + seen + (Flow_ast_utils.ident_of_source (loc, Flow_ast_utils.name_of_ident id)) + | (_, Statement.VariableDeclaration { Statement.VariableDeclaration.declarations; _ }) -> + declarations + |> List.fold_left + (fun names (_, { Statement.VariableDeclaration.Declarator.id; _ }) -> + extract_pattern_binding_names names [id]) + [] + |> List.fold_left (record_export env) seen + | ( _, + Statement.( + ( Block _ | Break _ + | ClassDeclaration { Class.id = None; _ } + | Continue _ | Debugger _ | DeclareClass _ | DeclareComponent _ | DeclareEnum _ + | DeclareExportDeclaration _ | DeclareFunction _ | DeclareInterface _ | DeclareModule _ + | DeclareModuleExports _ | DeclareNamespace _ | DeclareTypeAlias _ | DeclareOpaqueType _ + | DeclareVariable _ | DoWhile _ | Empty _ | ExportDefaultDeclaration _ + | ExportNamedDeclaration _ | Expression _ | For _ | ForIn _ | ForOf _ + | FunctionDeclaration { Function.id = None; _ } + | If _ | ImportDeclaration _ | Labeled _ | Match _ | Return _ | Switch _ | Throw _ + | Try _ | While _ | With _ )) + ) -> + (* these don't export names -- some are invalid, but the AST allows them *) + seen) + | ( _, + Statement.ExportNamedDeclaration + { Statement.ExportNamedDeclaration.declaration = None; specifiers = None; _ } + ) + | ( _, + Statement.ExportNamedDeclaration + { Statement.ExportNamedDeclaration.declaration = Some _; specifiers = Some _; _ } + ) -> + (* impossible *) + seen + | ( _, + Statement.( + ( Block _ | Break _ | ClassDeclaration _ | Continue _ | Debugger _ | DeclareClass _ + | DeclareComponent _ | DeclareEnum _ | DeclareExportDeclaration _ | DeclareFunction _ + | DeclareInterface _ | DeclareModule _ | DeclareModuleExports _ | DeclareNamespace _ + | DeclareTypeAlias _ | DeclareOpaqueType _ | DeclareVariable _ | DoWhile _ | Empty _ + | EnumDeclaration _ | Expression _ | For _ | ForIn _ | ForOf _ | FunctionDeclaration _ + | ComponentDeclaration _ | If _ | ImportDeclaration _ | InterfaceDeclaration _ | Labeled _ + | Match _ | Return _ | Switch _ | Throw _ | Try _ | TypeAlias _ | OpaqueType _ + | VariableDeclaration _ | While _ | With _ )) + ) -> + seen + in + (fun env stmts -> ignore (List.fold_left (record_export_of_statement env) SSet.empty stmts)) + +module rec Parse : PARSER = struct + module Type = Type_parser.Type (Parse) + module Declaration = Declaration_parser.Declaration (Parse) (Type) + module Pattern_cover = Pattern_cover.Cover (Parse) + module Match_pattern = Match_pattern_parser.Match_pattern (Parse) + module Expression = Expression_parser.Expression (Parse) (Type) (Declaration) (Pattern_cover) + module Object = Object_parser.Object (Parse) (Type) (Declaration) (Expression) (Pattern_cover) + module Statement = + Statement_parser.Statement (Parse) (Type) (Declaration) (Object) (Pattern_cover) (Expression) + module Pattern = Pattern_parser.Pattern (Parse) (Type) + module JSX = Jsx_parser.JSX (Parse) (Expression) + + let annot = Type.annotation + + let identifier ?restricted_error env = + let id = identifier_name env in + assert_identifier_name_is_identifier ?restricted_error env id; + id + + let rec program env = + let interpreter = + match Peek.token env with + | T_INTERPRETER (loc, value) -> + Eat.token env; + Some (loc, value) + | _ -> None + in + let leading = Eat.program_comments env in + let stmts = module_body_with_directives env (fun _ -> false) in + let end_loc = Peek.loc env in + Expect.token env T_EOF; + check_for_duplicate_exports env stmts; + let loc = + match stmts with + | [] -> end_loc + | _ -> Loc.btwn (fst (List.hd stmts)) (fst (List.hd (List.rev stmts))) + in + let all_comments = List.rev (comments env) in + ( loc, + { + Ast.Program.statements = stmts; + interpreter; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + all_comments; + } + ) + + and directives = + let check env token = + match token with + | T_STRING (loc, _, _, octal) -> + if octal then strict_error_at env (loc, Parse_error.StrictOctalLiteral) + | _ -> failwith ("Nooo: " ^ token_to_string token ^ "\n") + in + let rec statement_list env term_fn item_fn (string_tokens, stmts, contains_use_strict) = + match Peek.token env with + | T_EOF -> (env, string_tokens, stmts, contains_use_strict) + | t when term_fn t -> (env, string_tokens, stmts, contains_use_strict) + | T_STRING _ as string_token -> + let possible_directive = item_fn env in + let stmts = possible_directive :: stmts in + (match possible_directive with + | (loc, Ast.Statement.Expression { Ast.Statement.Expression.directive = Some raw; _ }) -> + (* 14.1.1 says that it has to be "use strict" without any + escapes, so "use\x20strict" is disallowed. *) + let strict = raw = "use strict" in + if strict && not (has_simple_parameters env) then + error_at env (loc, Parse_error.StrictParamNotSimple); + let env = + if strict then + with_strict true env + else + env + in + let string_tokens = string_token :: string_tokens in + statement_list env term_fn item_fn (string_tokens, stmts, contains_use_strict || strict) + | _ -> (env, string_tokens, stmts, contains_use_strict)) + | _ -> (env, string_tokens, stmts, contains_use_strict) + in + fun env term_fn item_fn -> + let env = with_allow_directive true env in + let (env, string_tokens, stmts, contains_use_strict) = + statement_list env term_fn item_fn ([], [], false) + in + let env = with_allow_directive false env in + List.iter (check env) (List.rev string_tokens); + (env, stmts, contains_use_strict) + + (* 15.2 *) + and module_item env = + let decorators = Object.decorator_list env in + match Peek.token env with + | T_EXPORT -> Statement.export_declaration ~decorators env + | T_IMPORT -> + error_on_decorators env decorators; + let statement = + match Peek.ith_token ~i:1 env with + | T_LPAREN (* import(...) *) + | T_PERIOD (* import.meta *) -> + Statement.expression env + | _ -> Statement.import_declaration env + in + statement + | T_DECLARE when Peek.ith_token ~i:1 env = T_EXPORT -> + error_on_decorators env decorators; + Statement.declare_export_declaration env + | _ -> statement_list_item env ~decorators + + and module_body_with_directives env term_fn = + let (env, directives, _contains_use_strict) = directives env term_fn module_item in + let stmts = module_body ~term_fn env in + (* Prepend the directives *) + List.fold_left (fun acc stmt -> stmt :: acc) stmts directives + + and module_body = + let rec module_item_list env term_fn acc = + match Peek.token env with + | T_EOF -> List.rev acc + | t when term_fn t -> List.rev acc + | _ -> module_item_list env term_fn (module_item env :: acc) + in + (fun ~term_fn env -> module_item_list env term_fn []) + + and statement_list_with_directives ~term_fn env = + let (env, directives, contains_use_strict) = directives env term_fn statement_list_item in + let stmts = statement_list ~term_fn env in + (* Prepend the directives *) + let stmts = List.fold_left (fun acc stmt -> stmt :: acc) stmts directives in + (stmts, contains_use_strict) + + and statement_list = + let rec statements env term_fn acc = + match Peek.token env with + | T_EOF -> List.rev acc + | t when term_fn t -> List.rev acc + | _ -> statements env term_fn (statement_list_item env :: acc) + in + (fun ~term_fn env -> statements env term_fn []) + + and statement_list_item ?(decorators = []) env = + if not (Peek.is_class env) then error_on_decorators env decorators; + let open Statement in + match Peek.token env with + (* Remember kids, these look like statements but they're not + * statements... (see section 13) *) + | T_LET -> let_ env + | T_CONST -> const env + | _ when Peek.is_function env || Peek.is_hook env -> Declaration._function env + | _ when Peek.is_class env -> class_declaration env decorators + | T_INTERFACE -> interface env + | T_DECLARE -> declare env + | T_TYPE -> type_alias env + | T_OPAQUE -> opaque_type env + | T_ENUM when (parse_options env).enums -> Declaration.enum_declaration env + | _ when Peek.is_component env -> Declaration.component env + | _ -> statement env + + and statement ?(allow_sequence = true) env = + let open Statement in + let expression = Statement.expression ~allow_sequence in + match Peek.token env with + | T_EOF -> + error_unexpected ~expected:"the start of a statement" env; + (Peek.loc env, Ast.Statement.Empty { Ast.Statement.Empty.comments = None }) + | T_SEMICOLON -> empty env + | T_LCURLY -> block env + | T_VAR -> var env + | T_BREAK -> break env + | T_CONTINUE -> continue env + | T_DEBUGGER -> debugger env + | T_DO -> do_while env + | T_FOR -> for_ env + | T_IF -> if_ env + | T_RETURN -> return env + | T_SWITCH -> switch env + | T_MATCH + when (parse_options env).pattern_matching + && (not (Peek.ith_is_line_terminator ~i:1 env)) + && Peek.ith_token ~i:1 env = T_LPAREN -> + (match Try.to_parse env Statement.match_statement with + | Try.ParsedSuccessfully m -> m + | Try.FailedToParse -> expression env) + | T_THROW -> throw env + | T_TRY -> try_ env + | T_WHILE -> while_ env + | T_WITH -> with_ env + (* If we see an else then it's definitely an error, but we can probably + * assume that this is a malformed if statement that is missing the if *) + | T_ELSE -> if_ env + (* There are a bunch of tokens that aren't the start of any valid + * statement. We list them here in order to skip over them, rather than + * getting stuck *) + | T_COLON + | T_RPAREN + | T_RCURLY + | T_RBRACKET + | T_COMMA + | T_PERIOD + | T_PLING_PERIOD + | T_ARROW + | T_IN + | T_INSTANCEOF + | T_CATCH + | T_FINALLY + | T_CASE + | T_DEFAULT + | T_EXTENDS + | T_STATIC + | T_EXPORT + (* TODO *) + | T_ELLIPSIS -> + error_unexpected ~expected:"the start of a statement" env; + Eat.token env; + statement env + (* The rest of these patterns handle ExpressionStatement and its negative + lookaheads, which prevent ambiguities. + See https://tc39.github.io/ecma262/#sec-expression-statement *) + | _ when Peek.is_function env || Peek.is_hook env -> + let func = Declaration._function env in + function_as_statement_error_at env (fst func); + func + | T_LET when Peek.ith_token ~i:1 env = T_LBRACKET -> + (* `let [foo]` is ambiguous: either a let binding pattern, or a + member expression, so it is banned. *) + let loc = Loc.btwn (Peek.loc env) (Peek.ith_loc ~i:1 env) in + error_at env (loc, Parse_error.AmbiguousLetBracket); + expression env + (* recover as a member expression *) + | _ when Peek.is_identifier env -> maybe_labeled env + | _ when Peek.is_class env -> + error_unexpected env; + Eat.token env; + expression env + | _ -> expression env + + and expression env = + let start_loc = Peek.loc env in + let expr = Expression.assignment env in + match Peek.token env with + | T_COMMA -> Expression.sequence env ~start_loc [expr] + | _ -> expr + + and expression_or_pattern env = + let start_loc = Peek.loc env in + let expr_or_pattern = Expression.assignment_cover env in + match Peek.token env with + | T_COMMA -> + let expr = Pattern_cover.as_expression env expr_or_pattern in + let seq = Expression.sequence env ~start_loc [expr] in + Cover_expr seq + | _ -> expr_or_pattern + + and conditional = Expression.conditional + + and assignment = Expression.assignment + + and left_hand_side = Expression.left_hand_side + + and object_initializer = Object._initializer + + and object_key = Object.key + + and class_declaration = Object.class_declaration + + and class_expression = Object.class_expression + + and is_assignable_lhs = Expression.is_assignable_lhs + + and number = Expression.number + + and bigint = Expression.bigint + + and identifier_with_type = + let with_loc_helper no_optional restricted_error env = + let name = identifier ~restricted_error env in + let optional = (not no_optional) && Peek.token env = T_PLING in + if optional then ( + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + Expect.token env T_PLING + ); + let annot = Type.annotation_opt env in + Ast.Pattern.Identifier.{ name; optional; annot } + in + fun env ?(no_optional = false) restricted_error -> + with_loc (with_loc_helper no_optional restricted_error) env + + and block_body env = + let start_loc = Peek.loc env in + let leading = Peek.comments env in + Expect.token env T_LCURLY; + let term_fn t = t = T_RCURLY in + let body = statement_list ~term_fn env in + let end_loc = Peek.loc env in + let internal = + if body = [] then + Peek.comments env + else + [] + in + Expect.token env T_RCURLY; + let trailing = Eat.trailing_comments env in + ( Loc.btwn start_loc end_loc, + { + Ast.Statement.Block.body; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + } + ) + + and function_block_body ~expression = + with_loc_extra (fun env -> + let leading = Peek.comments env in + Expect.token env T_LCURLY; + let term_fn t = t = T_RCURLY in + let (body, contains_use_strict) = statement_list_with_directives ~term_fn env in + let internal = + if body = [] then + Peek.comments env + else + [] + in + Expect.token env T_RCURLY; + let trailing = + match (expression, Peek.token env) with + | (true, _) + | (_, (T_RCURLY | T_EOF)) -> + Eat.trailing_comments env + | _ when Peek.is_line_terminator env -> Eat.comments_until_next_line env + | _ -> [] + in + let comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal () + in + ({ Ast.Statement.Block.body; comments }, contains_use_strict) + ) + + and jsx_element_or_fragment = JSX.element_or_fragment ~parent_opening_name:None + + and pattern = Pattern.pattern + + and pattern_from_expr = Pattern.from_expr + + and match_pattern = Match_pattern.match_pattern +end + +(*****************************************************************************) +(* Entry points *) +(*****************************************************************************) +let do_parse env parser fail = + let ast = parser env in + let error_list = filter_duplicate_errors (errors env) in + match error_list with + | e :: es when fail -> raise (Parse_error.Error (e, es)) + | _ -> (ast, error_list) + +let parse_program fail ?(token_sink = None) ?(parse_options = None) filename content = + let env = init_env ~token_sink ~parse_options filename content in + do_parse env Parse.program fail + +let program ?(fail = true) ?(token_sink = None) ?(parse_options = None) content = + parse_program fail ~token_sink ~parse_options None content + +let program_file ?(fail = true) ?(token_sink = None) ?(parse_options = None) content filename = + parse_program fail ~token_sink ~parse_options filename content + +let parse_annot ?(parse_options = None) filename content = + let env = init_env ~token_sink:None ~parse_options filename content in + do_parse env Parse.annot false + +let package_json_file = + let parser env = + let (loc, obj, { if_expr; _ }) = Parse.object_initializer env in + List.iter (error_at env) if_expr; + (loc, obj) + in + fun ?(fail = true) ?(token_sink = None) ?(parse_options = None) content filename -> + let env = init_env ~token_sink ~parse_options filename content in + do_parse env parser fail + +(* even if fail=false, still raises an error on a totally invalid token, since + there's no legitimate fallback. *) +let json_file = + let null_fallback _env = Ast.Expression.NullLiteral None in + let parser env = + match Peek.token env with + | T_LBRACKET + | T_LCURLY + | T_STRING _ + | T_NUMBER _ + | T_TRUE + | T_FALSE + | T_NULL -> + Parse.expression env + | T_MINUS -> + (match Peek.ith_token ~i:1 env with + | T_NUMBER _ -> Parse.expression env + | _ -> + error_unexpected ~expected:"a number" env; + with_loc null_fallback env) + | _ -> + error_unexpected ~expected:"a valid JSON value" env; + with_loc null_fallback env + in + fun ?(fail = true) ?(token_sink = None) ?(parse_options = None) content filename -> + let env = init_env ~token_sink ~parse_options filename content in + do_parse env parser fail + +let jsx_pragma_expression = + let left_hand_side env = + let ast = Parse.left_hand_side (with_no_new true env) in + Expect.token env T_EOF; + ast + in + fun content filename -> + let env = init_env ~token_sink:None ~parse_options:None filename content in + do_parse env left_hand_side true + +let string_is_valid_identifier_name str = + let lexbuf = Sedlexing.Utf8.from_string str in + Flow_lexer.is_valid_identifier_name lexbuf + +(** + * Returns the string and location of the first identifier in [input] for which + * [predicate] holds. + *) +let find_ident ~predicate input = + let env = init_env ~token_sink:None ~parse_options:None None input in + let rec loop token = + match token with + | T_EOF -> None + | _ -> + let loc = Peek.loc env in + (match token with + | T_IDENTIFIER { value = s; _ } when predicate s -> Some (loc, s) + | _ -> + Eat.token env; + loop (Peek.token env)) + in + loop (Peek.token env) diff --git a/compiler/flow_parser/parser/pattern_cover.ml b/compiler/flow_parser/parser/pattern_cover.ml new file mode 100644 index 00000000000..93143df1f82 --- /dev/null +++ b/compiler/flow_parser/parser/pattern_cover.ml @@ -0,0 +1,44 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Parser_common +open Parser_env + +module Cover (Parse : PARSER) : Parser_common.COVER = struct + let as_expression env = function + | Cover_expr expr -> expr + | Cover_patt (expr, { if_expr; if_patt = _ }) -> + List.iter (error_at env) if_expr; + expr + + let as_pattern ?(err = Parse_error.InvalidLHSInAssignment) env cover = + let expr = + match cover with + | Cover_expr expr -> expr + | Cover_patt (expr, { if_expr = _; if_patt }) -> + List.iter (error_at env) if_patt; + expr + in + if not (Parse.is_assignable_lhs expr) then error_at env (fst expr, err); + + (match expr with + | (loc, Flow_ast.Expression.Identifier (_, { Flow_ast.Identifier.name; comments = _ })) + when is_restricted name -> + strict_error_at env (loc, Parse_error.StrictLHSAssignment) + | _ -> ()); + + Parse.pattern_from_expr env expr + + let empty_errors = { if_patt = []; if_expr = [] } + + let cons_error err { if_patt; if_expr } = { if_patt = err :: if_patt; if_expr = err :: if_expr } + + let rev_append_errors a b = + { if_patt = List.rev_append a.if_patt b.if_patt; if_expr = List.rev_append a.if_expr b.if_expr } + + let rev_errors a = { if_patt = List.rev a.if_patt; if_expr = List.rev a.if_expr } +end diff --git a/compiler/flow_parser/parser/pattern_cover.mli b/compiler/flow_parser/parser/pattern_cover.mli new file mode 100644 index 00000000000..4a8a461e162 --- /dev/null +++ b/compiler/flow_parser/parser/pattern_cover.mli @@ -0,0 +1,8 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Cover (_ : Parser_common.PARSER) : Parser_common.COVER diff --git a/compiler/flow_parser/parser/pattern_parser.ml b/compiler/flow_parser/parser/pattern_parser.ml new file mode 100644 index 00000000000..f3a790928df --- /dev/null +++ b/compiler/flow_parser/parser/pattern_parser.ml @@ -0,0 +1,401 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Ast = Flow_ast +open Token +open Parser_common +open Parser_env +open Flow_ast + +let missing_annot env = Ast.Type.Missing (Peek.loc_skip_lookahead env) + +module Pattern (Parse : Parser_common.PARSER) (Type : Parser_common.TYPE) : Parser_common.PATTERN = +struct + (* Reinterpret various expressions as patterns. + * This is not the correct thing to do and is only used for assignment + * expressions. This should be removed and replaced ASAP. + *) + let rec object_from_expr = + let rec properties env acc = + let open Ast.Expression.Object in + function + | [] -> List.rev acc + | Property (loc, prop) :: remaining -> + let acc = + match prop with + | Property.Init { key; value; shorthand } -> + let open Ast.Expression in + let key = + match key with + | Property.StringLiteral s -> Pattern.Object.Property.StringLiteral s + | Property.NumberLiteral n -> Pattern.Object.Property.NumberLiteral n + | Property.BigIntLiteral b -> Pattern.Object.Property.BigIntLiteral b + | Property.Identifier id -> Pattern.Object.Property.Identifier id + | Property.PrivateName _ -> failwith "Internal Error: Found object private prop" + | Property.Computed key -> Pattern.Object.Property.Computed key + in + let (pattern, default) = + match value with + | (_loc, Assignment { Assignment.operator = None; left; right; comments = _ }) -> + (left, Some right) + | _ -> (from_expr env value, None) + in + Pattern.Object.Property + (loc, { Pattern.Object.Property.key; pattern; default; shorthand }) + :: acc + | Property.Method { key = _; value = (loc, _) } -> + error_at env (loc, Parse_error.MethodInDestructuring); + acc + | Property.Get { key = _; value = (loc, _); comments = _ } + | Property.Set { key = _; value = (loc, _); comments = _ } -> + (* these should never happen *) + error_at env (loc, Parse_error.Unexpected "identifier"); + acc + in + properties env acc remaining + | [SpreadProperty (loc, { SpreadProperty.argument; comments })] -> + let acc = + Pattern.Object.RestElement + (loc, { Pattern.RestElement.argument = from_expr env argument; comments }) + :: acc + in + properties env acc [] + | SpreadProperty (loc, _) :: remaining -> + error_at env (loc, Parse_error.PropertyAfterRestElement); + properties env acc remaining + in + fun env (loc, { Ast.Expression.Object.properties = props; comments }) -> + ( loc, + Pattern.( + Object + { Object.properties = properties env [] props; annot = missing_annot env; comments } + ) + ) + + and array_from_expr = + (* Convert an Expression to a Pattern if it is a valid + DestructuringAssignmentTarget, which must be an Object, Array or + IsValidSimpleAssignmentTarget. + #sec-destructuring-assignment-static-semantics-early-errors *) + let assignment_target env ((loc, _) as expr) = + if Parse.is_assignable_lhs expr then + Some (from_expr env expr) + else ( + error_at env (loc, Parse_error.InvalidLHSInAssignment); + None + ) + in + let rec elements env acc = + let open Ast.Expression in + function + | [] -> List.rev acc + | [Array.Spread (loc, { SpreadElement.argument; comments })] -> + (* AssignmentRestElement is a DestructuringAssignmentTarget, see + #prod-AssignmentRestElement *) + let acc = + match assignment_target env argument with + | Some argument -> + Pattern.Array.RestElement (loc, { Pattern.RestElement.argument; comments }) :: acc + | None -> acc + in + elements env acc [] + | Array.Spread (loc, _) :: remaining -> + error_at env (loc, Parse_error.ElementAfterRestElement); + elements env acc remaining + | Array.Expression (loc, Assignment { Assignment.operator = None; left; right; comments = _ }) + :: remaining -> + (* AssignmentElement is a `DestructuringAssignmentTarget Initializer`, see + #prod-AssignmentElement *) + let acc = + Pattern.Array.Element + (loc, { Pattern.Array.Element.argument = left; default = Some right }) + :: acc + in + elements env acc remaining + | Array.Expression expr :: remaining -> + (* AssignmentElement is a DestructuringAssignmentTarget, see + #prod-AssignmentElement *) + let acc = + match assignment_target env expr with + | Some ((loc, _) as expr) -> + let element = + Pattern.Array.Element (loc, { Pattern.Array.Element.argument = expr; default = None }) + in + element :: acc + | None -> acc + in + elements env acc remaining + | Array.Hole loc :: remaining -> elements env (Pattern.Array.Hole loc :: acc) remaining + in + fun env (loc, { Ast.Expression.Array.elements = elems; comments }) -> + ( loc, + Pattern.Array + { Pattern.Array.elements = elements env [] elems; annot = missing_annot env; comments } + ) + + and from_expr env (loc, expr) = + let open Ast.Expression in + match expr with + | Object obj -> object_from_expr env (loc, obj) + | Array arr -> array_from_expr env (loc, arr) + | Identifier ((id_loc, { Identifier.name = string_val; comments = _ }) as name) -> + (* per #sec-destructuring-assignment-static-semantics-early-errors, + it is a syntax error if IsValidSimpleAssignmentTarget of this + IdentifierReference is false. That happens when `string_val` is + "eval" or "arguments" in strict mode. *) + if in_strict_mode env && is_restricted string_val then + error_at env (id_loc, Parse_error.StrictLHSAssignment) + (* per #prod-IdentifierReference, yield is only a valid + IdentifierReference when [~Yield], and await is only valid + when [~Await]. but per #sec-identifiers-static-semantics-early-errors, + they are already invalid in strict mode, which we should have + already errored about when parsing the expression that we're now + converting into a pattern. *) + else if not (in_strict_mode env) then + if allow_yield env && string_val = "yield" then + error_at env (id_loc, Parse_error.YieldAsIdentifierReference) + else if allow_await env && string_val = "await" then + error_at env (id_loc, Parse_error.AwaitAsIdentifierReference); + ( loc, + Pattern.Identifier { Pattern.Identifier.name; annot = missing_annot env; optional = false } + ) + | expr -> (loc, Pattern.Expression (loc, expr)) + + (* Parse object destructuring pattern *) + let rec object_ restricted_error = + let rest_property env = + let leading = Peek.comments env in + let (loc, argument) = + with_loc + (fun env -> + Expect.token env T_ELLIPSIS; + pattern env restricted_error) + env + in + Pattern.Object.RestElement + ( loc, + { Pattern.RestElement.argument; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + in + let property_default env = + match Peek.token env with + | T_ASSIGN -> + Expect.token env T_ASSIGN; + Some (Parse.assignment env) + | _ -> None + in + let rec property env = + if Peek.token env = T_ELLIPSIS then + Some (rest_property env) + else + let start_loc = Peek.loc env in + let raw_key = Parse.object_key env in + match Peek.token env with + | T_COLON -> + Expect.token env T_COLON; + let (loc, (pattern, default)) = + with_loc + ~start_loc + (fun env -> + let pattern = pattern env restricted_error in + let default = property_default env in + (pattern, default)) + env + in + let key = + let open Ast.Expression.Object.Property in + match raw_key with + | (_, StringLiteral lit) -> Pattern.Object.Property.StringLiteral lit + | (_, NumberLiteral lit) -> Pattern.Object.Property.NumberLiteral lit + | (_, BigIntLiteral lit) -> Pattern.Object.Property.BigIntLiteral lit + | (_, Identifier id) -> Pattern.Object.Property.Identifier id + | (_, PrivateName _) -> failwith "Internal Error: Found object private prop" + | (_, Computed key) -> Pattern.Object.Property.Computed key + in + Some Pattern.Object.(Property (loc, Property.{ key; pattern; default; shorthand = false })) + | _ -> + (match raw_key with + | ( _, + Ast.Expression.Object.Property.Identifier + ((id_loc, { Identifier.name = string_val; comments = _ }) as name) + ) -> + (* #sec-identifiers-static-semantics-early-errors *) + if is_reserved string_val then + (* it is a syntax error if `name` is a reserved word other than await or yield *) + error_at env (id_loc, Parse_error.UnexpectedReserved) + else if is_strict_reserved string_val then + (* it is a syntax error if `name` is a strict reserved word, in strict mode *) + strict_error_at env (id_loc, Parse_error.StrictReservedWord); + let (loc, (pattern, default)) = + with_loc + ~start_loc + (fun env -> + let pattern = + ( id_loc, + Pattern.Identifier + { Pattern.Identifier.name; annot = missing_annot env; optional = false } + ) + in + let default = property_default env in + (pattern, default)) + env + in + Some + Pattern.Object.( + Property + ( loc, + { Property.key = Property.Identifier name; pattern; default; shorthand = true } + ) + ) + | _ -> + error_unexpected ~expected:"an identifier" env; + + (* invalid shorthand destructuring *) + None) + (* seen_rest is true when we've seen a rest element. rest_trailing_comma is the location of + * the rest element's trailing command + * Trailing comma: `let { ...rest, } = obj` + * Still invalid, but not a trailing comma: `let { ...rest, x } = obj` *) + and properties env ~seen_rest ~rest_trailing_comma acc = + match Peek.token env with + | T_EOF + | T_RCURLY -> + begin + match rest_trailing_comma with + | Some loc -> error_at env (loc, Parse_error.TrailingCommaAfterRestElement) + | None -> () + end; + List.rev acc + | _ -> + (match property env with + | Some ((Pattern.Object.Property (loc, _) | Pattern.Object.RestElement (loc, _)) as prop) -> + let rest_trailing_comma = + if seen_rest then ( + error_at env (loc, Parse_error.PropertyAfterRestElement); + None + ) else + rest_trailing_comma + in + let (seen_rest, rest_trailing_comma) = + match prop with + | Pattern.Object.RestElement _ -> + ( true, + if Peek.token env = T_COMMA then + Some (Peek.loc env) + else + None + ) + | _ -> (seen_rest, rest_trailing_comma) + in + if Peek.token env <> T_RCURLY then Expect.token env T_COMMA; + properties env ~seen_rest ~rest_trailing_comma (prop :: acc) + | None -> properties env ~seen_rest ~rest_trailing_comma acc) + in + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_LCURLY; + let properties = properties env ~seen_rest:false ~rest_trailing_comma:None [] in + let internal = Peek.comments env in + Expect.token env T_RCURLY; + let trailing = Eat.trailing_comments env in + let annot = + if Peek.token env = T_COLON then + Ast.Type.Available (Type.annotation env) + else + missing_annot env + in + Pattern.Object + { + Pattern.Object.properties; + annot; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + } + ) + + (* Parse array destructuring pattern *) + and array_ restricted_error = + let rec elements env acc = + match Peek.token env with + | T_EOF + | T_RBRACKET -> + List.rev acc + | T_COMMA -> + let loc = Peek.loc env in + Expect.token env T_COMMA; + elements env (Pattern.Array.Hole loc :: acc) + | T_ELLIPSIS -> + let leading = Peek.comments env in + let (loc, argument) = + with_loc + (fun env -> + Expect.token env T_ELLIPSIS; + pattern env restricted_error) + env + in + let element = + Pattern.Array.RestElement + ( loc, + { + Pattern.RestElement.argument; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + in + (* rest elements are always last, the closing ] should be next. but if not, + error and keep going so we recover gracefully by parsing the rest of the + elements. *) + if Peek.token env <> T_RBRACKET then ( + error_at env (loc, Parse_error.ElementAfterRestElement); + if Peek.token env = T_COMMA then Eat.token env + ); + elements env (element :: acc) + | _ -> + let (loc, (pattern, default)) = + with_loc + (fun env -> + let pattern = pattern env restricted_error in + let default = + match Peek.token env with + | T_ASSIGN -> + Expect.token env T_ASSIGN; + Some (Parse.assignment env) + | _ -> None + in + (pattern, default)) + env + in + let element = Pattern.Array.(Element (loc, { Element.argument = pattern; default })) in + if Peek.token env <> T_RBRACKET then Expect.token env T_COMMA; + elements env (element :: acc) + in + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_LBRACKET; + let elements = elements env [] in + let internal = Peek.comments env in + Expect.token env T_RBRACKET; + let annot = + if Peek.token env = T_COLON then + Ast.Type.Available (Type.annotation env) + else + missing_annot env + in + let trailing = Eat.trailing_comments env in + let comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal () + in + Pattern.Array { Pattern.Array.elements; annot; comments } + ) + + and pattern env restricted_error = + match Peek.token env with + | T_LCURLY -> object_ restricted_error env + | T_LBRACKET -> array_ restricted_error env + | _ -> + let (loc, id) = Parse.identifier_with_type env restricted_error in + (loc, Pattern.Identifier id) +end diff --git a/compiler/flow_parser/parser/pattern_parser.mli b/compiler/flow_parser/parser/pattern_parser.mli new file mode 100644 index 00000000000..8d73d0fd0f0 --- /dev/null +++ b/compiler/flow_parser/parser/pattern_parser.mli @@ -0,0 +1,8 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Pattern (_ : Parser_common.PARSER) (_ : Parser_common.TYPE) : Parser_common.PATTERN diff --git a/compiler/flow_parser/parser/relativeLoc.ml b/compiler/flow_parser/parser/relativeLoc.ml new file mode 100644 index 00000000000..3c0f5625fbb --- /dev/null +++ b/compiler/flow_parser/parser/relativeLoc.ml @@ -0,0 +1,35 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type t = + | Same_line of { + start: Loc.position; + column_offset: int; + } + | Diff_line of { + start: Loc.position; + line_offset: int; + column: int; + } + +let of_loc ({ Loc.start = base_pos; _end = pos; _ } : Loc.t) = + let line_offset = pos.Loc.line - base_pos.Loc.line in + if line_offset = 0 then + Same_line { start = base_pos; column_offset = pos.Loc.column - base_pos.Loc.column } + else + Diff_line { start = base_pos; line_offset; column = pos.Loc.column } + +let to_loc relative_loc source : Loc.t = + match relative_loc with + | Same_line { start; column_offset } -> + { + Loc.start; + _end = { Loc.line = start.Loc.line; column = start.Loc.column + column_offset }; + source; + } + | Diff_line { start; line_offset; column } -> + { Loc.start; _end = { Loc.line = start.Loc.line + line_offset; column }; source } diff --git a/compiler/flow_parser/parser/relativeLoc.mli b/compiler/flow_parser/parser/relativeLoc.mli new file mode 100644 index 00000000000..acca0fe8811 --- /dev/null +++ b/compiler/flow_parser/parser/relativeLoc.mli @@ -0,0 +1,27 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +(* + * When we store data to the shared heap, we first marshal it using OCaml's marshaller, then we + * compress it. OCaml's marshaling algorithm uses a more compact representation for smaller + * integers, so it is advantageous to use small integers rather than large ones when serializing to + * the shared heap. + * + * To that end, this utility converts locations so that the end position is stored relative to the + * start position, rather than storing it in absolute terms. The intuition is that the end location + * will always be closer to (or as close as) the start position than to the start of the file, so + * the numbers stored will be smaller and therefore have a more compact representation, on average. + * + * This does not change the in-memory size of the location. It does, however make it smaller to + * serialize. + * *) + +type t + +val of_loc : Loc.t -> t + +val to_loc : t -> File_key.t option -> Loc.t diff --git a/compiler/flow_parser/parser/statement_parser.ml b/compiler/flow_parser/parser/statement_parser.ml new file mode 100644 index 00000000000..2ab0ef51233 --- /dev/null +++ b/compiler/flow_parser/parser/statement_parser.ml @@ -0,0 +1,2321 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Token +open Parser_env +open Flow_ast +open Parser_common +open Comment_attachment + +module Statement + (Parse : PARSER) + (Type : Parser_common.TYPE) + (Declaration : Parser_common.DECLARATION) + (Object : Parser_common.OBJECT) + (Pattern_cover : Parser_common.COVER) + (Expression : Parser_common.EXPRESSION) : Parser_common.STATEMENT = struct + module Enum = Enum_parser.Enum (Parse) + + type for_lhs = + | For_expression of pattern_cover + | For_declaration of (Loc.t * (Loc.t, Loc.t) Ast.Statement.VariableDeclaration.t) + + type semicolon_type = + | Explicit of Loc.t Comment.t list + | Implicit of Comment_attachment.trailing_and_remover_result + + (* FunctionDeclaration is not a valid Statement, but Annex B sometimes allows it. + However, AsyncFunctionDeclaration and GeneratorFunctionDeclaration are never + allowed as statements. We still parse them as statements (and raise an error) to + recover gracefully. *) + let function_as_statement env = + let func = Declaration._function env in + ( if in_strict_mode env then + function_as_statement_error_at env (fst func) + else + let open Ast.Statement in + match func with + | (loc, FunctionDeclaration { Ast.Function.async = true; _ }) -> + error_at env (loc, Parse_error.AsyncFunctionAsStatement) + | (loc, FunctionDeclaration { Ast.Function.generator = true; _ }) -> + error_at env (loc, Parse_error.GeneratorFunctionAsStatement) + | _ -> () + ); + func + + let string_literal env (loc, value, raw, octal) = + if octal then strict_error env Parse_error.StrictOctalLiteral; + let leading = Peek.comments env in + Expect.token env (T_STRING (loc, value, raw, octal)); + let trailing = Eat.trailing_comments env in + ( loc, + { StringLiteral.value; raw; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) + + (* Semicolon insertion is handled here :(. There seem to be 2 cases where + * semicolons are inserted. First, if we reach the EOF. Second, if the next + * token is } or is separated by a LineTerminator. + *) + let semicolon ?(expected = "the token `;`") ?(required = true) env = + match Peek.token env with + | T_EOF + | T_RCURLY -> + Implicit { trailing = Eat.trailing_comments env; remove_trailing = (fun x _ -> x) } + | T_SEMICOLON -> + Eat.token env; + (match Peek.token env with + | T_EOF + | T_RCURLY -> + Explicit (Eat.trailing_comments env) + | _ when Peek.is_line_terminator env -> Explicit (Eat.comments_until_next_line env) + | _ -> Explicit []) + | _ when Peek.is_line_terminator env -> + Implicit (Comment_attachment.trailing_and_remover_after_last_line env) + | _ -> + if required then error_unexpected ~expected env; + Explicit [] + + (* Consumes and returns the trailing comments after the end of a statement. Also returns + a remover that can remove all comments that are not trailing the previous token. + + If a statement is the end of a block or file, all comments are trailing. + Otherwise, if a statement is followed by a new line, only comments on the current + line are trailing. If a statement is not followed by a new line, it does not have + trailing comments as they are instead leading comments for the next statement. *) + let statement_end_trailing_comments env = + match Peek.token env with + | T_EOF + | T_RCURLY -> + { trailing = Eat.trailing_comments env; remove_trailing = (fun x _ -> x) } + | _ when Peek.is_line_terminator env -> + Comment_attachment.trailing_and_remover_after_last_line env + | _ -> Comment_attachment.trailing_and_remover_after_last_loc env + + let variable_declaration_end ~kind env declarations = + match semicolon env with + | Explicit comments -> (comments, declarations) + | Implicit { remove_trailing; _ } -> + (* Remove trailing comments from the last declarator *) + let declarations = + match List.rev declarations with + | [] -> [] + | decl :: decls -> + let decl' = + remove_trailing decl (fun remover decl -> remover#variable_declarator ~kind decl) + in + List.rev (decl' :: decls) + in + ([], declarations) + + let rec empty env = + let loc = Peek.loc env in + let leading = Peek.comments env in + Expect.token env T_SEMICOLON; + let { trailing; _ } = statement_end_trailing_comments env in + ( loc, + Statement.Empty + { Statement.Empty.comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) + + and break env = + let leading = Peek.comments env in + let (loc, (label, trailing)) = + with_loc + (fun env -> + Expect.token env T_BREAK; + let label = + if Peek.token env = T_SEMICOLON || Peek.is_implicit_semicolon env then + None + else + let ((label_loc, { Identifier.name; comments = _ }) as label) = + Parse.identifier env + in + if not (SSet.mem name (labels env)) then + error_at env (label_loc, Parse_error.UnknownLabel name); + Some label + in + let (trailing, label) = + match (semicolon env, label) with + | (Explicit trailing, _) + | (Implicit { trailing; _ }, None) -> + (trailing, label) + | (Implicit { remove_trailing; _ }, Some label) -> + ([], Some (remove_trailing label (fun remover label -> remover#identifier label))) + in + (label, trailing)) + env + in + if label = None && not (in_loop env || in_switch env) then + error_at env (loc, Parse_error.IllegalBreak); + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (loc, Statement.Break { Statement.Break.label; comments }) + + and continue env = + let leading = Peek.comments env in + let (loc, (label, trailing)) = + with_loc + (fun env -> + Expect.token env T_CONTINUE; + let label = + if Peek.token env = T_SEMICOLON || Peek.is_implicit_semicolon env then + None + else + let ((label_loc, { Identifier.name; comments = _ }) as label) = + Parse.identifier env + in + if not (SSet.mem name (labels env)) then + error_at env (label_loc, Parse_error.UnknownLabel name); + Some label + in + let (trailing, label) = + match (semicolon env, label) with + | (Explicit trailing, _) + | (Implicit { trailing; _ }, None) -> + (trailing, label) + | (Implicit { remove_trailing; _ }, Some label) -> + ([], Some (remove_trailing label (fun remover label -> remover#identifier label))) + in + (label, trailing)) + env + in + if not (in_loop env) then error_at env (loc, Parse_error.IllegalContinue); + ( loc, + Statement.Continue + { + Statement.Continue.label; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + and debugger = + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_DEBUGGER; + let pre_semicolon_trailing = + if Peek.token env = T_SEMICOLON then + Eat.trailing_comments env + else + [] + in + let trailing = + match semicolon env with + | Explicit trailing + | Implicit { trailing; _ } -> + pre_semicolon_trailing @ trailing + in + Statement.Debugger + { Statement.Debugger.comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) + + and do_while = + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_DO; + let body = Parse.statement (env |> with_in_loop true) in + (* Annex B allows labelled FunctionDeclarations (see + sec-labelled-function-declarations), but not in IterationStatement + (see sec-semantics-static-semantics-early-errors). *) + if (not (in_strict_mode env)) && is_labelled_function body then + function_as_statement_error_at env (fst body); + let pre_keyword_trailing = Eat.trailing_comments env in + Expect.token env T_WHILE; + let pre_cond_trailing = Eat.trailing_comments env in + Expect.token env T_LPAREN; + let test = Parse.expression env in + Expect.token env T_RPAREN; + let past_cond_trailing = + if Peek.token env = T_SEMICOLON then + Eat.trailing_comments env + else + [] + in + (* The rules of automatic semicolon insertion in ES5 don't mention this, + * but the semicolon after a do-while loop is optional. This is properly + * specified in ES6 *) + let past_cond_trailing = + match semicolon ~required:false env with + | Explicit trailing -> past_cond_trailing @ trailing + | Implicit { trailing; _ } -> trailing + in + let trailing = pre_keyword_trailing @ pre_cond_trailing @ past_cond_trailing in + Statement.DoWhile + { + Statement.DoWhile.body; + test; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + and for_ = + let assert_can_be_forin_or_forof env err = function + | (loc, { Statement.VariableDeclaration.declarations; _ }) -> + (* Only a single declarator is allowed, without an init. So + * something like + * + * for (var x in y) {} + * + * is allowed, but we disallow + * + * for (var x, y in z) {} + * for (var x = 42 in y) {} + *) + (match declarations with + | [(_, { Statement.VariableDeclaration.Declarator.init = None; _ })] -> () + | _ -> error_at env (loc, err)) + in + (* Annex B allows labelled FunctionDeclarations (see + sec-labelled-function-declarations), but not in IterationStatement + (see sec-semantics-static-semantics-early-errors). *) + let assert_not_labelled_function env body = + if (not (in_strict_mode env)) && is_labelled_function body then + function_as_statement_error_at env (fst body) + else + () + in + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_FOR; + let async = allow_await env && Eat.maybe env T_AWAIT in + let leading = leading @ Peek.comments env in + Expect.token env T_LPAREN; + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + let init_starts_with_async = + match Peek.token env with + | T_ASYNC -> true + | _ -> false + in + let (init, errs) = + let env = env |> with_no_in true in + match Peek.token env with + | T_SEMICOLON -> (None, []) + | T_LET when Peek.ith_token env ~i:1 <> T_IN -> + let (loc, (declarations, leading, errs)) = with_loc Declaration.let_ env in + ( Some + (For_declaration + ( loc, + { + Statement.VariableDeclaration.kind = Variable.Let; + declarations; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + ), + errs + ) + | T_CONST -> + let (loc, (declarations, leading, errs)) = with_loc Declaration.const env in + ( Some + (For_declaration + ( loc, + { + Statement.VariableDeclaration.kind = Variable.Const; + declarations; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + ), + errs + ) + | T_VAR -> + let (loc, (declarations, leading, errs)) = with_loc Declaration.var env in + ( Some + (For_declaration + ( loc, + { + Statement.VariableDeclaration.kind = Variable.Var; + declarations; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + ), + errs + ) + | _ -> + let expr = Parse.expression_or_pattern env in + (Some (For_expression expr), []) + in + match Peek.token env with + | T_OF -> + (* This is a for of loop *) + let left = + match init with + | Some (For_declaration decl) -> + assert_can_be_forin_or_forof env Parse_error.InvalidLHSInForOf decl; + Statement.ForOf.LeftDeclaration decl + | Some (For_expression expr) -> + (* #sec-for-in-and-for-of-statements-static-semantics-early-errors *) + let patt = Pattern_cover.as_pattern ~err:Parse_error.InvalidLHSInForOf env expr in + (match ((not async) && init_starts_with_async, patt) with + | ( true, + ( _, + Pattern.Identifier + { + Pattern.Identifier.name = + (id_loc, { Identifier.name = "async"; comments = _ }); + annot = _; + optional = _; + } + ) + ) -> + (* #prod-nLtPS4oB - `for (async of ...)` is forbidden because it is + ambiguous whether it's a for-of with an `async` identifier, or a + regular for loop with an async arrow function with a param named + `of`. We can backtrack, so we know it's a for-of, but the spec + still disallows it. *) + error_at env (id_loc, Parse_error.InvalidLHSInForOf) + | _ -> ()); + Statement.ForOf.LeftPattern patt + | None -> assert false + in + Expect.token env T_OF; + let right = Parse.assignment env in + Expect.token env T_RPAREN; + let body = Parse.statement (env |> with_in_loop true) in + assert_not_labelled_function env body; + Statement.ForOf { Statement.ForOf.left; right; body; await = async; comments } + | T_IN -> + (* This is a for in loop *) + let left = + match init with + | Some (For_declaration decl) -> + assert_can_be_forin_or_forof env Parse_error.InvalidLHSInForIn decl; + Statement.ForIn.LeftDeclaration decl + | Some (For_expression expr) -> + (* #sec-for-in-and-for-of-statements-static-semantics-early-errors *) + let patt = Pattern_cover.as_pattern ~err:Parse_error.InvalidLHSInForIn env expr in + Statement.ForIn.LeftPattern patt + | None -> assert false + in + if async then + (* If `async` is true, this should have been a for-await-of loop, but we + recover by trying to parse like a for-in loop. *) + Expect.token env T_OF + else + Expect.token env T_IN; + let right = Parse.expression env in + Expect.token env T_RPAREN; + let body = Parse.statement (env |> with_in_loop true) in + assert_not_labelled_function env body; + Statement.ForIn { Statement.ForIn.left; right; body; each = false; comments } + | _ -> + (* This is a for loop *) + errs |> List.iter (error_at env); + if async then + (* If `async` is true, this should have been a for-await-of loop, but we + recover by trying to parse like a normal loop. *) + Expect.token env T_OF + else + Expect.token env T_SEMICOLON; + let init = + match init with + | Some (For_declaration decl) -> Some (Statement.For.InitDeclaration decl) + | Some (For_expression expr) -> + Some (Statement.For.InitExpression (Pattern_cover.as_expression env expr)) + | None -> None + in + let test = + match Peek.token env with + | T_SEMICOLON -> None + | _ -> Some (Parse.expression env) + in + Expect.token env T_SEMICOLON; + let update = + match Peek.token env with + | T_RPAREN -> None + | _ -> Some (Parse.expression env) + in + Expect.token env T_RPAREN; + let body = Parse.statement (env |> with_in_loop true) in + assert_not_labelled_function env body; + Statement.For { Statement.For.init; test; update; body; comments } + ) + + and if_ = + (* + * Either the consequent or alternate of an if statement + *) + let if_branch env = + (* Normally this would just be a Statement, but Annex B allows + FunctionDeclarations in non-strict mode. See + sec-functiondeclarations-in-ifstatement-statement-clauses *) + let stmt = + if Peek.is_function env then + function_as_statement env + else + Parse.statement env + in + (* Annex B allows labelled FunctionDeclarations in non-strict mode + (see sec-labelled-function-declarations), but not in IfStatement + (see sec-if-statement-static-semantics-early-errors). *) + if (not (in_strict_mode env)) && is_labelled_function stmt then + function_as_statement_error_at env (fst stmt); + + stmt + in + let alternate env = + let leading = Peek.comments env in + Expect.token env T_ELSE; + let body = if_branch env in + { Statement.If.Alternate.body; comments = Flow_ast_utils.mk_comments_opt ~leading () } + in + with_loc (fun env -> + let pre_if_leading = Peek.comments env in + Expect.token env T_IF; + let pre_cond_leading = Peek.comments env in + let leading = pre_if_leading @ pre_cond_leading in + Expect.token env T_LPAREN; + let test = Parse.expression env in + Expect.token env T_RPAREN; + let consequent = if_branch env in + let alternate = + if Peek.token env = T_ELSE then + Some (with_loc alternate env) + else + None + in + Statement.If + { + Statement.If.test; + consequent; + alternate; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + + and return = + with_loc (fun env -> + if not (in_function env) then error env Parse_error.IllegalReturn; + let leading = Peek.comments env in + let start_loc = Peek.loc env in + Expect.token env T_RETURN; + let trailing = + if Peek.token env = T_SEMICOLON then + Eat.trailing_comments env + else + [] + in + let argument = + if Peek.token env = T_SEMICOLON || Peek.is_implicit_semicolon env then + None + else + Some (Parse.expression env) + in + let return_out = Loc.btwn start_loc (Peek.loc env) in + let (trailing, argument) = + match (semicolon env, argument) with + | (Explicit comments, _) + | (Implicit { trailing = comments; _ }, None) -> + (trailing @ comments, argument) + | (Implicit { remove_trailing; _ }, Some arg) -> + (trailing, Some (remove_trailing arg (fun remover arg -> remover#expression arg))) + in + Statement.Return + { + Statement.Return.argument; + return_out; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + and switch = + let case ~seen_default env = + let leading = Peek.comments env in + let (test, trailing) = + match Peek.token env with + | T_DEFAULT -> + if seen_default then error env Parse_error.MultipleDefaultsInSwitch; + Expect.token env T_DEFAULT; + (None, Eat.trailing_comments env) + | _ -> + Expect.token env T_CASE; + (Some (Parse.expression env), []) + in + let seen_default = seen_default || test = None in + Expect.token env T_COLON; + let { trailing = line_end_trailing; _ } = statement_end_trailing_comments env in + let trailing = trailing @ line_end_trailing in + let term_fn = function + | T_RCURLY + | T_DEFAULT + | T_CASE -> + true + | _ -> false + in + let consequent = Parse.statement_list ~term_fn (env |> with_in_switch true) in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + let case = { Statement.Switch.Case.test; consequent; comments } in + (case, seen_default) + in + let rec case_list env (seen_default, acc) = + match Peek.token env with + | T_EOF + | T_RCURLY -> + List.rev acc + | _ -> + let (case_, seen_default) = with_loc_extra (case ~seen_default) env in + let acc = case_ :: acc in + case_list env (seen_default, acc) + in + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_SWITCH; + Expect.token env T_LPAREN; + let discriminant = Parse.expression env in + Expect.token env T_RPAREN; + Expect.token env T_LCURLY; + let cases = case_list env (false, []) in + Expect.token env T_RCURLY; + let { trailing; _ } = statement_end_trailing_comments env in + Statement.Switch + { + Statement.Switch.discriminant; + cases; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + exhaustive_out = fst discriminant; + } + ) + + and match_statement env = + let open Match in + let case env = + let leading = Peek.comments env in + let pattern = Parse.match_pattern env in + let guard = + if Eat.maybe env T_IF then ( + Expect.token env T_LPAREN; + let test = Parse.expression env in + Expect.token env T_RPAREN; + Some test + ) else + None + in + (* Continue parsing colon until hermes-parser is also updated. *) + if not @@ Eat.maybe env T_COLON then Expect.token env T_ARROW; + let body = Parse.statement ~allow_sequence:false env in + (match Peek.token env with + | T_EOF + | T_RCURLY -> + () + | _ -> ignore @@ Eat.maybe env T_COMMA); + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + { Case.pattern; body; guard; comments } + in + let rec case_list env acc = + match Peek.token env with + | T_EOF + | T_RCURLY -> + List.rev acc + | _ -> case_list env (with_loc case env :: acc) + in + with_loc + (fun env -> + let leading = Peek.comments env in + let match_keyword_loc = Peek.loc env in + Expect.token env T_MATCH; + if Peek.is_line_terminator env then raise Try.Rollback; + let args = Expression.arguments env in + if Peek.is_line_terminator env || not (Eat.maybe env T_LCURLY) then raise Try.Rollback; + let arg = Parser_common.reparse_arguments_as_match_argument env args in + let cases = case_list env [] in + Expect.token env T_RCURLY; + let trailing = Eat.trailing_comments env in + Statement.Match + { + arg; + cases; + match_keyword_loc; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + + and throw = + with_loc (fun env -> + let leading = Peek.comments env in + let start_loc = Peek.loc env in + Expect.token env T_THROW; + if Peek.is_line_terminator env then error_at env (start_loc, Parse_error.NewlineAfterThrow); + let argument = Parse.expression env in + let (trailing, argument) = + match semicolon env with + | Explicit trailing -> (trailing, argument) + | Implicit { remove_trailing; _ } -> + ([], remove_trailing argument (fun remover arg -> remover#expression arg)) + in + let open Statement in + Throw { Throw.argument; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) + + and try_ = + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_TRY; + let block = + let block = Parse.block_body env in + if Peek.token env = T_CATCH then + block_remove_trailing env block + else + block + in + let handler = + match Peek.token env with + | T_CATCH -> + let catch = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_CATCH; + let trailing = Eat.trailing_comments env in + let param = + if Peek.token env = T_LPAREN then ( + Expect.token env T_LPAREN; + let p = Some (Parse.pattern env Parse_error.StrictCatchVariable) in + Expect.token env T_RPAREN; + p + ) else + None + in + let body = Parse.block_body env in + (* Fix trailing comment attachment if catch block is end of statement *) + let body = + if Peek.token env <> T_FINALLY then + let { remove_trailing; _ } = statement_end_trailing_comments env in + remove_trailing body (fun remover (loc, body) -> (loc, remover#block loc body)) + else + body + in + { + Ast.Statement.Try.CatchClause.param; + body; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + in + Some catch + | _ -> None + in + let finalizer = + match Peek.token env with + | T_FINALLY -> + Expect.token env T_FINALLY; + let (loc, body) = Parse.block_body env in + let { remove_trailing; _ } = statement_end_trailing_comments env in + let body = remove_trailing body (fun remover body -> remover#block loc body) in + Some (loc, body) + | _ -> None + in + (* No catch or finally? That's an error! *) + if handler = None && finalizer = None then + error_at env (fst block, Parse_error.NoCatchOrFinally); + + Statement.Try + { + Statement.Try.block; + handler; + finalizer; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + + and var = + with_loc (fun env -> + let kind = Variable.Var in + let (declarations, leading, errs) = Declaration.var env in + let (trailing, declarations) = variable_declaration_end ~kind env declarations in + errs |> List.iter (error_at env); + Statement.VariableDeclaration + { + Statement.VariableDeclaration.kind; + declarations; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + and const = + with_loc (fun env -> + let kind = Variable.Const in + let (declarations, leading, errs) = Declaration.const env in + let (trailing, declarations) = variable_declaration_end ~kind env declarations in + errs |> List.iter (error_at env); + Statement.VariableDeclaration + { + Statement.VariableDeclaration.kind; + declarations; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + and let_ = + with_loc (fun env -> + let kind = Variable.Let in + let (declarations, leading, errs) = Declaration.let_ env in + let (trailing, declarations) = variable_declaration_end ~kind env declarations in + errs |> List.iter (error_at env); + Statement.VariableDeclaration + { + Statement.VariableDeclaration.kind; + declarations; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + + and while_ = + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_WHILE; + let leading = leading @ Peek.comments env in + Expect.token env T_LPAREN; + let test = Parse.expression env in + Expect.token env T_RPAREN; + let body = Parse.statement (env |> with_in_loop true) in + (* Annex B allows labelled FunctionDeclarations in non-strict mode + (see sec-labelled-function-declarations), but not in IterationStatement + (see sec-semantics-static-semantics-early-errors). *) + if (not (in_strict_mode env)) && is_labelled_function body then + function_as_statement_error_at env (fst body); + Statement.While + { Statement.While.test; body; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + + and with_ env = + let (loc, stmt) = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_WITH; + let leading = leading @ Peek.comments env in + Expect.token env T_LPAREN; + let _object = Parse.expression env in + Expect.token env T_RPAREN; + let body = Parse.statement env in + (* Annex B allows labelled FunctionDeclarations in non-strict mode + (see sec-labelled-function-declarations), but not in WithStatement + (see sec-with-statement-static-semantics-early-errors). *) + if (not (in_strict_mode env)) && is_labelled_function body then + function_as_statement_error_at env (fst body); + Statement.With + { Statement.With._object; body; comments = Flow_ast_utils.mk_comments_opt ~leading () }) + env + in + strict_error_at env (loc, Parse_error.StrictModeWith); + (loc, stmt) + + and block env = + let (loc, block) = Parse.block_body env in + let { remove_trailing; _ } = statement_end_trailing_comments env in + let block = remove_trailing block (fun remover block -> remover#block loc block) in + (loc, Statement.Block block) + + and maybe_labeled = + with_loc (fun env -> + let leading = Peek.comments env in + match (Parse.expression env, Peek.token env) with + | ((loc, Ast.Expression.Identifier label), T_COLON) -> + let (_, { Identifier.name; comments = _ }) = label in + Expect.token env T_COLON; + if SSet.mem name (labels env) then + error_at env (loc, Parse_error.Redeclaration ("Label", name)); + let env = add_label env name in + let body = + (* labelled FunctionDeclarations are allowed in non-strict mode + (see #sec-labelled-function-declarations) *) + if Peek.is_function env then + function_as_statement env + else + Parse.statement env + in + Statement.Labeled + { Statement.Labeled.label; body; comments = Flow_ast_utils.mk_comments_opt ~leading () } + | (expression, _) -> + let (trailing, expression) = + match semicolon ~expected:"the end of an expression statement (`;`)" env with + | Explicit comments -> (comments, expression) + | Implicit { remove_trailing; _ } -> + ([], remove_trailing expression (fun remover expr -> remover#expression expr)) + in + let open Statement in + Expression + { + Expression.expression; + directive = None; + comments = Flow_ast_utils.mk_comments_opt ~trailing (); + } + ) + + and expression ?(allow_sequence = true) = + with_loc (fun env -> + let expression = + if allow_sequence then + Parse.expression env + else + Parse.assignment env + in + let (trailing, expression) = + match semicolon ~expected:"the end of an expression statement (`;`)" env with + | Explicit comments -> (comments, expression) + | Implicit { remove_trailing; _ } -> + ([], remove_trailing expression (fun remover expr -> remover#expression expr)) + in + let directive = + if allow_directive env then + match expression with + | (_, Ast.Expression.StringLiteral { Ast.StringLiteral.raw; _ }) -> + (* the parser may recover from errors and generate unclosed strings, where + the opening quote should be reliable but the closing one might not exist. + be defensive. *) + if String.length raw > 1 && raw.[0] = raw.[String.length raw - 1] then + Some (String.sub raw 1 (String.length raw - 2)) + else + None + | _ -> None + else + None + in + Statement.Expression + { + Statement.Expression.expression; + directive; + comments = Flow_ast_utils.mk_comments_opt ~trailing (); + } + ) + + and type_alias_helper ~leading env = + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAlias; + let leading = leading @ Peek.comments env in + Expect.token env T_TYPE; + Eat.push_lex_mode env Lex_mode.TYPE; + let id = + let id = Type.type_identifier env in + if Peek.token env = T_LESS_THAN then + id_remove_trailing env id + else + id + in + let tparams = Type.type_params env in + Expect.token env T_ASSIGN; + let right = Type._type env in + Eat.pop_lex_mode env; + let (trailing, right) = + match semicolon env with + | Explicit comments -> (comments, right) + | Implicit { remove_trailing; _ } -> + ([], remove_trailing right (fun remover right -> remover#type_ right)) + in + + { + Statement.TypeAlias.id; + tparams; + right; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + + and declare_type_alias env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let type_alias = type_alias_helper ~leading env in + Statement.DeclareTypeAlias type_alias) + env + + (** Type aliases squeeze into an unambiguous unused portion of the grammar: `type` is not a + reserved word, so `type T` is otherwise two identifiers in a row and that's never valid JS. + However, if there's a line separator between the two, ASI makes it valid JS, so line + separators are disallowed. *) + and type_alias env = + if Peek.ith_is_identifier ~i:1 env && not (Peek.ith_is_implicit_semicolon ~i:1 env) then + let (loc, type_alias) = with_loc (type_alias_helper ~leading:[]) env in + (loc, Statement.TypeAlias type_alias) + else + Parse.statement env + + and opaque_type_helper ?(declare = false) ~leading env = + if not (should_parse_types env) then error env Parse_error.UnexpectedOpaqueTypeAlias; + let leading_opaque = leading @ Peek.comments env in + Expect.token env T_OPAQUE; + let leading_type = Peek.comments env in + Expect.token env T_TYPE; + let leading = leading_opaque @ leading_type in + Eat.push_lex_mode env Lex_mode.TYPE; + let id = + let id = Type.type_identifier env in + if Peek.token env = T_LESS_THAN then + id_remove_trailing env id + else + id + in + let tparams = Type.type_params env in + let supertype = + match Peek.token env with + | T_COLON -> + Expect.token env T_COLON; + Some (Type._type env) + | _ -> None + in + let impltype = + if declare then + match Peek.token env with + | T_ASSIGN -> + error env Parse_error.DeclareOpaqueTypeInitializer; + Eat.token env; + if Peek.token env = T_SEMICOLON || Peek.is_implicit_semicolon env then + None + else + Some (Type._type env) + | _ -> None + else ( + Expect.token env T_ASSIGN; + Some (Type._type env) + ) + in + Eat.pop_lex_mode env; + let (trailing, id, tparams, supertype, impltype) = + match (semicolon env, tparams, supertype, impltype) with + (* opaque type Foo = Bar; *) + | (Explicit comments, _, _, _) -> (comments, id, tparams, supertype, impltype) + (* opaque type Foo = Bar *) + | (Implicit { remove_trailing; _ }, _, _, Some impl) -> + ( [], + id, + tparams, + supertype, + Some (remove_trailing impl (fun remover impl -> remover#type_ impl)) + ) + (* opaque type Foo: Super *) + | (Implicit { remove_trailing; _ }, _, Some super, None) -> + ( [], + id, + tparams, + Some (remove_trailing super (fun remover super -> remover#type_ super)), + None + ) + (* opaque type Foo *) + | (Implicit { remove_trailing; _ }, Some tparams, None, None) -> + ( [], + id, + Some + (remove_trailing tparams (fun remover tparams -> + remover#type_params ~kind:Flow_ast_mapper.OpaqueTypeTP tparams + ) + ), + None, + None + ) + (* declare opaque type Foo *) + | (Implicit { remove_trailing; _ }, None, None, None) -> + ([], remove_trailing id (fun remover id -> remover#identifier id), None, None, None) + in + + { + Statement.OpaqueType.id; + tparams; + impltype; + supertype; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + + and declare_opaque_type env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let opaque_t = opaque_type_helper ~declare:true ~leading env in + Statement.DeclareOpaqueType opaque_t) + env + + and opaque_type env = + match Peek.ith_token ~i:1 env with + | T_TYPE -> + let (loc, opaque_t) = with_loc (opaque_type_helper ~declare:false ~leading:[]) env in + (loc, Statement.OpaqueType opaque_t) + | _ -> Parse.statement env + + and interface_helper ~leading env = + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeInterface; + let leading = leading @ Peek.comments env in + Expect.token env T_INTERFACE; + let id = + let id = Type.type_identifier env in + if Peek.token env = T_EXTENDS then + id + else + id_remove_trailing env id + in + let tparams = + let tparams = Type.type_params env in + if Peek.token env = T_EXTENDS then + tparams + else + type_params_remove_trailing env ~kind:Flow_ast_mapper.InterfaceTP tparams + in + let (extends, body) = Type.interface_helper env in + let { remove_trailing; _ } = statement_end_trailing_comments env in + let body = + remove_trailing body (fun remover (loc, body) -> (loc, remover#object_type loc body)) + in + + { + Statement.Interface.id; + tparams; + body; + extends; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + + and declare_interface env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let iface = interface_helper ~leading env in + Statement.DeclareInterface iface) + env + + and interface env = + (* disambiguate between a value named `interface`, like `var interface = 1; interface++`, + and an interface declaration like `interface Foo {}`.` *) + if Peek.ith_is_identifier_name ~i:1 env then + let (loc, iface) = with_loc (interface_helper ~leading:[]) env in + (loc, Statement.InterfaceDeclaration iface) + else + expression env + + and declare_class = + let rec mixins env acc = + let super = Type.generic env in + let acc = super :: acc in + match Peek.token env with + | T_COMMA -> + Expect.token env T_COMMA; + mixins env acc + | _ -> List.rev acc + (* This is identical to `interface`, except that mixins are allowed *) + in + fun ~leading env -> + let env = env |> with_strict true in + let leading = leading @ Peek.comments env in + Expect.token env T_CLASS; + let id = + let id = Parse.identifier env in + match Peek.token env with + | T_LESS_THAN + | T_LCURLY -> + id_remove_trailing env id + | _ -> id + in + let tparams = + let tparams = Type.type_params env in + match Peek.token env with + | T_LCURLY -> type_params_remove_trailing env ~kind:Flow_ast_mapper.DeclareClassTP tparams + | _ -> tparams + in + let extends = + if Eat.maybe env T_EXTENDS then + let extends = Type.generic env in + match Peek.token env with + | T_LCURLY -> Some (generic_type_remove_trailing env extends) + | _ -> Some extends + else + None + in + let mixins = + match Peek.token env with + | T_IDENTIFIER { raw = "mixins"; _ } -> + Eat.token env; + let mixins = mixins env [] in + (match Peek.token env with + | T_LCURLY -> generic_type_list_remove_trailing env mixins + | _ -> mixins) + | _ -> [] + in + let implements = + match Peek.token env with + | T_IMPLEMENTS -> + let implements = Object.class_implements env ~attach_leading:false in + (match Peek.token env with + | T_LCURLY -> Some (class_implements_remove_trailing env implements) + | _ -> Some implements) + | _ -> None + in + let body = Type._object ~is_class:true env in + let { remove_trailing; _ } = statement_end_trailing_comments env in + let body = + remove_trailing body (fun remover (loc, body) -> (loc, remover#object_type loc body)) + in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareClass.{ id; tparams; body; extends; mixins; implements; comments } + + and declare_class_statement env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let fn = declare_class ~leading env in + Statement.DeclareClass fn) + env + + and declare_component ~leading env = + let leading = leading @ Peek.comments env in + Expect.identifier env "component"; + let id = + id_remove_trailing + env + (* Components should have at least the same strictness as functions *) + (Parse.identifier ~restricted_error:Parse_error.StrictFunctionName env) + in + let tparams = + type_params_remove_trailing env ~kind:Flow_ast_mapper.DeclareComponentTP (Type.type_params env) + in + let params = Type.component_param_list env in + let (params, renders) = + if Peek.is_renders_ident env then + let renders = Type.renders_annotation_opt env in + let renders = component_renders_annotation_remove_trailing env renders in + (params, renders) + else + let missing_annotation = Type.renders_annotation_opt env in + (params, missing_annotation) + in + + let (trailing, renders) = + match semicolon env with + | Explicit comments -> (comments, renders) + | Implicit { remove_trailing; _ } -> + ( [], + remove_trailing renders (fun remover annot -> remover#component_renders_annotation annot) + ) + in + { + Statement.DeclareComponent.id; + params; + renders; + tparams; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + + and declare_component_statement env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let component = declare_component ~leading env in + Statement.DeclareComponent component) + env + + and declare_enum env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let enum = Enum.declaration ~leading env in + Statement.DeclareEnum enum) + env + + and declare_function ~async ?(leading = []) env = + let leading = leading @ Peek.comments env in + let effect_ = + match Peek.token env with + | T_FUNCTION -> + Eat.token env; + Function.Arbitrary + | T_IDENTIFIER { raw = "hook"; _ } when not async -> + Eat.token env; + Function.Hook + | t -> + Expect.error env t; + Function.Arbitrary + in + let id = id_remove_trailing env (Parse.identifier env) in + let annot = + with_loc + (fun env -> + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.DeclareFunctionTP + (Type.type_params env) + in + let params = Type.function_param_list env in + Expect.token env T_COLON; + Eat.push_lex_mode env Lex_mode.TYPE; + let return = + if is_start_of_type_guard env && effect_ <> Function.Hook then + Ast.Type.Function.TypeGuard (Type.type_guard env) + else + let return = Type._type env in + let has_predicate = Peek.token env = T_CHECKS in + if has_predicate && effect_ <> Function.Hook then + Ast.Type.Function.TypeAnnotation (type_remove_trailing env return) + else + Ast.Type.Function.TypeAnnotation return + in + Eat.pop_lex_mode env; + Ast.Type.(Function { Function.params; return; tparams; comments = None; effect_ })) + env + in + let predicate = Type.predicate_opt env in + let (trailing, annot, predicate) = + match (semicolon env, predicate) with + | (Explicit comments, _) -> (comments, annot, predicate) + | (Implicit { remove_trailing; _ }, None) -> + ([], remove_trailing annot (fun remover annot -> remover#type_ annot), None) + | (Implicit { remove_trailing; _ }, Some pred) -> + ([], annot, Some (remove_trailing pred (fun remover pred -> remover#predicate pred))) + in + let annot = (fst annot, annot) in + + { + Statement.DeclareFunction.id; + annot; + predicate; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + + and declare_function_statement env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let async = + match Peek.token env with + | T_ASYNC -> + error env Parse_error.DeclareAsync; + Expect.token env T_ASYNC; + true + | _ -> false + in + let fn = declare_function ~async ~leading env in + Statement.DeclareFunction fn) + env + + and declare_var ~kind env leading = + let leading = leading @ Peek.comments env in + (match kind with + | Ast.Variable.Var -> Expect.token env T_VAR + | Ast.Variable.Let -> Expect.token env T_LET + | Ast.Variable.Const -> Expect.token env T_CONST); + let name = Parse.identifier ~restricted_error:Parse_error.StrictVarName env in + let annot = Type.annotation env in + let (trailing, name, annot) = + match semicolon env with + (* declare var x; *) + | Explicit trailing -> (trailing, name, annot) + (* declare var x *) + | Implicit { remove_trailing; _ } -> + ([], name, remove_trailing annot (fun remover annot -> remover#type_annotation annot)) + in + + { + Statement.DeclareVariable.id = name; + annot; + kind; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + + and declare_var_statement ~kind env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let var = declare_var ~kind env leading in + Statement.DeclareVariable var) + env + + and declare_module_or_namespace_body env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LCURLY; + let body = + Parse.module_body + ~term_fn:(function + | T_RCURLY -> true + | _ -> false) + env + in + let internal = + if body = [] then + Peek.comments env + else + [] + in + Expect.token env T_RCURLY; + let { trailing; _ } = statement_end_trailing_comments env in + let comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal () + in + { Statement.Block.body; comments }) + env + + and declare_module = + let declare_module_ ~leading env = + let id = + match Peek.token env with + | T_STRING str -> + Statement.DeclareModule.Literal + (string_literal_remove_trailing env (string_literal env str)) + | _ -> Statement.DeclareModule.Identifier (id_remove_trailing env (Parse.identifier env)) + in + let body = declare_module_or_namespace_body env in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.(DeclareModule DeclareModule.{ id; body; comments }) + in + fun env -> + let start_loc = Peek.loc env in + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let leading = leading @ Peek.comments env in + Expect.identifier env "module"; + if Peek.token env = T_PERIOD then + with_loc ~start_loc (declare_module_exports ~leading) env + else + with_loc ~start_loc (declare_module_ ~leading) env + + and declare_namespace = + let declare_namespace_ ~leading ~global env = + let id = id_remove_trailing env (Parse.identifier env) in + let id = + if global then + Statement.DeclareNamespace.Global id + else + Statement.DeclareNamespace.Local id + in + let body = declare_module_or_namespace_body env in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.(DeclareNamespace DeclareNamespace.{ id; body; comments }) + in + fun env ~global -> + let start_loc = Peek.loc env in + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let leading = leading @ Peek.comments env in + if not global then Expect.identifier env "namespace"; + with_loc ~start_loc (declare_namespace_ ~global ~leading) env + + and declare_module_exports ~leading env = + let leading_period = Peek.comments env in + Expect.token env T_PERIOD; + let leading_exports = Peek.comments env in + Expect.identifier env "exports"; + let leading_annot = Peek.comments env in + let leading = List.concat [leading; leading_period; leading_exports; leading_annot] in + let annot = Type.annotation env in + let (annot, trailing) = + match semicolon env with + | Explicit trailing -> (annot, trailing) + | Implicit { remove_trailing; _ } -> + (remove_trailing annot (fun remover annot -> remover#type_annotation annot), []) + in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Statement.DeclareModuleExports { Statement.DeclareModuleExports.annot; comments } + + and declare ?(in_module_or_namespace = false) env = + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeDeclaration; + + (* eventually, just emit a wrapper AST node *) + match Peek.ith_token ~i:1 env with + | T_CLASS -> declare_class_statement env + | T_ENUM when (parse_options env).enums -> declare_enum env + | T_INTERFACE -> declare_interface env + | T_TYPE -> + (match Peek.token env with + | T_IMPORT when in_module_or_namespace -> import_declaration env + | _ -> declare_type_alias env) + | T_OPAQUE -> declare_opaque_type env + | T_TYPEOF when Peek.token env = T_IMPORT -> import_declaration env + | T_FUNCTION + | T_ASYNC -> + declare_function_statement env + | T_IDENTIFIER { raw = "hook"; _ } when (parse_options env).components -> + declare_function_statement env + | T_VAR -> declare_var_statement ~kind:Ast.Variable.Var env + | T_LET -> declare_var_statement ~kind:Ast.Variable.Let env + | T_CONST -> declare_var_statement ~kind:Ast.Variable.Const env + | T_EXPORT when in_module_or_namespace -> declare_export_declaration env + | T_IDENTIFIER { raw = "module"; _ } -> declare_module env + | T_IDENTIFIER { raw = "global"; _ } -> declare_namespace ~global:true env + | T_IDENTIFIER { raw = "namespace"; _ } -> declare_namespace ~global:false env + | T_IDENTIFIER { raw = "component"; _ } when (parse_options env).components -> + declare_component_statement env + | _ when in_module_or_namespace -> + (match Peek.token env with + | T_IMPORT -> import_declaration env + | _ -> + (* Oh boy, found some bad stuff in a declare module. Let's just + * pretend it's a declare var (arbitrary choice) *) + declare_var_statement ~kind:Ast.Variable.Var env) + | _ -> Parse.statement env + + and export_source env = + Expect.identifier env "from"; + match Peek.token env with + | T_STRING str -> string_literal env str + | _ -> + (* Just make up a string for the error case *) + let ret = (Peek.loc env, { StringLiteral.value = ""; raw = ""; comments = None }) in + error_unexpected ~expected:"a string" env; + ret + + and export_source_and_semicolon env = + let (source_loc, source) = export_source env in + match semicolon env with + | Explicit trailing -> ((source_loc, source), trailing) + | Implicit { remove_trailing; _ } -> + ( ( source_loc, + remove_trailing source (fun remover source -> remover#string_literal source_loc source) + ), + [] + ) + + and export_specifiers ?(preceding_comma = true) env specifiers = + match Peek.token env with + | T_EOF + | T_RCURLY -> + List.rev specifiers + | _ -> + if not preceding_comma then error env Parse_error.ExportSpecifierMissingComma; + let specifier = + with_loc + (fun env -> + let local = identifier_name env in + let exported = + match Peek.token env with + | T_IDENTIFIER { raw = "as"; _ } -> + Eat.token env; + Some (identifier_name env) + | _ -> None + in + { + Statement.ExportNamedDeclaration.ExportSpecifier.local; + exported; + from_remote = false; + imported_name_def_loc = None; + }) + env + in + let preceding_comma = Eat.maybe env T_COMMA in + export_specifiers ~preceding_comma env (specifier :: specifiers) + + and assert_export_specifier_identifiers env specifiers = + List.iter + (function + | ( _, + { + Statement.ExportNamedDeclaration.ExportSpecifier.local = id; + exported = _; + from_remote = _; + imported_name_def_loc = _; + } + ) -> + assert_identifier_name_is_identifier ~restricted_error:Parse_error.StrictVarName env id) + specifiers + + and export_declaration ~decorators env = + let env = env |> with_strict true |> with_in_export true in + let leading = Peek.comments env in + let start_loc = Peek.loc env in + Expect.token env T_EXPORT; + match Peek.token env with + | T_DEFAULT -> + (* export default ... *) + with_loc + ~start_loc + (fun env -> + let open Statement.ExportDefaultDeclaration in + let leading = leading @ Peek.comments env in + let (default, ()) = with_loc (fun env -> Expect.token env T_DEFAULT) env in + let env = with_in_export_default true env in + let (declaration, trailing) = + if Peek.is_function env || Peek.is_hook env then + (* export default [async] function [foo] (...) { ... } *) + let fn = Declaration._function env in + (Declaration fn, []) + else if Peek.is_class env then + (* export default class foo { ... } *) + let _class = Object.class_declaration env decorators in + (Declaration _class, []) + else if Peek.token env = T_ENUM then + (* export default enum foo { ... } *) + (Declaration (Declaration.enum_declaration env), []) + else if Peek.is_component env then + (* export default component foo { ... } *) + (Declaration (Declaration.component env), []) + else + (* export default [assignment expression]; *) + let expr = Parse.assignment env in + let (expr, trailing) = + match semicolon env with + | Explicit trailing -> (expr, trailing) + | Implicit { remove_trailing; _ } -> + (remove_trailing expr (fun remover expr -> remover#expression expr), []) + in + (Expression expr, trailing) + in + Statement.ExportDefaultDeclaration + { + default; + declaration; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + | T_TYPE when Peek.ith_token ~i:1 env <> T_LCURLY -> + (* export type ... *) + with_loc + ~start_loc + (fun env -> + let open Statement.ExportNamedDeclaration in + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeExport; + match Peek.ith_token ~i:1 env with + | T_MULT -> + Expect.token env T_TYPE; + let specifier_loc = Peek.loc env in + Expect.token env T_MULT; + let (source, trailing) = export_source_and_semicolon env in + Statement.ExportNamedDeclaration + { + declaration = None; + specifiers = Some (ExportBatchSpecifier (specifier_loc, None)); + source = Some source; + export_kind = Statement.ExportType; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + | T_ENUM -> + error env Parse_error.EnumInvalidExport; + Expect.token env T_TYPE; + Statement.ExportNamedDeclaration + { + declaration = None; + specifiers = None; + source = None; + export_kind = Statement.ExportType; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + | _ -> + let (loc, type_alias) = with_loc (type_alias_helper ~leading:[]) env in + let type_alias = (loc, Statement.TypeAlias type_alias) in + Statement.ExportNamedDeclaration + { + declaration = Some type_alias; + specifiers = None; + source = None; + export_kind = Statement.ExportType; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | T_OPAQUE -> + (* export opaque type ... *) + with_loc + ~start_loc + (fun env -> + let open Statement.ExportNamedDeclaration in + let (loc, opaque_t) = with_loc (opaque_type_helper ~leading:[]) env in + let opaque_t = (loc, Statement.OpaqueType opaque_t) in + Statement.ExportNamedDeclaration + { + declaration = Some opaque_t; + specifiers = None; + source = None; + export_kind = Statement.ExportType; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | T_INTERFACE -> + (* export interface I { ... } *) + with_loc + ~start_loc + (fun env -> + let open Statement.ExportNamedDeclaration in + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeExport; + let interface = + let (loc, iface) = with_loc (interface_helper ~leading:[]) env in + (loc, Statement.InterfaceDeclaration iface) + in + Statement.ExportNamedDeclaration + { + declaration = Some interface; + specifiers = None; + source = None; + export_kind = Statement.ExportType; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | _ when Peek.is_class env -> + with_loc + ~start_loc + (fun env -> + let stmt = Object.class_declaration env decorators in + Statement.ExportNamedDeclaration + { + Statement.ExportNamedDeclaration.declaration = Some stmt; + specifiers = None; + source = None; + export_kind = Statement.ExportValue; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | _ when Peek.is_function env || Peek.is_hook env -> + with_loc + ~start_loc + (fun env -> + error_on_decorators env decorators; + let stmt = Declaration._function env in + Statement.ExportNamedDeclaration + { + Statement.ExportNamedDeclaration.declaration = Some stmt; + specifiers = None; + source = None; + export_kind = Statement.ExportValue; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | T_LET + | T_CONST + | T_VAR -> + with_loc + ~start_loc + (fun env -> + let stmt = Parse.statement_list_item env ~decorators in + Statement.ExportNamedDeclaration + { + Statement.ExportNamedDeclaration.declaration = Some stmt; + specifiers = None; + source = None; + export_kind = Statement.ExportValue; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | T_ENUM when (parse_options env).enums -> + with_loc + ~start_loc + (fun env -> + let stmt = Parse.statement_list_item env ~decorators in + Statement.ExportNamedDeclaration + { + Statement.ExportNamedDeclaration.declaration = Some stmt; + specifiers = None; + source = None; + export_kind = Statement.ExportValue; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + (* export component *) + | _ when Peek.is_component env -> + with_loc + ~start_loc + (fun env -> + let stmt = Declaration.component env in + Statement.ExportNamedDeclaration + { + Statement.ExportNamedDeclaration.declaration = Some stmt; + specifiers = None; + source = None; + export_kind = Statement.ExportValue; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | T_MULT -> + with_loc + ~start_loc + (fun env -> + let open Statement.ExportNamedDeclaration in + let loc = Peek.loc env in + Expect.token env T_MULT; + let local_name = + match Peek.token env with + | T_IDENTIFIER { raw = "as"; _ } -> + Eat.token env; + Some (identifier_name env) + | _ -> None + in + let specifiers = Some (ExportBatchSpecifier (loc, local_name)) in + let (source, trailing) = export_source_and_semicolon env in + Statement.ExportNamedDeclaration + { + declaration = None; + specifiers; + source = Some source; + export_kind = Statement.ExportValue; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + | _ -> + let open Statement.ExportNamedDeclaration in + let export_kind = + if Eat.maybe env T_TYPE then + Statement.ExportType + else + Statement.ExportValue + in + if Eat.maybe env T_LCURLY then + with_loc + ~start_loc + (fun env -> + let specifiers = export_specifiers env [] in + Expect.token env T_RCURLY; + let (source, trailing, specifiers) = + match Peek.token env with + | T_IDENTIFIER { raw = "from"; _ } -> + let (source, trailing) = export_source_and_semicolon env in + let specifiers = + List.map + (fun (loc, s) -> + ( loc, + { + s with + Statement.ExportNamedDeclaration.ExportSpecifier.from_remote = true; + } + )) + specifiers + in + (Some source, trailing, specifiers) + | _ -> + assert_export_specifier_identifiers env specifiers; + let trailing = + match semicolon env with + | Explicit trailing -> trailing + | Implicit { trailing; _ } -> trailing + in + (None, trailing, specifiers) + in + Statement.ExportNamedDeclaration + { + declaration = None; + specifiers = Some (ExportSpecifiers specifiers); + source; + export_kind; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + else ( + (* error. recover by ignoring the `export` *) + error_unexpected ~expected:"a declaration, statement or export specifiers" env; + Parse.statement_list_item env ~decorators + ) + + and declare_export_declaration env = + with_loc + (fun env -> + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeDeclaration; + let leading = Peek.comments env in + Expect.token env T_DECLARE; + let env = env |> with_strict true |> with_in_export true in + let leading = leading @ Peek.comments env in + Expect.token env T_EXPORT; + Statement.DeclareExportDeclaration.( + match Peek.token env with + | T_DEFAULT -> + (* declare export default ... *) + let leading = leading @ Peek.comments env in + let (default, ()) = with_loc (fun env -> Expect.token env T_DEFAULT) env in + let env = with_in_export_default true env in + let (declaration, trailing) = + match Peek.token env with + | T_FUNCTION -> + (* declare export default function foo (...): ... *) + let fn = with_loc (declare_function ~async:false) env in + (Some (Function fn), []) + | T_CLASS -> + (* declare export default class foo { ... } *) + let class_ = with_loc (declare_class ~leading:[]) env in + (Some (Class class_), []) + | T_IDENTIFIER { raw = "component"; _ } when (parse_options env).components -> + (* declare export default component Foo() { ... } *) + let component = with_loc (declare_component ~leading:[]) env in + (Some (Component component), []) + | T_IDENTIFIER { raw = "hook"; _ } when (parse_options env).components -> + (* declare export default hook foo (...): ... *) + let fn = with_loc (declare_function ~async:false) env in + (Some (Function fn), []) + | _ -> + (* declare export default [type]; *) + let type_ = Type._type env in + let (type_, trailing) = + match semicolon env with + | Explicit trailing -> (type_, trailing) + | Implicit { remove_trailing; _ } -> + (remove_trailing type_ (fun remover type_ -> remover#type_ type_), []) + in + (Some (DefaultType type_), trailing) + in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Statement.DeclareExportDeclaration + { default = Some default; declaration; specifiers = None; source = None; comments } + | T_LET + | T_CONST + | T_VAR + | T_CLASS + | T_FUNCTION -> + let declaration = + match Peek.token env with + | T_FUNCTION -> + (* declare export function foo (...): ... *) + let fn = with_loc (declare_function ~async:false) env in + Some (Function fn) + | T_CLASS -> + (* declare export class foo { ... } *) + let class_ = with_loc (declare_class ~leading:[]) env in + Some (Class class_) + | T_VAR -> + (* declare export var foo: ... *) + let var = with_loc (fun env -> declare_var ~kind:Ast.Variable.Var env []) env in + Some (Variable var) + | T_LET -> + (* declare export let foo: ... *) + let var = with_loc (fun env -> declare_var ~kind:Ast.Variable.Let env []) env in + Some (Variable var) + | T_CONST -> + (* declare export const foo: ... *) + let var = with_loc (fun env -> declare_var ~kind:Ast.Variable.Const env []) env in + Some (Variable var) + | _ -> assert false + in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareExportDeclaration + { default = None; declaration; specifiers = None; source = None; comments } + | T_IDENTIFIER { raw = "hook"; _ } when (parse_options env).components -> + let declaration = + (* declare export hook foo (...): ... *) + let fn = with_loc (declare_function ~async:false) env in + Some (Function fn) + in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareExportDeclaration + { default = None; declaration; specifiers = None; source = None; comments } + | T_IDENTIFIER { raw = "component"; _ } when (parse_options env).components -> + let declaration = + (* declare export component Foo() { ... } *) + let component = with_loc (declare_component ~leading:[]) env in + Some (Component component) + in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareExportDeclaration + { default = None; declaration; specifiers = None; source = None; comments } + | T_MULT -> + (* declare export * from 'foo' *) + let loc = Peek.loc env in + Expect.token env T_MULT; + let local_name = + match Peek.token env with + | T_IDENTIFIER { raw = "as"; _ } -> + Eat.token env; + Some (Parse.identifier env) + | _ -> None + in + let specifiers = + Statement.ExportNamedDeclaration.(Some (ExportBatchSpecifier (loc, local_name))) + in + let (source, trailing) = export_source_and_semicolon env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Statement.DeclareExportDeclaration + { default = None; declaration = None; specifiers; source = Some source; comments } + | T_TYPE -> + (* declare export type = ... *) + let alias = with_loc (type_alias_helper ~leading:[]) env in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareExportDeclaration + { + default = None; + declaration = Some (NamedType alias); + specifiers = None; + source = None; + comments; + } + | T_OPAQUE -> + (* declare export opaque type = ... *) + let opaque = with_loc (opaque_type_helper ~declare:true ~leading:[]) env in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareExportDeclaration + { + default = None; + declaration = Some (NamedOpaqueType opaque); + specifiers = None; + source = None; + comments; + } + | T_INTERFACE -> + (* declare export interface ... *) + let iface = with_loc (interface_helper ~leading:[]) env in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareExportDeclaration + { + default = None; + declaration = Some (Interface iface); + specifiers = None; + source = None; + comments; + } + | T_ENUM when (parse_options env).enums -> + (* declare export enum ... *) + let enum = with_loc Enum.declaration env in + let comments = Flow_ast_utils.mk_comments_opt ~leading () in + Statement.DeclareExportDeclaration + { + default = None; + declaration = Some (Enum enum); + specifiers = None; + source = None; + comments; + } + | _ -> + Expect.token env T_LCURLY; + let specifiers = export_specifiers env [] in + Expect.token env T_RCURLY; + let (source, trailing) = + match Peek.token env with + | T_IDENTIFIER { raw = "from"; _ } -> + let (source, trailing) = export_source_and_semicolon env in + (Some source, trailing) + | _ -> + assert_export_specifier_identifiers env specifiers; + let trailing = + match semicolon env with + | Explicit trailing -> trailing + | Implicit { trailing; _ } -> trailing + in + (None, trailing) + in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Statement.DeclareExportDeclaration + { + default = None; + declaration = None; + specifiers = Some (Statement.ExportNamedDeclaration.ExportSpecifiers specifiers); + source; + comments; + } + )) + env + + and import_declaration = + Statement.ImportDeclaration.( + let missing_source env = + (* Just make up a string for the error case *) + let loc = Peek.loc_skip_lookahead env in + (loc, { StringLiteral.value = ""; raw = ""; comments = None }) + in + let source env = + match Peek.token env with + | T_IDENTIFIER { raw = "from"; _ } -> + Eat.token env; + (match Peek.token env with + | T_STRING str -> string_literal env str + | _ -> + error_unexpected ~expected:"a string" env; + missing_source env) + | _ -> + error_unexpected ~expected:"the keyword `from`" env; + missing_source env + in + let is_type_import = function + | T_TYPE + | T_TYPEOF -> + true + | _ -> false + (* `x` or `x as y` in a specifier *) + in + let with_maybe_as ~for_type ?error_if_type env = + let identifier env = + if for_type then + Type.type_identifier env + else + Parse.identifier env + in + match Peek.ith_token ~i:1 env with + | T_IDENTIFIER { raw = "as"; _ } -> + let remote = identifier_name env in + Eat.token env; + + (* as *) + let local = Some (identifier env) in + (remote, local) + | T_EOF + | T_COMMA + | T_RCURLY -> + (identifier env, None) + | _ -> begin + match (error_if_type, Peek.token env) with + | (Some error_if_type, T_TYPE) + | (Some error_if_type, T_TYPEOF) -> + error env error_if_type; + Eat.token env; + + (* consume `type` or `typeof` *) + (Type.type_identifier env, None) + | _ -> (identifier env, None) + end + (* + ImportSpecifier[Type]: + [~Type] ImportedBinding + [~Type] IdentifierName ImportedTypeBinding + [~Type] IdentifierName IdentifierName ImportedBinding + [~Type] IdentifierName IdentifierName IdentifierName ImportedTypeBinding + [+Type] ImportedTypeBinding + [+Type] IdentifierName IdentifierName ImportedTypeBinding + + Static Semantics: + + `IdentifierName ImportedTypeBinding`: + - It is a Syntax Error if IdentifierName's StringValue is not "type" or "typeof" + + `IdentifierName IdentifierName ImportedBinding`: + - It is a Syntax Error if the second IdentifierName's StringValue is not "as" + + `IdentifierName IdentifierName IdentifierName ImportedTypeBinding`: + - It is a Syntax Error if the first IdentifierName's StringValue is not "type" + or "typeof", and the third IdentifierName's StringValue is not "as" + *) + in + + let specifier env = + let kind = + match Peek.token env with + | T_TYPE -> Some ImportType + | T_TYPEOF -> Some ImportTypeof + | _ -> None + in + if is_type_import (Peek.token env) then + (* consume `type`, but we don't know yet whether this is `type foo` or + `type as foo`. *) + let type_keyword_or_remote = identifier_name env in + match Peek.token env with + (* `type` (a value) *) + | T_EOF + | T_RCURLY + | T_COMMA -> + let remote = type_keyword_or_remote in + (* `type` becomes a value *) + assert_identifier_name_is_identifier env remote; + { remote; local = None; remote_name_def_loc = None; kind = None } + (* `type as foo` (value named `type`) or `type as,` (type named `as`) *) + | T_IDENTIFIER { raw = "as"; _ } -> begin + match Peek.ith_token ~i:1 env with + | T_EOF + | T_RCURLY + | T_COMMA -> + (* `type as` *) + { remote = Type.type_identifier env; remote_name_def_loc = None; local = None; kind } + | T_IDENTIFIER { raw = "as"; _ } -> + (* `type as as foo` *) + let remote = identifier_name env in + (* first `as` *) + Eat.token env; + + (* second `as` *) + let local = Some (Type.type_identifier env) in + (* `foo` *) + { remote; remote_name_def_loc = None; local; kind } + | _ -> + (* `type as foo` *) + let remote = type_keyword_or_remote in + (* `type` becomes a value *) + assert_identifier_name_is_identifier env remote; + Eat.token env; + + (* `as` *) + let local = Some (Parse.identifier env) in + { remote; remote_name_def_loc = None; local; kind = None } + end + (* `type x`, or `type x as y` *) + | _ -> + let (remote, local) = with_maybe_as ~for_type:true env in + { remote; remote_name_def_loc = None; local; kind } + else + (* standard `x` or `x as y` *) + let (remote, local) = with_maybe_as ~for_type:false env in + { remote; remote_name_def_loc = None; local; kind = None } + (* specifier in an `import type { ... }` *) + in + let type_specifier env = + let (remote, local) = + with_maybe_as + env + ~for_type:true + ~error_if_type:Parse_error.ImportTypeShorthandOnlyInPureImport + in + { remote; remote_name_def_loc = None; local; kind = None } + (* specifier in an `import typeof { ... }` *) + in + let typeof_specifier env = + let (remote, local) = + with_maybe_as + env + ~for_type:true + ~error_if_type:Parse_error.ImportTypeShorthandOnlyInPureImport + in + { remote; remote_name_def_loc = None; local; kind = None } + in + let rec specifier_list ?(preceding_comma = true) env statement_kind acc = + match Peek.token env with + | T_EOF + | T_RCURLY -> + List.rev acc + | _ -> + if not preceding_comma then error env Parse_error.ImportSpecifierMissingComma; + let specifier = + match statement_kind with + | ImportType -> type_specifier env + | ImportTypeof -> typeof_specifier env + | ImportValue -> specifier env + in + let preceding_comma = Eat.maybe env T_COMMA in + specifier_list ~preceding_comma env statement_kind (specifier :: acc) + in + let named_or_namespace_specifier env import_kind = + match Peek.token env with + | T_MULT -> + let id = + with_loc_opt + (fun env -> + (* consume T_MULT *) + Eat.token env; + match Peek.token env with + | T_IDENTIFIER { raw = "as"; _ } -> + (* consume "as" *) + Eat.token env; + (match import_kind with + | ImportType + | ImportTypeof -> + Some (Type.type_identifier env) + | ImportValue -> Some (Parse.identifier env)) + | _ -> + error_unexpected ~expected:"the keyword `as`" env; + None) + env + in + (match id with + | Some id -> Some (ImportNamespaceSpecifier id) + | None -> None) + | _ -> + Expect.token env T_LCURLY; + let specifiers = specifier_list env import_kind [] in + Expect.token env T_RCURLY; + Some (ImportNamedSpecifiers specifiers) + in + let semicolon_and_trailing env source = + match semicolon env with + | Explicit trailing -> (trailing, source) + | Implicit { remove_trailing; _ } -> + ( [], + remove_trailing source (fun remover (loc, source) -> + (loc, remover#string_literal loc source) + ) + ) + in + let with_specifiers import_kind env leading = + let specifiers = named_or_namespace_specifier env import_kind in + let source = source env in + let (trailing, source) = semicolon_and_trailing env source in + Statement.ImportDeclaration + { + import_kind; + source; + specifiers; + default = None; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + in + let with_default import_kind env leading = + let default_specifier = + match import_kind with + | ImportType + | ImportTypeof -> + { + Statement.ImportDeclaration.identifier = Type.type_identifier env; + remote_default_name_def_loc = None; + } + | ImportValue -> + { + Statement.ImportDeclaration.identifier = Parse.identifier env; + remote_default_name_def_loc = None; + } + in + let additional_specifiers = + match Peek.token env with + | T_COMMA -> + (* `import Foo, ...` *) + Expect.token env T_COMMA; + named_or_namespace_specifier env import_kind + | _ -> None + in + let source = source env in + let (trailing, source) = semicolon_and_trailing env source in + Statement.ImportDeclaration + { + import_kind; + source; + specifiers = additional_specifiers; + default = Some default_specifier; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + in + with_loc (fun env -> + let env = env |> with_strict true in + let leading = Peek.comments env in + Expect.token env T_IMPORT; + + match Peek.token env with + (* `import * as ns from "ModuleName";` *) + | T_MULT -> with_specifiers ImportValue env leading + (* `import { ... } from "ModuleName";` *) + | T_LCURLY -> with_specifiers ImportValue env leading + (* `import "ModuleName";` *) + | T_STRING str -> + let source = string_literal env str in + let (trailing, source) = semicolon_and_trailing env source in + Statement.ImportDeclaration + { + import_kind = ImportValue; + source; + specifiers = None; + default = None; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + (* `import type [...] from "ModuleName";` + note that if [...] is missing, we're importing a value named `type`! *) + | T_TYPE when should_parse_types env -> begin + match Peek.ith_token ~i:1 env with + (* `import type, { other, names } from "ModuleName";` *) + | T_COMMA + (* `import type from "ModuleName";` *) + | T_IDENTIFIER { raw = "from"; _ } -> + (* Importing the exported value named "type". This is not a type-import.*) + with_default ImportValue env leading + (* `import type *` is invalid, since the namespace can't be a type *) + | T_MULT -> + (* consume `type` *) + Eat.token env; + + (* unexpected `*` *) + error_unexpected env; + + with_specifiers ImportType env leading + | T_LCURLY -> + (* consume `type` *) + Eat.token env; + + with_specifiers ImportType env leading + | _ -> + (* consume `type` *) + Eat.token env; + + with_default ImportType env leading + end + (* `import typeof ... from "ModuleName";` *) + | T_TYPEOF when should_parse_types env -> + Expect.token env T_TYPEOF; + begin + match Peek.token env with + | T_MULT + | T_LCURLY -> + with_specifiers ImportTypeof env leading + | _ -> with_default ImportTypeof env leading + end + (* import Foo from "ModuleName"; *) + | _ -> with_default ImportValue env leading + ) + ) +end diff --git a/compiler/flow_parser/parser/statement_parser.mli b/compiler/flow_parser/parser/statement_parser.mli new file mode 100644 index 00000000000..4b9c72ed409 --- /dev/null +++ b/compiler/flow_parser/parser/statement_parser.mli @@ -0,0 +1,14 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Statement + (_ : Parser_common.PARSER) + (_ : Parser_common.TYPE) + (_ : Parser_common.DECLARATION) + (_ : Parser_common.OBJECT) + (_ : Parser_common.COVER) + (_ : Parser_common.EXPRESSION) : Parser_common.STATEMENT diff --git a/compiler/flow_parser/parser/token.ml b/compiler/flow_parser/parser/token.ml new file mode 100644 index 00000000000..e6800f8bba5 --- /dev/null +++ b/compiler/flow_parser/parser/token.ml @@ -0,0 +1,551 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +type t = + | T_NUMBER of { + kind: number_type; + raw: string; + } + | T_BIGINT of { + kind: bigint_type; + raw: string; + } + | T_STRING of (Loc.t * string * string * bool) (* loc, value, raw, octal *) + | T_TEMPLATE_PART of (Loc.t * string * string * bool * bool) (* loc, value, raw, head, tail *) + | T_IDENTIFIER of { + loc: Loc.t; + value: string; + raw: string; + } + | T_REGEXP of Loc.t * string * string (* /pattern/flags *) + (* Syntax *) + | T_LCURLY + | T_RCURLY + | T_LCURLYBAR + | T_RCURLYBAR + | T_LPAREN + | T_RPAREN + | T_LBRACKET + | T_RBRACKET + | T_SEMICOLON + | T_COMMA + | T_PERIOD + | T_ARROW + | T_ELLIPSIS + | T_AT + | T_POUND + (* Keywords *) + | T_FUNCTION + | T_IF + | T_IN + | T_INSTANCEOF + | T_RETURN + | T_SWITCH + | T_MATCH + | T_THIS + | T_THROW + | T_TRY + | T_VAR + | T_WHILE + | T_WITH + | T_CONST + | T_LET + | T_NULL + | T_FALSE + | T_TRUE + | T_BREAK + | T_CASE + | T_CATCH + | T_CONTINUE + | T_DEFAULT + | T_DO + | T_FINALLY + | T_FOR + | T_CLASS + | T_EXTENDS + | T_STATIC + | T_ELSE + | T_NEW + | T_DELETE + | T_TYPEOF + | T_VOID + | T_ENUM + | T_EXPORT + | T_IMPORT + | T_SUPER + | T_IMPLEMENTS + | T_INTERFACE + | T_PACKAGE + | T_PRIVATE + | T_PROTECTED + | T_PUBLIC + | T_YIELD + | T_DEBUGGER + | T_DECLARE + | T_TYPE + | T_OPAQUE + | T_OF + | T_ASYNC + | T_AWAIT + | T_CHECKS + (* Operators *) + | T_RSHIFT3_ASSIGN + | T_RSHIFT_ASSIGN + | T_LSHIFT_ASSIGN + | T_BIT_XOR_ASSIGN + | T_BIT_OR_ASSIGN + | T_BIT_AND_ASSIGN + | T_MOD_ASSIGN + | T_DIV_ASSIGN + | T_MULT_ASSIGN + | T_EXP_ASSIGN + | T_MINUS_ASSIGN + | T_PLUS_ASSIGN + | T_NULLISH_ASSIGN + | T_AND_ASSIGN + | T_OR_ASSIGN + | T_ASSIGN + | T_PLING_PERIOD + | T_PLING_PLING + | T_PLING + | T_COLON + | T_OR + | T_AND + | T_BIT_OR + | T_BIT_XOR + | T_BIT_AND + | T_EQUAL + | T_NOT_EQUAL + | T_STRICT_EQUAL + | T_STRICT_NOT_EQUAL + | T_LESS_THAN_EQUAL + | T_GREATER_THAN_EQUAL + | T_LESS_THAN + | T_GREATER_THAN + | T_LSHIFT + | T_RSHIFT + | T_RSHIFT3 + | T_PLUS + | T_MINUS + | T_DIV + | T_MULT + | T_EXP + | T_MOD + | T_NOT + | T_BIT_NOT + | T_INCR + | T_DECR + (* Extra tokens *) + | T_INTERPRETER of Loc.t * string + | T_ERROR of string + | T_EOF + (* JSX *) + | T_JSX_IDENTIFIER of { + raw: string; + loc: Loc.t; + } + | T_JSX_CHILD_TEXT of Loc.t * string * string (* loc, value, raw *) + | T_JSX_QUOTE_TEXT of Loc.t * string * string (* loc, value, raw *) + (* Type primitives *) + | T_ANY_TYPE + | T_MIXED_TYPE + | T_EMPTY_TYPE + | T_BOOLEAN_TYPE of bool_or_boolean + | T_NUMBER_TYPE + | T_BIGINT_TYPE + | T_NUMBER_SINGLETON_TYPE of { + kind: number_type; + value: float; + raw: string; + } + | T_BIGINT_SINGLETON_TYPE of { + kind: bigint_type; + value: int64 option; + raw: string; + } + | T_STRING_TYPE + | T_VOID_TYPE + | T_SYMBOL_TYPE + | T_UNKNOWN_TYPE + | T_NEVER_TYPE + | T_UNDEFINED_TYPE + | T_KEYOF + | T_READONLY + | T_INFER + | T_IS + | T_ASSERTS + | T_IMPLIES + | T_RENDERS_QUESTION + | T_RENDERS_STAR + +(* `bool` and `boolean` are equivalent annotations, but we need to track + which one was used for when it might be an identifier, as in + `(bool: boolean) => void`. It's lexed as two T_BOOLEAN_TYPEs, then the + first one is converted into an identifier. *) +and bool_or_boolean = + | BOOL + | BOOLEAN + +and number_type = + | BINARY + | LEGACY_OCTAL + | LEGACY_NON_OCTAL (* NonOctalDecimalIntegerLiteral in Annex B *) + | OCTAL + | NORMAL + +and bigint_type = + | BIG_BINARY + | BIG_OCTAL + | BIG_NORMAL +[@@deriving eq] + +(*****************************************************************************) +(* Pretty printer (pretty?) *) +(*****************************************************************************) +let token_to_string = function + | T_NUMBER _ -> "T_NUMBER" + | T_BIGINT _ -> "T_BIGINT" + | T_STRING _ -> "T_STRING" + | T_TEMPLATE_PART _ -> "T_TEMPLATE_PART" + | T_IDENTIFIER _ -> "T_IDENTIFIER" + | T_REGEXP _ -> "T_REGEXP" + | T_FUNCTION -> "T_FUNCTION" + | T_IF -> "T_IF" + | T_IN -> "T_IN" + | T_INSTANCEOF -> "T_INSTANCEOF" + | T_RETURN -> "T_RETURN" + | T_SWITCH -> "T_SWITCH" + | T_MATCH -> "T_MATCH" + | T_THIS -> "T_THIS" + | T_THROW -> "T_THROW" + | T_TRY -> "T_TRY" + | T_VAR -> "T_VAR" + | T_WHILE -> "T_WHILE" + | T_WITH -> "T_WITH" + | T_CONST -> "T_CONST" + | T_LET -> "T_LET" + | T_NULL -> "T_NULL" + | T_FALSE -> "T_FALSE" + | T_TRUE -> "T_TRUE" + | T_BREAK -> "T_BREAK" + | T_CASE -> "T_CASE" + | T_CATCH -> "T_CATCH" + | T_CONTINUE -> "T_CONTINUE" + | T_DEFAULT -> "T_DEFAULT" + | T_DO -> "T_DO" + | T_FINALLY -> "T_FINALLY" + | T_FOR -> "T_FOR" + | T_CLASS -> "T_CLASS" + | T_EXTENDS -> "T_EXTENDS" + | T_STATIC -> "T_STATIC" + | T_ELSE -> "T_ELSE" + | T_NEW -> "T_NEW" + | T_DELETE -> "T_DELETE" + | T_TYPEOF -> "T_TYPEOF" + | T_VOID -> "T_VOID" + | T_ENUM -> "T_ENUM" + | T_EXPORT -> "T_EXPORT" + | T_IMPORT -> "T_IMPORT" + | T_SUPER -> "T_SUPER" + | T_IMPLEMENTS -> "T_IMPLEMENTS" + | T_INTERFACE -> "T_INTERFACE" + | T_PACKAGE -> "T_PACKAGE" + | T_PRIVATE -> "T_PRIVATE" + | T_PROTECTED -> "T_PROTECTED" + | T_PUBLIC -> "T_PUBLIC" + | T_YIELD -> "T_YIELD" + | T_DEBUGGER -> "T_DEBUGGER" + | T_DECLARE -> "T_DECLARE" + | T_TYPE -> "T_TYPE" + | T_OPAQUE -> "T_OPAQUE" + | T_OF -> "T_OF" + | T_ASYNC -> "T_ASYNC" + | T_AWAIT -> "T_AWAIT" + | T_CHECKS -> "T_CHECKS" + | T_LCURLY -> "T_LCURLY" + | T_RCURLY -> "T_RCURLY" + | T_LCURLYBAR -> "T_LCURLYBAR" + | T_RCURLYBAR -> "T_RCURLYBAR" + | T_LPAREN -> "T_LPAREN" + | T_RPAREN -> "T_RPAREN" + | T_LBRACKET -> "T_LBRACKET" + | T_RBRACKET -> "T_RBRACKET" + | T_SEMICOLON -> "T_SEMICOLON" + | T_COMMA -> "T_COMMA" + | T_PERIOD -> "T_PERIOD" + | T_ARROW -> "T_ARROW" + | T_ELLIPSIS -> "T_ELLIPSIS" + | T_AT -> "T_AT" + | T_POUND -> "T_POUND" + | T_RSHIFT3_ASSIGN -> "T_RSHIFT3_ASSIGN" + | T_RSHIFT_ASSIGN -> "T_RSHIFT_ASSIGN" + | T_LSHIFT_ASSIGN -> "T_LSHIFT_ASSIGN" + | T_BIT_XOR_ASSIGN -> "T_BIT_XOR_ASSIGN" + | T_BIT_OR_ASSIGN -> "T_BIT_OR_ASSIGN" + | T_BIT_AND_ASSIGN -> "T_BIT_AND_ASSIGN" + | T_MOD_ASSIGN -> "T_MOD_ASSIGN" + | T_DIV_ASSIGN -> "T_DIV_ASSIGN" + | T_MULT_ASSIGN -> "T_MULT_ASSIGN" + | T_EXP_ASSIGN -> "T_EXP_ASSIGN" + | T_MINUS_ASSIGN -> "T_MINUS_ASSIGN" + | T_PLUS_ASSIGN -> "T_PLUS_ASSIGN" + | T_NULLISH_ASSIGN -> "T_NULLISH_ASSIGN" + | T_AND_ASSIGN -> "T_AND_ASSIGN" + | T_OR_ASSIGN -> "T_OR_ASSIGN" + | T_ASSIGN -> "T_ASSIGN" + | T_PLING_PERIOD -> "T_PLING_PERIOD" + | T_PLING_PLING -> "T_PLING_PLING" + | T_PLING -> "T_PLING" + | T_COLON -> "T_COLON" + | T_OR -> "T_OR" + | T_AND -> "T_AND" + | T_BIT_OR -> "T_BIT_OR" + | T_BIT_XOR -> "T_BIT_XOR" + | T_BIT_AND -> "T_BIT_AND" + | T_EQUAL -> "T_EQUAL" + | T_NOT_EQUAL -> "T_NOT_EQUAL" + | T_STRICT_EQUAL -> "T_STRICT_EQUAL" + | T_STRICT_NOT_EQUAL -> "T_STRICT_NOT_EQUAL" + | T_LESS_THAN_EQUAL -> "T_LESS_THAN_EQUAL" + | T_GREATER_THAN_EQUAL -> "T_GREATER_THAN_EQUAL" + | T_LESS_THAN -> "T_LESS_THAN" + | T_GREATER_THAN -> "T_GREATER_THAN" + | T_LSHIFT -> "T_LSHIFT" + | T_RSHIFT -> "T_RSHIFT" + | T_RSHIFT3 -> "T_RSHIFT3" + | T_PLUS -> "T_PLUS" + | T_MINUS -> "T_MINUS" + | T_DIV -> "T_DIV" + | T_MULT -> "T_MULT" + | T_EXP -> "T_EXP" + | T_MOD -> "T_MOD" + | T_NOT -> "T_NOT" + | T_BIT_NOT -> "T_BIT_NOT" + | T_INCR -> "T_INCR" + | T_DECR -> "T_DECR" + | T_KEYOF -> "T_KEYOF" + | T_READONLY -> "T_READONLY" + | T_INFER -> "T_INFER" + | T_IS -> "T_IS" + | T_ASSERTS -> "T_ASSERTS" + | T_IMPLIES -> "T_IMPLIES" + | T_RENDERS_QUESTION -> "T_RENDERS_QUESTION" + | T_RENDERS_STAR -> "T_RENDERS_QUESTION" + (* Extra tokens *) + | T_INTERPRETER _ -> "T_INTERPRETER" + | T_ERROR _ -> "T_ERROR" + | T_EOF -> "T_EOF" + | T_JSX_IDENTIFIER _ -> "T_JSX_IDENTIFIER" + | T_JSX_CHILD_TEXT _ -> "T_JSX_TEXT" + | T_JSX_QUOTE_TEXT _ -> "T_JSX_TEXT" + (* Type primitives *) + | T_ANY_TYPE -> "T_ANY_TYPE" + | T_MIXED_TYPE -> "T_MIXED_TYPE" + | T_EMPTY_TYPE -> "T_EMPTY_TYPE" + | T_BOOLEAN_TYPE _ -> "T_BOOLEAN_TYPE" + | T_NUMBER_TYPE -> "T_NUMBER_TYPE" + | T_BIGINT_TYPE -> "T_BIGINT_TYPE" + | T_NUMBER_SINGLETON_TYPE _ -> "T_NUMBER_SINGLETON_TYPE" + | T_BIGINT_SINGLETON_TYPE _ -> "T_BIGINT_SINGLETON_TYPE" + | T_STRING_TYPE -> "T_STRING_TYPE" + | T_VOID_TYPE -> "T_VOID_TYPE" + | T_SYMBOL_TYPE -> "T_SYMBOL_TYPE" + | T_UNKNOWN_TYPE -> "T_UNKNOWN_TYPE" + | T_NEVER_TYPE -> "T_NEVER_TYPE" + | T_UNDEFINED_TYPE -> "T_UNDEFINED_TYPE" + +let value_of_token = function + | T_NUMBER { raw; _ } -> raw + | T_BIGINT { raw; _ } -> raw + | T_STRING (_, _, raw, _) -> raw + | T_TEMPLATE_PART (_, _, raw, is_head, is_tail) -> + if is_head && is_tail then + "`" ^ raw ^ "`" + else if is_head then + "`" ^ raw ^ "${" + else if is_tail then + "}" ^ raw ^ "`" + else + "${" ^ raw ^ "}" + | T_IDENTIFIER { raw; _ } -> raw + | T_REGEXP (_, pattern, flags) -> "/" ^ pattern ^ "/" ^ flags + | T_LCURLY -> "{" + | T_RCURLY -> "}" + | T_LCURLYBAR -> "{|" + | T_RCURLYBAR -> "|}" + | T_LPAREN -> "(" + | T_RPAREN -> ")" + | T_LBRACKET -> "[" + | T_RBRACKET -> "]" + | T_SEMICOLON -> ";" + | T_COMMA -> "," + | T_PERIOD -> "." + | T_ARROW -> "=>" + | T_ELLIPSIS -> "..." + | T_AT -> "@" + | T_POUND -> "#" + | T_FUNCTION -> "function" + | T_IF -> "if" + | T_IN -> "in" + | T_INSTANCEOF -> "instanceof" + | T_RETURN -> "return" + | T_SWITCH -> "switch" + | T_MATCH -> "match" + | T_THIS -> "this" + | T_THROW -> "throw" + | T_TRY -> "try" + | T_VAR -> "var" + | T_WHILE -> "while" + | T_WITH -> "with" + | T_CONST -> "const" + | T_LET -> "let" + | T_NULL -> "null" + | T_FALSE -> "false" + | T_TRUE -> "true" + | T_BREAK -> "break" + | T_CASE -> "case" + | T_CATCH -> "catch" + | T_CONTINUE -> "continue" + | T_DEFAULT -> "default" + | T_DO -> "do" + | T_FINALLY -> "finally" + | T_FOR -> "for" + | T_CLASS -> "class" + | T_EXTENDS -> "extends" + | T_STATIC -> "static" + | T_ELSE -> "else" + | T_NEW -> "new" + | T_DELETE -> "delete" + | T_TYPEOF -> "typeof" + | T_VOID -> "void" + | T_ENUM -> "enum" + | T_EXPORT -> "export" + | T_IMPORT -> "import" + | T_SUPER -> "super" + | T_IMPLEMENTS -> "implements" + | T_INTERFACE -> "interface" + | T_PACKAGE -> "package" + | T_PRIVATE -> "private" + | T_PROTECTED -> "protected" + | T_PUBLIC -> "public" + | T_YIELD -> "yield" + | T_DEBUGGER -> "debugger" + | T_DECLARE -> "declare" + | T_TYPE -> "type" + | T_OPAQUE -> "opaque" + | T_OF -> "of" + | T_ASYNC -> "async" + | T_AWAIT -> "await" + | T_CHECKS -> "%checks" + | T_RSHIFT3_ASSIGN -> ">>>=" + | T_RSHIFT_ASSIGN -> ">>=" + | T_LSHIFT_ASSIGN -> "<<=" + | T_BIT_XOR_ASSIGN -> "^=" + | T_BIT_OR_ASSIGN -> "|=" + | T_BIT_AND_ASSIGN -> "&=" + | T_MOD_ASSIGN -> "%=" + | T_DIV_ASSIGN -> "/=" + | T_MULT_ASSIGN -> "*=" + | T_EXP_ASSIGN -> "**=" + | T_MINUS_ASSIGN -> "-=" + | T_PLUS_ASSIGN -> "+=" + | T_NULLISH_ASSIGN -> "??=" + | T_AND_ASSIGN -> "&&=" + | T_OR_ASSIGN -> "||=" + | T_ASSIGN -> "=" + | T_PLING_PERIOD -> "?." + | T_PLING_PLING -> "??" + | T_PLING -> "?" + | T_COLON -> ":" + | T_OR -> "||" + | T_AND -> "&&" + | T_BIT_OR -> "|" + | T_BIT_XOR -> "^" + | T_BIT_AND -> "&" + | T_EQUAL -> "==" + | T_NOT_EQUAL -> "!=" + | T_STRICT_EQUAL -> "===" + | T_STRICT_NOT_EQUAL -> "!==" + | T_LESS_THAN_EQUAL -> "<=" + | T_GREATER_THAN_EQUAL -> ">=" + | T_LESS_THAN -> "<" + | T_GREATER_THAN -> ">" + | T_LSHIFT -> "<<" + | T_RSHIFT -> ">>" + | T_RSHIFT3 -> ">>>" + | T_PLUS -> "+" + | T_MINUS -> "-" + | T_DIV -> "/" + | T_MULT -> "*" + | T_EXP -> "**" + | T_MOD -> "%" + | T_NOT -> "!" + | T_BIT_NOT -> "~" + | T_INCR -> "++" + | T_DECR -> "--" + | T_KEYOF -> "keyof" + | T_READONLY -> "readonly" + | T_INFER -> "infer" + | T_IS -> "is" + | T_ASSERTS -> "asserts" + | T_IMPLIES -> "implies" + | T_RENDERS_QUESTION -> "renders?" + | T_RENDERS_STAR -> "renders*" + (* Extra tokens *) + | T_INTERPRETER (_, str) -> str + | T_ERROR raw -> raw + | T_EOF -> "" + | T_JSX_IDENTIFIER { raw; _ } -> raw + | T_JSX_CHILD_TEXT (_, _, raw) -> raw + | T_JSX_QUOTE_TEXT (_, _, raw) -> raw + (* Type primitives *) + | T_ANY_TYPE -> "any" + | T_MIXED_TYPE -> "mixed" + | T_EMPTY_TYPE -> "empty" + | T_BOOLEAN_TYPE kind -> begin + match kind with + | BOOL -> "bool" + | BOOLEAN -> "boolean" + end + | T_NUMBER_TYPE -> "number" + | T_BIGINT_TYPE -> "bigint" + | T_NUMBER_SINGLETON_TYPE { raw; _ } -> raw + | T_BIGINT_SINGLETON_TYPE { raw; _ } -> raw + | T_STRING_TYPE -> "string" + | T_VOID_TYPE -> "void" + | T_SYMBOL_TYPE -> "symbol" + | T_UNKNOWN_TYPE -> "unknown" + | T_NEVER_TYPE -> "never" + | T_UNDEFINED_TYPE -> "undefined" + +let quote_token_value value = Printf.sprintf "token `%s`" value + +let explanation_of_token ?(use_article = false) token = + let (value, article) = + match token with + | T_NUMBER_SINGLETON_TYPE _ + | T_NUMBER _ -> + ("number", "a") + | T_BIGINT_SINGLETON_TYPE _ + | T_BIGINT _ -> + ("bigint", "a") + | T_JSX_CHILD_TEXT _ + | T_JSX_QUOTE_TEXT _ + | T_STRING _ -> + ("string", "a") + | T_TEMPLATE_PART _ -> ("template literal part", "a") + | T_JSX_IDENTIFIER _ + | T_IDENTIFIER _ -> + ("identifier", "an") + | T_REGEXP _ -> ("regexp", "a") + | T_EOF -> ("end of input", "the") + | _ -> (quote_token_value (value_of_token token), "the") + in + if use_article then + article ^ " " ^ value + else + value diff --git a/compiler/flow_parser/parser/token_translator.ml b/compiler/flow_parser/parser/token_translator.ml new file mode 100644 index 00000000000..02a7c7c2d83 --- /dev/null +++ b/compiler/flow_parser/parser/token_translator.ml @@ -0,0 +1,67 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Translate (Impl : Translator_intf.S) : sig + type t + + val token : Offset_utils.t -> Parser_env.token_sink_result -> t + + val token_list : Offset_utils.t -> Parser_env.token_sink_result list -> t +end +with type t = Impl.t = struct + type t = Impl.t + + let token offset_table { Parser_env.token_loc; token; token_context } = + Loc.( + Impl.obj + [ + ("type", Impl.string (Token.token_to_string token)); + ( "context", + Impl.string + Parser_env.Lex_mode.( + match token_context with + | NORMAL -> "normal" + | TYPE -> "type" + | JSX_TAG -> "jsxTag" + | JSX_CHILD -> "jsxChild" + | TEMPLATE -> "template" + | REGEXP -> "regexp" + ) + ); + ( "loc", + Impl.obj + [ + ( "start", + Impl.obj + [ + ("line", Impl.number (float token_loc.start.line)); + ("column", Impl.number (float token_loc.start.column)); + ] + ); + ( "end", + Impl.obj + [ + ("line", Impl.number (float token_loc._end.line)); + ("column", Impl.number (float token_loc._end.column)); + ] + ); + ] + ); + ( "range", + Impl.array + [ + Impl.number (float (Offset_utils.offset offset_table token_loc.start)); + Impl.number (float (Offset_utils.offset offset_table token_loc._end)); + ] + ); + ("value", Impl.string (Token.value_of_token token)); + ] + ) + + let token_list offset_table tokens = + Impl.array (List.rev_map (token offset_table) tokens |> List.rev) +end diff --git a/compiler/flow_parser/parser/translator_intf.ml b/compiler/flow_parser/parser/translator_intf.ml new file mode 100644 index 00000000000..a58d8a6986c --- /dev/null +++ b/compiler/flow_parser/parser/translator_intf.ml @@ -0,0 +1,26 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module type S = sig + type t + + val string : string -> t + + val bool : bool -> t + + val obj : (string * t) list -> t + + val array : t list -> t + + val number : float -> t + + val int : int -> t + + val null : t + + val regexp : Loc.t -> string -> string -> t +end diff --git a/compiler/flow_parser/parser/type_parser.ml b/compiler/flow_parser/parser/type_parser.ml new file mode 100644 index 00000000000..adc926fe7f9 --- /dev/null +++ b/compiler/flow_parser/parser/type_parser.ml @@ -0,0 +1,2202 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +open Token +open Parser_env +open Flow_ast +open Parser_common +open Comment_attachment + +module Type (Parse : Parser_common.PARSER) : Parser_common.TYPE = struct + type param_list_or_type = + | ParamList of (Loc.t, Loc.t) Type.Function.Params.t' + | Type of (Loc.t, Loc.t) Type.t + + type tuple_syntax_element = + | TupleElement of (Loc.t, Loc.t) Type.Tuple.element' + | InexactTupleMarker + + let maybe_variance ?(parse_readonly = false) ?(parse_in_out = false) env = + let loc = Peek.loc env in + match Peek.token env with + | T_PLUS -> + let leading = Peek.comments env in + Eat.token env; + Some + ( loc, + { Variance.kind = Variance.Plus; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + | T_MINUS -> + let leading = Peek.comments env in + Eat.token env; + Some + ( loc, + { Variance.kind = Variance.Minus; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + | T_READONLY when parse_readonly -> + let leading = Peek.comments env in + Eat.token env; + Some + ( loc, + { + Variance.kind = Variance.Readonly; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + | T_IDENTIFIER { raw = "in"; _ } when parse_in_out && Peek.ith_is_type_identifier ~i:1 env -> + let leading = Peek.comments env in + Eat.token env; + let (kind, loc) = + match Peek.token env with + | T_IDENTIFIER { raw = "out"; _ } -> + let end_loc = Peek.loc env in + Eat.token env; + (Variance.InOut, Loc.btwn loc end_loc) + | _ -> (Variance.In, loc) + in + Some (loc, { Variance.kind; comments = Flow_ast_utils.mk_comments_opt ~leading () }) + | T_IDENTIFIER { raw = "out"; _ } when parse_in_out && Peek.ith_is_type_identifier ~i:1 env -> + let leading = Peek.comments env in + Eat.token env; + Some + ( loc, + { Variance.kind = Variance.Out; comments = Flow_ast_utils.mk_comments_opt ~leading () } + ) + | _ -> None + + let maybe_const env = + match Peek.token env with + | T_CONST -> + Some + (with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + Flow_ast_utils.mk_comments_opt ~leading ()) + env + ) + | _ -> None + + let number_singleton ~neg kind value raw env = + if kind = LEGACY_OCTAL then strict_error env Parse_error.StrictOctalLiteral; + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let (value, raw, comments) = + match neg with + | None -> + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (value, raw, comments) + | Some leading_neg -> + let leading = leading_neg @ leading in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (~-.value, "-" ^ raw, comments) + in + Type.NumberLiteral { Ast.NumberLiteral.value; raw; comments } + + let bigint_singleton ~neg value raw env = + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let (value, raw, comments) = + match neg with + | None -> + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (value, raw, comments) + | Some leading_neg -> + let leading = leading_neg @ leading in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + (Option.map Int64.neg value, "-" ^ raw, comments) + in + Type.BigIntLiteral { Ast.BigIntLiteral.value; raw; comments } + + let rec _type env = conditional env + + and annotation env = + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + with_loc + (fun env -> + Expect.token env T_COLON; + _type env) + env + + and function_return_annotation env = + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + let start_loc = Peek.loc env in + Expect.token env T_COLON; + if is_start_of_type_guard env then + Function.ReturnAnnot.TypeGuard (type_guard_annotation env ~start_loc) + else + Function.ReturnAnnot.Available (with_loc ~start_loc _type env) + + and conditional env = + let start_loc = Peek.loc env in + let env = Parser_env.with_no_conditional_type false env in + let check_type = union env in + conditional_with env ~start_loc check_type + + and conditional_with env ~start_loc check_type = + match Peek.token env with + | T_EXTENDS -> + with_loc + ~start_loc + (fun env -> + Expect.token env T_EXTENDS; + let extends_type = union (Parser_env.with_no_conditional_type true env) in + Expect.token_opt env T_PLING; + let true_type = _type env in + Expect.token_opt env T_COLON; + let false_type = _type env in + let trailing = Eat.trailing_comments env in + Type.Conditional + { + Type.Conditional.check_type; + extends_type; + true_type; + false_type; + comments = Flow_ast_utils.mk_comments_opt ~trailing (); + }) + env + | _ -> check_type + + and union env = + let start_loc = Peek.loc env in + let leading = + if Peek.token env = T_BIT_OR then ( + let leading = Peek.comments env in + Eat.token env; + leading + ) else + [] + in + let left = intersection env in + union_with env ~leading ~start_loc left + + and union_with = + let rec unions leading acc env = + if Eat.maybe env T_BIT_OR then + unions leading (intersection env :: acc) env + else + match List.rev acc with + | t0 :: t1 :: ts -> + Type.Union + { + Type.Union.types = (t0, t1, ts); + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + | _ -> assert false + in + fun env ?(leading = []) ~start_loc left -> + if Peek.token env = T_BIT_OR then + with_loc ~start_loc (unions leading [left]) env + else + left + + and intersection env = + let start_loc = Peek.loc env in + let leading = + if Peek.token env = T_BIT_AND then ( + let leading = Peek.comments env in + Eat.token env; + leading + ) else + [] + in + let left = anon_function_without_parens env in + intersection_with env ~leading ~start_loc left + + and intersection_with = + let rec intersections leading acc env = + if Eat.maybe env T_BIT_AND then + intersections leading (anon_function_without_parens env :: acc) env + else + match List.rev acc with + | t0 :: t1 :: ts -> + Type.Intersection + { + Type.Intersection.types = (t0, t1, ts); + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + | _ -> assert false + in + fun env ?(leading = []) ~start_loc left -> + if Peek.token env = T_BIT_AND then + with_loc ~start_loc (intersections leading [left]) env + else + left + + and anon_function_without_parens env = + let param = prefix env in + anon_function_without_parens_with env param + + and anon_function_without_parens_with env param = + match Peek.token env with + | T_ARROW when not (no_anon_function_type env) -> + let (start_loc, tparams, params) = + let param = anonymous_function_param env param in + ( fst param, + None, + ( fst param, + { + Ast.Type.Function.Params.params = [param]; + this_ = None; + rest = None; + comments = None; + } + ) + ) + in + function_with_params ~effect_:Function.Arbitrary env start_loc tparams params + | _ -> param + + and prefix env = + match Peek.token env with + | T_PLING -> + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_PLING; + Type.Nullable + { + Type.Nullable.argument = prefix env; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | _ -> postfix env + + and postfix env = + let start_loc = Peek.loc env in + let t = primary env in + postfix_with env ~start_loc t + + and postfix_with ?(in_optional_indexed_access = false) env ~start_loc t = + if Peek.is_line_terminator env then + t + else + match Peek.token env with + | T_PLING_PERIOD -> + Eat.token env; + if Peek.token env <> T_LBRACKET then error env Parse_error.InvalidOptionalIndexedAccess; + Expect.token env T_LBRACKET; + postfix_brackets + ~in_optional_indexed_access:true + ~optional_indexed_access:true + env + start_loc + t + | T_LBRACKET -> + Eat.token env; + postfix_brackets ~in_optional_indexed_access ~optional_indexed_access:false env start_loc t + | T_PERIOD -> + (match Peek.ith_token ~i:1 env with + | T_LBRACKET -> + error env (Parse_error.InvalidIndexedAccess { has_bracket = true }); + Expect.token env T_PERIOD; + Expect.token env T_LBRACKET; + postfix_brackets + ~in_optional_indexed_access + ~optional_indexed_access:false + env + start_loc + t + | _ -> + error env (Parse_error.InvalidIndexedAccess { has_bracket = false }); + t) + | _ -> t + + and postfix_brackets ~in_optional_indexed_access ~optional_indexed_access env start_loc t = + let t = + with_loc + ~start_loc + (fun env -> + (* Legacy Array syntax `Foo[]` *) + if (not optional_indexed_access) && Eat.maybe env T_RBRACKET then + let trailing = Eat.trailing_comments env in + Type.Array + { Type.Array.argument = t; comments = Flow_ast_utils.mk_comments_opt ~trailing () } + else + let index = _type env in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + let indexed_access = + { + Type.IndexedAccess._object = t; + index; + comments = Flow_ast_utils.mk_comments_opt ~trailing (); + } + in + if in_optional_indexed_access then + Type.OptionalIndexedAccess + { Type.OptionalIndexedAccess.indexed_access; optional = optional_indexed_access } + else + Type.IndexedAccess indexed_access) + env + in + postfix_with env ~in_optional_indexed_access ~start_loc t + + and typeof_expr env = raw_typeof_expr_with_identifier env (Parse.identifier env) + + and raw_typeof_expr_with_identifier = + let rec identifier env (q_loc, qualification) = + if Peek.token env = T_PERIOD && Peek.ith_is_identifier_name ~i:1 env then + let (loc, q) = + with_loc + ~start_loc:q_loc + (fun env -> + Expect.token env T_PERIOD; + let id = identifier_name env in + { Type.Typeof.Target.qualification; id }) + env + in + let qualification = Type.Typeof.Target.Qualified (loc, q) in + identifier env (loc, qualification) + else + qualification + in + fun env ((loc, _) as id) -> + let id = Type.Typeof.Target.Unqualified id in + identifier env (loc, id) + + and typeof_arg env = + Eat.push_lex_mode env Lex_mode.NORMAL; + let result = + if Peek.token env = T_LPAREN then ( + Eat.token env; + let typeof = typeof_arg env in + Expect.token env T_RPAREN; + typeof + ) else if Peek.is_identifier env then + Some (typeof_expr env) + else ( + error env Parse_error.InvalidTypeof; + None + ) + in + Eat.pop_lex_mode env; + result + + and typeof env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_TYPEOF; + match typeof_arg env with + | None -> Type.Any None + | Some argument -> + let targs = + if Peek.is_line_terminator env then + None + else + type_args env + in + Type.Typeof + { Type.Typeof.argument; targs; comments = Flow_ast_utils.mk_comments_opt ~leading () }) + env + + and primary env = + let loc = Peek.loc env in + match Peek.token env with + | T_MULT -> + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + (loc, Type.Exists (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_LESS_THAN -> _function env + | T_LPAREN -> function_or_group env + | T_LCURLY + | T_LCURLYBAR -> + let (loc, o) = _object env ~is_class:false ~allow_exact:true ~allow_spread:true in + (loc, Type.Object o) + | T_INTERFACE -> + with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + let (extends, body) = interface_helper env in + Type.Interface + { Type.Interface.extends; body; comments = Flow_ast_utils.mk_comments_opt ~leading () }) + env + | T_TYPEOF -> typeof env + | T_LBRACKET -> tuple env + | T_IDENTIFIER { raw = "component"; _ } when (parse_options env).components -> + with_loc + (fun env -> + (* This logic is very similar to the statement parser but omits the component name *) + let leading = Peek.comments env in + Expect.identifier env "component"; + let tparams = + type_params_remove_trailing env ~kind:Flow_ast_mapper.ComponentTypeTP (type_params env) + in + let params = component_param_list env in + let (params, renders) = + if Peek.is_renders_ident env then + let renders = renders_annotation_opt env in + let renders = component_renders_annotation_remove_trailing env renders in + (params, renders) + else + let missing_annotation = renders_annotation_opt env in + (component_type_params_remove_trailing env params, missing_annotation) + in + Type.Component + { + Type.Component.tparams; + params; + renders; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + | T_IDENTIFIER { raw = "renders"; _ } + | T_RENDERS_QUESTION + | T_RENDERS_STAR -> + with_loc (fun env -> Type.Renders (render_type env)) env + | T_IDENTIFIER { raw = "hook"; _ } when (parse_options env).components -> + (match Peek.ith_token ~i:1 env with + | T_LESS_THAN + | T_LPAREN -> + hook env + | _ -> + let (loc, g) = generic env in + (loc, Type.Generic g)) + | T_IDENTIFIER _ + | T_EXTENDS (* `extends` is reserved, but recover by treating it as an identifier *) + | T_STATIC (* `static` is reserved, but recover by treating it as an identifier *) -> + let (loc, g) = generic env in + (loc, Type.Generic g) + | T_STRING (loc, value, raw, octal) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + ( loc, + Type.StringLiteral + { + Ast.StringLiteral.value; + raw; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + ) + | T_MINUS -> with_loc negate env + | T_NUMBER_SINGLETON_TYPE { kind; value; raw } -> + with_loc (number_singleton ~neg:None kind value raw) env + | T_BIGINT_SINGLETON_TYPE { kind = _; value; raw } -> + with_loc (bigint_singleton ~neg:None value raw) env + | (T_TRUE | T_FALSE) as token -> + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let value = token = T_TRUE in + ( loc, + Type.BooleanLiteral + { BooleanLiteral.value; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) + | T_KEYOF -> + with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let argument = _type env in + Type.Keyof + { Type.Keyof.argument; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () }) + env + | T_READONLY -> + with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let argument = _type env in + Type.ReadOnly + { + Type.ReadOnly.argument; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + | T_INFER -> + with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let tparam = + with_loc + (fun env -> + let name = type_identifier env in + let bound = + Try.or_else + env + ~fallback:(Type.Missing (Peek.loc env)) + (fun env -> + if not @@ Eat.maybe env T_EXTENDS then raise Try.Rollback; + let bound = union env in + if Parser_env.no_conditional_type env || Peek.token env <> T_PLING then + Type.Available (fst bound, bound) + else + raise Try.Rollback) + in + { + Type.TypeParam.name; + bound; + bound_kind = Type.TypeParam.Extends; + variance = None; + default = None; + const = None; + }) + env + in + Type.Infer + { Type.Infer.tparam; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () }) + env + | T_ERROR "`" -> + error env Parse_error.TSTemplateLiteralType; + (loc, Type.Any None) + | _ -> + (match primitive env with + | Some t -> (loc, t) + | None -> + error_unexpected ~expected:"a type" env; + (loc, Type.Any None)) + + and negate env = + let leading = Peek.comments env in + Eat.token env; + match Peek.token env with + | T_NUMBER_SINGLETON_TYPE { kind; value; raw } -> + number_singleton ~neg:(Some leading) kind value raw env + | T_BIGINT_SINGLETON_TYPE { kind = _; value; raw } -> + bigint_singleton ~neg:(Some leading) value raw env + | _ -> + error_unexpected ~expected:"a number literal type" env; + Type.Any None + + and is_primitive = function + | T_ANY_TYPE + | T_MIXED_TYPE + | T_EMPTY_TYPE + | T_BOOLEAN_TYPE _ + | T_NUMBER_TYPE + | T_BIGINT_TYPE + | T_STRING_TYPE + | T_SYMBOL_TYPE + | T_VOID_TYPE + | T_NULL + | T_UNKNOWN_TYPE + | T_NEVER_TYPE + | T_UNDEFINED_TYPE -> + true + | _ -> false + + and generic_of_primitive env name = + let leading = Peek.comments env in + let (loc, _) = with_loc Eat.token env in + let trailing = Eat.trailing_comments env in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Some + (Ast.Type.Generic + { + Ast.Type.Generic.id = + Ast.Type.Generic.Identifier.Unqualified (Flow_ast_utils.ident_of_source (loc, name)); + targs = None; + comments; + } + ) + + and primitive env = + let leading = Peek.comments env in + let token = Peek.token env in + match token with + | T_ANY_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Any (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_MIXED_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Mixed (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_EMPTY_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Empty (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_BOOLEAN_TYPE kind -> + Eat.token env; + let trailing = Eat.trailing_comments env in + let raw = + match kind with + | BOOL -> `Bool + | BOOLEAN -> `Boolean + in + let comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () in + Some (Type.Boolean { raw; comments }) + | T_NUMBER_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Number (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_BIGINT_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.BigInt (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_STRING_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.String (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_SYMBOL_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Symbol (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_VOID_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Void (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_NULL -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Null (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_UNKNOWN_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Unknown (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_NEVER_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Never (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_UNDEFINED_TYPE -> + Eat.token env; + let trailing = Eat.trailing_comments env in + Some (Type.Undefined (Flow_ast_utils.mk_comments_opt ~leading ~trailing ())) + | T_ASSERTS -> generic_of_primitive env "asserts" + | T_IMPLIES -> generic_of_primitive env "implies" + | T_IS -> generic_of_primitive env "is" + | _ -> None + + and tuple = + let element env = + with_loc + (fun env -> + if Eat.maybe env T_ELLIPSIS then + match Peek.token env with + | T_EOF + | T_RBRACKET -> + InexactTupleMarker + | T_COMMA -> + error_unexpected + ~expected: + "the end of a tuple type (no trailing comma is allowed in inexact tuple type)." + env; + Eat.token env; + InexactTupleMarker + | _ -> + let name = + match (Peek.is_identifier env, Peek.ith_token ~i:1 env) with + | (true, T_PLING) + | (true, T_COLON) -> + let name = identifier_name env in + if Peek.token env = T_PLING then ( + error env Parse_error.InvalidTupleOptionalSpread; + Eat.token env + ); + Expect.token env T_COLON; + Some name + | _ -> None + in + let annot = _type env in + TupleElement (Type.Tuple.SpreadElement { Type.Tuple.SpreadElement.name; annot }) + else + let variance = + match Peek.token env with + | T_PLUS -> maybe_variance env + | T_MINUS when Peek.ith_is_identifier ~i:1 env -> + (* `-1` is a valid type but not a valid tuple label. + But `-foo` is only valid as a tuple label. *) + maybe_variance env + | _ -> None + in + match (Peek.is_identifier env, Peek.ith_token ~i:1 env) with + | (true, T_PLING) + | (true, T_COLON) -> + let name = identifier_name env in + let optional = Eat.maybe env T_PLING in + Expect.token env T_COLON; + let annot = _type env in + TupleElement + (Type.Tuple.LabeledElement + { Type.Tuple.LabeledElement.name; annot; variance; optional } + ) + | _ -> + if Option.is_some variance then error env Parse_error.InvalidTupleVariance; + TupleElement (Type.Tuple.UnlabeledElement (_type env))) + env + in + let rec elements env acc = + match Peek.token env with + | T_EOF + | T_RBRACKET -> + (List.rev acc, false) + | _ -> + (match element env with + | (_, InexactTupleMarker) -> (List.rev acc, true) + | (loc, TupleElement el) -> + let acc = (loc, el) :: acc in + (* Trailing comma support (like [number, string,]) *) + if Peek.token env <> T_RBRACKET then Expect.token env T_COMMA; + elements env acc) + in + fun env -> + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LBRACKET; + let (els, inexact) = elements (with_no_anon_function_type false env) [] in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + Type.Tuple + { + Type.Tuple.elements = els; + inexact; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + + and render_type env = + let leading = Peek.comments env in + let variant = + match Peek.token env with + | T_IDENTIFIER { raw = "renders"; _ } -> Type.Renders.Normal + | T_RENDERS_QUESTION -> Type.Renders.Maybe + | T_RENDERS_STAR -> Type.Renders.Star + | _ -> + failwith + "You should only call render_type after making sure the next token is a renders variant" + in + let operator_loc = Peek.loc env in + Eat.token env; + let trailing = Eat.trailing_comments env in + let argument = prefix env in + { + Type.Renders.operator_loc; + argument; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + variant; + } + + and anonymous_function_param _env annot = + (fst annot, Type.Function.Param.{ name = None; annot; optional = false }) + + and function_param_with_id env = + with_loc + (fun env -> + Eat.push_lex_mode env Lex_mode.NORMAL; + let name = Parse.identifier env in + Eat.pop_lex_mode env; + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + let optional = Eat.maybe env T_PLING in + Expect.token env T_COLON; + let annot = _type env in + { Type.Function.Param.name = Some name; annot; optional }) + env + + and function_param_list_without_parens = + let param env = + match Peek.ith_token ~i:1 env with + | T_COLON + | T_PLING -> + function_param_with_id env + | _ -> + let annot = _type env in + anonymous_function_param env annot + in + let rec param_list env this_ acc = + match Peek.token env with + | (T_EOF | T_ELLIPSIS | T_RPAREN) as t -> + let rest = + if t = T_ELLIPSIS then + let rest = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_ELLIPSIS; + { + Type.Function.RestParam.argument = param env; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + Some rest + else + None + in + { Ast.Type.Function.Params.params = List.rev acc; rest; this_; comments = None } + | T_IDENTIFIER { raw = "this"; _ } + when Peek.ith_token ~i:1 env == T_COLON || Peek.ith_token ~i:1 env == T_PLING -> + if this_ <> None || acc <> [] then error env Parse_error.ThisParamMustBeFirst; + let this_ = + with_loc + (fun env -> + let leading = Peek.comments env in + Eat.token env; + if Peek.token env == T_PLING then error env Parse_error.ThisParamMayNotBeOptional; + { + Type.Function.ThisParam.annot = annotation env; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + if Peek.token env <> T_RPAREN then Expect.token env T_COMMA; + param_list env (Some this_) acc + | _ -> + let acc = param env :: acc in + if Peek.token env <> T_RPAREN then Expect.token env T_COMMA; + param_list env this_ acc + in + (fun env -> param_list env None) + + and function_param_list env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LPAREN; + let params = function_param_list_without_parens env [] in + let internal = Peek.comments env in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + { + params with + Ast.Type.Function.Params.comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }) + env + + and component_param_list_without_parens = + let param_name env = + match Peek.token env with + | T_STRING (loc, value, raw, octal) -> + if octal then strict_error env Parse_error.StrictOctalLiteral; + Expect.token env (T_STRING (loc, value, raw, octal)); + let trailing = Eat.trailing_comments env in + Statement.ComponentDeclaration.Param.StringLiteral + (loc, { StringLiteral.value; raw; comments = Flow_ast_utils.mk_comments_opt ~trailing () }) + (* If not a string, must be an identifier *) + | _ -> + Eat.push_lex_mode env Lex_mode.NORMAL; + let ident = Parse.identifier env in + Eat.pop_lex_mode env; + Statement.ComponentDeclaration.Param.Identifier ident + in + + let param env = + with_loc + (fun env -> + let name = param_name env in + let optional = Eat.maybe env T_PLING in + let annot = annotation env in + { Ast.Type.Component.Param.name; annot; optional }) + env + in + let rec param_list env acc = + match Peek.token env with + | (T_EOF | T_ELLIPSIS | T_RPAREN) as t -> + let rest = + if t = T_ELLIPSIS then + let rest = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_ELLIPSIS; + let (argument, optional) = + match Peek.ith_token ~i:1 env with + | T_COLON -> + Eat.push_lex_mode env Lex_mode.NORMAL; + let ident = Parse.identifier env in + Eat.pop_lex_mode env; + Expect.token env T_COLON; + (Some ident, false) + | T_PLING -> + Eat.push_lex_mode env Lex_mode.NORMAL; + let ident = Parse.identifier env in + Eat.pop_lex_mode env; + Expect.token env T_PLING; + Expect.token env T_COLON; + (Some ident, true) + | _ -> (None, false) + in + let annot = _type env in + if Peek.token env = T_COMMA then Eat.token env; + { + Ast.Type.Component.RestParam.argument; + annot; + optional; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + Some rest + else + None + in + { Ast.Type.Component.Params.params = List.rev acc; rest; comments = None } + | _ -> + let acc = param env :: acc in + if Peek.token env <> T_RPAREN then Expect.token env T_COMMA; + param_list env acc + in + (* Need this wrapper function due to a compilation issue with js_of_ocaml. + Directly returning param_list causes an error. *) + (fun env acc -> param_list env acc) + + and component_param_list env = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LPAREN; + let params = component_param_list_without_parens env [] in + let internal = Peek.comments env in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + { + params with + Ast.Type.Component.Params.comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }) + env + + and param_list_or_type env = + let leading = Peek.comments env in + Expect.token env T_LPAREN; + let ret = + let env = with_no_anon_function_type false env in + match Peek.token env with + | T_EOF + | T_ELLIPSIS -> + (* (... is definitely the beginning of a param list *) + ParamList (function_param_list_without_parens env []) + | T_RPAREN -> + (* () or is definitely a param list *) + ParamList + { Ast.Type.Function.Params.this_ = None; params = []; rest = None; comments = None } + | T_RENDERS_QUESTION -> + (match Peek.ith_token ~i:1 env with + | T_COLON -> + (* Ok this is definitely a parameter *) + ParamList (function_param_list_without_parens env []) + | _ -> Type (_type env)) + | T_IDENTIFIER { raw = "renders"; _ } -> + (match Peek.ith_token ~i:1 env with + | T_PLING + | T_COLON -> + (* Ok this is definitely a parameter *) + ParamList (function_param_list_without_parens env []) + | _ -> Type (_type env)) + | T_IDENTIFIER { raw = "component"; _ } when (parse_options env).components -> + (match Peek.ith_token ~i:1 env with + | T_LESS_THAN + | T_LPAREN -> + Type (_type env) + | _ -> function_param_or_generic_type env) + | T_IDENTIFIER _ + | T_STATIC (* `static` is reserved in strict mode, but still an identifier *) -> + (* This could be a function parameter or a generic type *) + function_param_or_generic_type env + | token when is_primitive token -> + (* Don't know if this is (number) or (number: number). The first + * is a type, the second is a param. *) + (match Peek.ith_token ~i:1 env with + | T_PLING + | T_COLON -> + (* Ok this is definitely a parameter *) + ParamList (function_param_list_without_parens env []) + | _ -> Type (_type env)) + | _ -> + (* All params start with an identifier or `...` *) + Type (_type env) + in + (* Now that we allow anonymous parameters in function types, we need to + * disambiguate a little bit more *) + let ret = + match ret with + | ParamList _ -> ret + | Type _ when no_anon_function_type env -> ret + | Type t -> + (match Peek.token env with + | T_RPAREN -> + (* Reinterpret `(type) =>` as a ParamList *) + if Peek.ith_token ~i:1 env = T_ARROW then + let param = anonymous_function_param env t in + ParamList (function_param_list_without_parens env [param]) + else + Type t + | T_COMMA -> + (* Reinterpret `(type,` as a ParamList *) + Expect.token env T_COMMA; + let param = anonymous_function_param env t in + ParamList (function_param_list_without_parens env [param]) + | _ -> ret) + in + let internal = Peek.comments env in + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + let ret = + match ret with + | ParamList params -> + ParamList + { + params with + Ast.Type.Function.Params.comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + } + | Type t -> Type (add_comments t leading trailing) + in + ret + + and function_param_or_generic_type env = + match Peek.ith_token ~i:1 env with + | T_PLING + (* optional param *) + | T_COLON -> + ParamList (function_param_list_without_parens env []) + | _ -> + let start_loc = Peek.loc env in + let id = type_identifier env in + Type + (generic_type_with_identifier env id + |> postfix_with env ~start_loc + |> anon_function_without_parens_with env + |> intersection_with ~start_loc env + |> union_with ~start_loc env + |> conditional_with (Parser_env.with_no_conditional_type false env) ~start_loc + ) + + and function_or_group env = + let start_loc = Peek.loc env in + match with_loc param_list_or_type env with + | (loc, ParamList params) -> + function_with_params ~effect_:Function.Arbitrary env start_loc None (loc, params) + | (_, Type _type) -> _type + + and _function env = + let start_loc = Peek.loc env in + let tparams = + type_params_remove_trailing env ~kind:Flow_ast_mapper.FunctionTP (type_params env) + in + let params = function_param_list env in + function_with_params ~effect_:Function.Arbitrary env start_loc tparams params + + and function_with_params + ~effect_ env start_loc tparams (params : (Loc.t, Loc.t) Ast.Type.Function.Params.t) = + with_loc + ~start_loc + (fun env -> + Expect.token env T_ARROW; + let return = function_return_type env in + Type.(Function { Function.params; return; tparams; comments = None; effect_ })) + env + + and hook env = + let start_loc = Peek.loc env in + Eat.token env; + let tparams = + type_params_remove_trailing env ~kind:Flow_ast_mapper.FunctionTP (type_params env) + in + let params = function_param_list env in + function_with_params ~effect_:Function.Hook env start_loc tparams params + + and function_return_type env = + if is_start_of_type_guard env then + Type.Function.TypeGuard (type_guard env) + else + Type.Function.TypeAnnotation (_type env) + + and type_guard env = + let parse_is_type_guard env = + let internal = Peek.comments env in + Expect.token env T_IS; + let internal = internal @ Peek.comments env in + (Some (_type env), internal) + in + with_loc + (fun env -> + let leading = Peek.comments env in + let kind = + if Eat.maybe env T_ASSERTS then + Ast.Type.TypeGuard.Asserts + else if Eat.maybe env T_IMPLIES then + Ast.Type.TypeGuard.Implies + else + Ast.Type.TypeGuard.Default + in + (* Parse the identifier part as normal code, since this can be any name that + * a parameter can be. *) + Eat.push_lex_mode env Lex_mode.NORMAL; + let param = identifier_name env in + Eat.pop_lex_mode env; + let (t, internal) = + if kind = Ast.Type.TypeGuard.Implies then + parse_is_type_guard env + else + match Peek.token env with + | T_IS -> parse_is_type_guard env + | _ -> (None, []) + in + let guard = (param, t) in + let comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~internal () in + { Ast.Type.TypeGuard.kind; guard; comments }) + env + + and type_guard_annotation env ~start_loc = with_loc ~start_loc type_guard env + + and _object = + let methodish env start_loc tparams = + with_loc + ~start_loc + (fun env -> + let params = function_param_list env in + Expect.token env T_COLON; + let return = function_return_type env in + { Type.Function.params; return; tparams; comments = None; effect_ = Function.Arbitrary }) + env + in + let method_property env start_loc static key ~leading = + let key = object_key_remove_trailing env key in + let tparams = + type_params_remove_trailing env ~kind:Flow_ast_mapper.FunctionTypeTP (type_params env) + in + let value = methodish env start_loc tparams in + let value = (fst value, Type.Function (snd value)) in + Type.Object.( + Property + ( fst value, + { + Property.key; + value = Property.Init value; + optional = false; + static = static <> None; + proto = false; + _method = true; + variance = None; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + } + ) + ) + in + let call_property env start_loc static ~leading = + let prop = + with_loc + ~start_loc + (fun env -> + let start_loc = Peek.loc env in + let tparams = + type_params_remove_trailing env ~kind:Flow_ast_mapper.FunctionTypeTP (type_params env) + in + let value = methodish env start_loc tparams in + Type.Object.CallProperty. + { + value; + static = static <> None; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + Type.Object.CallProperty prop + in + let init_property env start_loc ~variance ~static ~proto ~leading (key_loc, key) = + ignore proto; + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + let prop = + with_loc + ~start_loc + (fun env -> + let optional = Eat.maybe env T_PLING in + let value = + if Expect.token_maybe env T_COLON then + _type env + else + (key_loc, Type.Any None) + in + Type.Object.Property. + { + key; + value = Init value; + optional; + static = static <> None; + proto = proto <> None; + _method = false; + variance; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + Type.Object.Property prop + in + let getter_or_setter ~is_getter ~leading env start_loc static key = + let prop = + with_loc + ~start_loc + (fun env -> + let (key_loc, key) = key in + let key = object_key_remove_trailing env key in + let value = methodish env start_loc None in + let (_, { Type.Function.params; _ }) = value in + begin + match (is_getter, params) with + | (true, (_, { Type.Function.Params.this_ = Some _; _ })) -> + error_at env (key_loc, Parse_error.GetterMayNotHaveThisParam) + | (false, (_, { Type.Function.Params.this_ = Some _; _ })) -> + error_at env (key_loc, Parse_error.SetterMayNotHaveThisParam) + | ( true, + (_, { Type.Function.Params.params = []; rest = None; this_ = None; comments = _ }) + ) -> + () + | (false, (_, { Type.Function.Params.rest = Some _; _ })) -> + (* rest params don't make sense on a setter *) + error_at env (key_loc, Parse_error.SetterArity) + | (false, (_, { Type.Function.Params.params = [_]; _ })) -> () + | (true, _) -> error_at env (key_loc, Parse_error.GetterArity) + | (false, _) -> error_at env (key_loc, Parse_error.SetterArity) + end; + Type.Object.Property. + { + key; + value = + ( if is_getter then + Get value + else + Set value + ); + optional = false; + static = static <> None; + proto = false; + _method = false; + variance = None; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + Type.Object.Property prop + in + let indexer_property env start_loc static variance ~leading = + let indexer = + with_loc + ~start_loc + (fun env -> + let id = + if Peek.ith_token ~i:1 env = T_COLON then ( + let id = identifier_name env in + Expect.token env T_COLON; + Some id + ) else + None + in + let key = _type env in + Expect.token env T_RBRACKET; + let trailing = Eat.trailing_comments env in + Expect.token env T_COLON; + let value = _type env in + { + Type.Object.Indexer.id; + key; + value; + static = static <> None; + variance; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + in + Type.Object.Indexer indexer + in + + let mapped_type env start_loc variance ~leading = + let mapped_type = + with_loc + ~start_loc + (fun env -> + let ((key_name_loc, _) as key_id) = type_identifier env in + let key_tparam = + { + Type.TypeParam.name = key_id; + bound = Ast.Type.Missing key_name_loc; + variance = None; + default = None; + bound_kind = Type.TypeParam.Colon; + const = None; + } + in + (* We already checked in mapped_type_or_indexer that the next token was an + * "in" identifier. Now we eat it. *) + Eat.token env; + let source_type = _type env in + Expect.token env T_RBRACKET; + let optional = + Type.Object.MappedType.( + match Peek.token env with + | T_PLING -> + Eat.token env; + Optional + | T_PLUS -> + Eat.token env; + Expect.token env T_PLING; + PlusOptional + | T_MINUS -> + Eat.token env; + Expect.token env T_PLING; + MinusOptional + | _ -> NoOptionalFlag + ) + in + Expect.token env T_COLON; + let prop_type = _type env in + let trailing = Eat.trailing_comments env in + { + Type.Object.MappedType.key_tparam = (key_name_loc, key_tparam); + source_type; + prop_type; + variance; + optional; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + in + Type.Object.MappedType mapped_type + in + + let mapped_type_or_indexer env start_loc static variance ~leading = + let leading = leading @ Peek.comments env in + Expect.token env T_LBRACKET; + match Peek.ith_token ~i:1 env with + | T_IDENTIFIER { raw = "in"; _ } when static = None -> + mapped_type env start_loc variance ~leading + | _ -> indexer_property env start_loc static variance ~leading + in + + let internal_slot env start_loc static ~leading = + let islot = + with_loc + ~start_loc + (fun env -> + let leading = leading @ Peek.comments env in + Expect.token env T_LBRACKET; + Expect.token env T_LBRACKET; + let id = identifier_name env in + Expect.token env T_RBRACKET; + Expect.token env T_RBRACKET; + let (optional, _method, value, trailing) = + match Peek.token env with + | T_LESS_THAN + | T_LPAREN -> + let tparams = + type_params_remove_trailing + env + ~kind:Flow_ast_mapper.FunctionTypeTP + (type_params env) + in + let value = + let (fn_loc, fn) = methodish env start_loc tparams in + (fn_loc, Type.Function fn) + in + (false, true, value, []) + | _ -> + let optional = Eat.maybe env T_PLING in + let trailing = Eat.trailing_comments env in + Expect.token env T_COLON; + let value = _type env in + (optional, false, value, trailing) + in + { + Type.Object.InternalSlot.id; + value; + optional; + static = static <> None; + _method; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + }) + env + in + Type.Object.InternalSlot islot + (* Expects the T_ELLIPSIS has already been eaten *) + in + let spread_property env start_loc ~leading = + let spread = + with_loc + ~start_loc + (fun env -> + { + Type.Object.SpreadProperty.argument = _type env; + comments = Flow_ast_utils.mk_comments_opt ~leading (); + }) + env + in + Type.Object.SpreadProperty spread + in + let semicolon exact env = + match Peek.token env with + | T_COMMA + | T_SEMICOLON -> + Eat.token env + | T_RCURLYBAR when exact -> () + | T_RCURLY when not exact -> () + | _ -> Expect.error env T_COMMA + in + let error_unexpected_variance env = function + | Some (loc, _) -> error_at env (loc, Parse_error.UnexpectedVariance) + | None -> () + in + let error_unexpected_proto env = function + | Some loc -> error_at env (loc, Parse_error.UnexpectedProto) + | None -> () + in + let error_invalid_property_name env is_class static key = + let is_static = static <> None in + let is_constructor = String.equal "constructor" in + let is_prototype = String.equal "prototype" in + match key with + | Expression.Object.Property.Identifier (loc, { Identifier.name; comments = _ }) + when is_class && (is_constructor name || (is_static && is_prototype name)) -> + error_at + env + ( loc, + Parse_error.InvalidClassMemberName + { name; static = is_static; method_ = false; private_ = false } + ) + | _ -> () + in + let rec properties + ~is_class ~allow_inexact ~allow_spread ~exact env ((props, inexact, internal) as acc) = + (* no `static ...A` *) + assert (not (is_class && allow_spread)); + + (* allow_inexact implies allow_spread *) + assert ((not allow_inexact) || allow_spread); + + let start_loc = Peek.loc env in + match Peek.token env with + | T_EOF -> (List.rev props, inexact, internal) + | T_RCURLYBAR when exact -> (List.rev props, inexact, internal) + | T_RCURLY when not exact -> (List.rev props, inexact, internal) + | T_ELLIPSIS when allow_spread -> + let leading = Peek.comments env in + Eat.token env; + begin + match Peek.token env with + | T_COMMA + | T_SEMICOLON + | T_RCURLY + | T_RCURLYBAR -> + semicolon exact env; + begin + match Peek.token env with + | T_RCURLY when allow_inexact -> (List.rev props, true, leading) + | T_RCURLYBAR -> + error_at env (start_loc, Parse_error.InexactInsideExact); + (List.rev props, inexact, internal) + | _ -> + error_at env (start_loc, Parse_error.UnexpectedExplicitInexactInObject); + properties ~is_class ~allow_inexact ~allow_spread ~exact env acc + end + | _ -> + let prop = spread_property env start_loc ~leading in + semicolon exact env; + properties + ~is_class + ~allow_inexact + ~allow_spread + ~exact + env + (prop :: props, inexact, internal) + end + (* In this case, allow_spread is false, so we may assume allow_inexact is false based on our + * assertion at the top of this function. Thus, any T_ELLIPSIS here is not allowed. + *) + | T_ELLIPSIS -> + Eat.token env; + begin + match Peek.token env with + | T_COMMA + | T_SEMICOLON + | T_RCURLY + | T_RCURLYBAR -> + error_at env (start_loc, Parse_error.InexactInsideNonObject); + semicolon exact env; + properties ~is_class ~allow_inexact ~allow_spread ~exact env acc + | _ -> + error_list env (Peek.errors env); + error_at env (start_loc, Parse_error.UnexpectedSpreadType); + + (* It's likely the user is trying to spread something here, so we can + * eat what they try to spread to try to continue parsing the remaining + * properties. + *) + Eat.token env; + semicolon exact env; + properties ~is_class ~allow_inexact ~allow_spread ~exact env acc + end + | _ -> + let prop = + property + env + start_loc + ~is_class + ~allow_static:is_class + ~allow_proto:is_class + ~variance:None + ~static:None + ~proto:None + ~leading:[] + in + semicolon exact env; + properties + ~is_class + ~allow_inexact + ~allow_spread + ~exact + env + (prop :: props, inexact, internal) + and property + env ~is_class ~allow_static ~allow_proto ~variance ~static ~proto ~leading start_loc = + match Peek.token env with + | T_PLUS + | T_MINUS + when variance = None -> + let variance = maybe_variance env in + property + env + ~is_class + ~allow_static:false + ~allow_proto:false + ~variance + ~static + ~proto + ~leading + start_loc + | T_STATIC when allow_static -> + assert (variance = None); + + (* if we parsed variance, allow_static = false *) + let static = Some (Peek.loc env) in + let leading = leading @ Peek.comments env in + Eat.token env; + property + env + ~is_class + ~allow_static:false + ~allow_proto:false + ~variance + ~static + ~proto + ~leading + start_loc + | T_IDENTIFIER { raw = "proto"; _ } when allow_proto -> + assert (variance = None); + + (* if we parsed variance, allow_proto = false *) + let proto = Some (Peek.loc env) in + let leading = leading @ Peek.comments env in + Eat.token env; + property + env + ~is_class + ~allow_static:false + ~allow_proto:false + ~variance + ~static + ~proto + ~leading + start_loc + | T_READONLY + when variance = None + && (Peek.ith_is_identifier ~i:1 env || Peek.ith_token ~i:1 env = T_LBRACKET) -> + let variance = maybe_variance ~parse_readonly:true env in + property + env + ~is_class + ~allow_static:false + ~allow_proto:false + ~variance + ~static + ~proto + ~leading + start_loc + | T_LBRACKET -> + error_unexpected_proto env proto; + (match Peek.ith_token ~i:1 env with + | T_LBRACKET -> + error_unexpected_variance env variance; + internal_slot env start_loc static ~leading + | _ -> mapped_type_or_indexer env start_loc static variance ~leading) + | T_LESS_THAN + | T_LPAREN -> + (* Note that `static(): void` is a static callable property if we + successfully parsed the static modifier above. *) + error_unexpected_proto env proto; + error_unexpected_variance env variance; + call_property env start_loc static ~leading + | token -> + (match (static, proto, token) with + | (Some _, Some _, _) -> failwith "Can not have both `static` and `proto`" + | (Some static_loc, None, (T_PLING | T_COLON)) -> + (* We speculatively parsed `static` as a static modifier, but now + that we've parsed the next token, we changed our minds and want + to parse `static` as the key of a named property. *) + let key = + Expression.Object.Property.Identifier + (Flow_ast_utils.ident_of_source + (static_loc, "static") + ?comments:(Flow_ast_utils.mk_comments_opt ~leading ()) + ) + in + let static = None in + init_property env start_loc ~variance ~static ~proto ~leading:[] (static_loc, key) + | (None, Some proto_loc, (T_PLING | T_COLON)) -> + (* We speculatively parsed `proto` as a proto modifier, but now + that we've parsed the next token, we changed our minds and want + to parse `proto` as the key of a named property. *) + let key = + Expression.Object.Property.Identifier + (Flow_ast_utils.ident_of_source + (proto_loc, "proto") + ?comments:(Flow_ast_utils.mk_comments_opt ~leading ()) + ) + in + let proto = None in + init_property env start_loc ~variance ~static ~proto ~leading:[] (proto_loc, key) + | _ -> + let object_key env = + Eat.push_lex_mode env Lex_mode.NORMAL; + let result = Parse.object_key env in + Eat.pop_lex_mode env; + result + in + let leading_key = Peek.comments env in + (match object_key env with + | ( key_loc, + ( Expression.Object.Property.Identifier + (_, { Identifier.name = ("get" | "set") as name; comments = _ }) as key + ) + ) -> begin + match Peek.token env with + | T_LESS_THAN + | T_LPAREN -> + error_unexpected_proto env proto; + error_unexpected_variance env variance; + method_property env start_loc static key ~leading + | T_COLON + | T_PLING -> + init_property env start_loc ~variance ~static ~proto ~leading (key_loc, key) + | _ -> + ignore (object_key_remove_trailing env key); + let key = object_key env in + let is_getter = name = "get" in + let leading = leading @ leading_key in + error_unexpected_proto env proto; + error_unexpected_variance env variance; + getter_or_setter ~is_getter ~leading env start_loc static key + end + | (key_loc, key) -> begin + match Peek.token env with + | T_LESS_THAN + | T_LPAREN -> + error_unexpected_proto env proto; + error_unexpected_variance env variance; + method_property env start_loc static key ~leading + | _ -> + error_invalid_property_name env is_class static key; + init_property env start_loc ~variance ~static ~proto ~leading (key_loc, key) + end)) + in + fun ~is_class ~allow_exact ~allow_spread env -> + let exact = allow_exact && Peek.token env = T_LCURLYBAR in + let allow_inexact = allow_exact && not exact in + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token + env + ( if exact then + T_LCURLYBAR + else + T_LCURLY + ); + let (properties, inexact, internal) = + let env = with_no_anon_function_type false env in + properties ~is_class ~allow_inexact ~exact ~allow_spread env ([], false, []) + in + let internal = internal @ Peek.comments env in + Expect.token + env + ( if exact then + T_RCURLYBAR + else + T_RCURLY + ); + let trailing = Eat.trailing_comments env in + + (* inexact = true iff `...` was used to indicate inexactnes *) + { + Type.Object.exact; + properties; + inexact; + comments = Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }) + env + + and interface_helper = + let rec supers env acc = + let super = generic env in + let acc = super :: acc in + match Peek.token env with + | T_COMMA -> + Expect.token env T_COMMA; + supers env acc + | _ -> List.rev acc + in + fun env -> + let extends = + if Eat.maybe env T_EXTENDS then + let extends = supers env [] in + generic_type_list_remove_trailing env extends + else + [] + in + let body = _object env ~allow_exact:false ~allow_spread:false ~is_class:false in + (extends, body) + + and type_identifier env = + let (loc, { Identifier.name; comments }) = identifier_name env in + if is_reserved_type name then error_at env (loc, Parse_error.UnexpectedReservedType); + (loc, { Identifier.name; comments }) + + and bounded_type env = + with_loc + (fun env -> + let name = type_identifier env in + let (bound, bound_kind) = + match Peek.token env with + | T_COLON -> (Ast.Type.Available (annotation env), Ast.Type.TypeParam.Colon) + | T_EXTENDS -> + ( Ast.Type.Available + (with_loc + (fun env -> + Eat.token env; + _type env) + env + ), + Ast.Type.TypeParam.Extends + ) + | _ -> (Ast.Type.Missing (Peek.loc_skip_lookahead env), Ast.Type.TypeParam.Colon) + in + (name, bound, bound_kind)) + env + + and type_params = + (* whether we should consume [token] as a type param. a type param can + either start with an identifier or a variance sigil; we'll also parse + types like `number` to improve error recovery. *) + let token_is_maybe_param env token = + token_is_type_identifier env token || token_is_variance token || token_is_reserved_type token + in + (* whether an unexpected [token] should signal the end of the param list. + these are tokens that are likely to follow a param list, if the closing + > is missing. This improves error recovery when you add type params + to an existing node. + + Note that we're in Lex_mode.TYPE here, so the tokens are those produced + by [Flow_lexer.type_token]. *) + let token_is_maybe_end_of_list env token = + match token with + (* Reserved words are lexed as identifiers in Lex_env.TYPE mode (if + they're not also reserved types). e.g. `switch` is a T_IDENTIFIER. + we're not expecting a type identifier, so let's assume it's a + NORMAL-mode keyword and end the list. *) + | T_IDENTIFIER { raw; _ } when is_reserved raw || is_contextually_reserved raw -> true + (* adding a type above an enum: `type T true + (* adding a type above another: `type T + true + | _ -> false + in + let rec params env ~require_default acc = + let (acc, require_default) = + if token_is_maybe_param env (Peek.token env) then + let (param, require_default) = + with_loc_extra + (fun env -> + let const = maybe_const env in + let variance = maybe_variance ~parse_in_out:true env in + let (loc, (name, bound, bound_kind)) = bounded_type env in + let (default, require_default) = + match Peek.token env with + | T_ASSIGN -> + Eat.token env; + (Some (_type env), true) + | _ -> + if require_default then error_at env (loc, Parse_error.MissingTypeParamDefault); + (None, require_default) + in + ( { Type.TypeParam.name; bound; bound_kind; variance; default; const }, + require_default + )) + env + in + (param :: acc, require_default) + else + (acc, require_default) + in + match Peek.token env with + | T_EOF + | T_GREATER_THAN -> + (* end of list *) + List.rev acc + | T_COMMA -> + (* handle multiple params *) + Eat.token env; + params env ~require_default acc + | token when token_is_maybe_end_of_list env token -> + (* error recovery: tokens likely to follow a param list *) + Expect.error env T_GREATER_THAN; + List.rev acc + | token when token_is_maybe_param env token -> + (* recover from a missing comma between items by not consuming the token *) + Expect.error env T_COMMA; + params env ~require_default acc + | _ -> + (* unexpected token. consume it until we hit the end of the list *) + Expect.token env T_COMMA; + params env ~require_default acc + in + fun env -> + if Peek.token env = T_LESS_THAN then ( + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + let ((loc, { Type.TypeParams.params; _ }) as result) = + with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LESS_THAN; + let params = params env ~require_default:false [] in + let internal = Peek.comments env in + Expect.token_opt env T_GREATER_THAN; + let trailing = Eat.trailing_comments env in + let comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal () + in + { Type.TypeParams.params; comments }) + env + in + (match params with + | [] -> error_at env (loc, Parse_error.MissingTypeParam) + | _ -> ()); + Some result + ) else + None + + and type_args = + let rec args env acc = + match Peek.token env with + | T_EOF + | T_GREATER_THAN -> + List.rev acc + | _ -> + let acc = _type env :: acc in + if Peek.token env <> T_GREATER_THAN then Expect.token env T_COMMA; + args env acc + in + fun env -> + if Peek.token env = T_LESS_THAN then + Some + (with_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_LESS_THAN; + let env = with_no_anon_function_type false env in + let arguments = args env [] in + let internal = Peek.comments env in + Expect.token env T_GREATER_THAN; + let trailing = Eat.trailing_comments env in + { + Type.TypeArgs.arguments; + comments = + Flow_ast_utils.mk_comments_with_internal_opt ~leading ~trailing ~internal (); + }) + env + ) + else + None + + and generic env = raw_generic_with_identifier env (type_identifier env) + + and raw_generic_with_identifier = + let rec identifier env (q_loc, qualification) = + if Peek.token env = T_PERIOD && Peek.ith_is_type_identifier ~i:1 env then + let (loc, q) = + with_loc + ~start_loc:q_loc + (fun env -> + Expect.token env T_PERIOD; + let id = type_identifier env in + { Type.Generic.Identifier.qualification; id }) + env + in + let qualification = Type.Generic.Identifier.Qualified (loc, q) in + identifier env (loc, qualification) + else + (q_loc, qualification) + in + fun env id -> + with_loc + ~start_loc:(fst id) + (fun env -> + let id = (fst id, Type.Generic.Identifier.Unqualified id) in + let id = + let (_id_loc, id) = identifier env id in + if Peek.token env <> T_LESS_THAN then + id + else + let { remove_trailing; _ } = trailing_and_remover env in + remove_trailing id (fun remover id -> remover#generic_identifier_type id) + in + let targs = type_args env in + { Type.Generic.id; targs; comments = None }) + env + + and generic_type_with_identifier env id = + let (loc, generic) = raw_generic_with_identifier env id in + (loc, Type.Generic generic) + + and function_return_annotation_opt env = + match Peek.token env with + | T_COLON -> function_return_annotation env + | _ -> Function.ReturnAnnot.Missing (Peek.loc_skip_lookahead env) + + and annotation_opt env = + match Peek.token env with + | T_COLON -> Type.Available (annotation env) + | _ -> Type.Missing (Peek.loc_skip_lookahead env) + + and renders_annotation_opt env = + match Peek.token env with + | T_COLON -> + let operator_loc = Peek.loc env in + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + Eat.token env; + let (loc, argument) = with_loc _type env in + let has_nested_render = + match argument with + | (_, Ast.Type.Renders _) -> true + | _ -> false + in + error_at env (operator_loc, Parse_error.InvalidComponentRenderAnnotation { has_nested_render }); + Type.AvailableRenders + ( loc, + { + Ast.Type.Renders.operator_loc; + argument; + variant = Ast.Type.Renders.Normal; + comments = None; + } + ) + | T_IDENTIFIER { raw = "renders"; _ } + | T_RENDERS_QUESTION + | T_RENDERS_STAR -> + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + let (loc, renders) = with_loc ~start_loc:(Peek.loc env) render_type env in + Type.AvailableRenders (loc, renders) + | _ -> Type.MissingRenders (Peek.loc_skip_lookahead env) + + and add_comments (loc, t) leading trailing = + let merge_comments inner = + Flow_ast_utils.merge_comments + ~inner + ~outer:(Flow_ast_utils.mk_comments_opt ~leading ~trailing ()) + in + let merge_comments_with_internal inner = + Flow_ast_utils.merge_comments_with_internal + ~inner + ~outer:(Flow_ast_utils.mk_comments_opt ~leading ~trailing ()) + in + let open Ast.Type in + ( loc, + match t with + | Any comments -> Any (merge_comments comments) + | Mixed comments -> Mixed (merge_comments comments) + | Empty comments -> Empty (merge_comments comments) + | Void comments -> Void (merge_comments comments) + | Null comments -> Null (merge_comments comments) + | Number comments -> Number (merge_comments comments) + | BigInt comments -> BigInt (merge_comments comments) + | String comments -> String (merge_comments comments) + | Boolean ({ comments; _ } as t) -> Boolean { t with comments = merge_comments comments } + | Symbol comments -> Symbol (merge_comments comments) + | Exists comments -> Exists (merge_comments comments) + | Unknown comments -> Unknown (merge_comments comments) + | Never comments -> Never (merge_comments comments) + | Undefined comments -> Undefined (merge_comments comments) + | Nullable ({ Nullable.comments; _ } as t) -> + Nullable { t with Nullable.comments = merge_comments comments } + | Function ({ Function.comments; _ } as t) -> + Function { t with Function.comments = merge_comments comments } + | Component ({ Component.comments; _ } as t) -> + Component { t with Component.comments = merge_comments comments } + | Object ({ Object.comments; _ } as t) -> + Object { t with Object.comments = merge_comments_with_internal comments } + | Interface ({ Interface.comments; _ } as t) -> + Interface { t with Interface.comments = merge_comments comments } + | Array ({ Array.comments; _ } as t) -> + Array { t with Array.comments = merge_comments comments } + | Conditional ({ Conditional.comments; _ } as t) -> + Conditional { t with Conditional.comments = merge_comments comments } + | Infer ({ Infer.comments; _ } as t) -> + Infer { t with Infer.comments = merge_comments comments } + | Generic ({ Generic.comments; _ } as t) -> + Generic { t with Generic.comments = merge_comments comments } + | IndexedAccess ({ IndexedAccess.comments; _ } as t) -> + IndexedAccess { t with IndexedAccess.comments = merge_comments comments } + | OptionalIndexedAccess + { + OptionalIndexedAccess.indexed_access = { IndexedAccess.comments; _ } as indexed_access; + optional; + } -> + OptionalIndexedAccess + { + OptionalIndexedAccess.indexed_access = + { indexed_access with IndexedAccess.comments = merge_comments comments }; + optional; + } + | Union ({ Union.comments; _ } as t) -> + Union { t with Union.comments = merge_comments comments } + | Intersection ({ Intersection.comments; _ } as t) -> + Intersection { t with Intersection.comments = merge_comments comments } + | Typeof ({ Typeof.comments; _ } as t) -> + Typeof { t with Typeof.comments = merge_comments comments } + | Keyof ({ Keyof.comments; _ } as t) -> + Keyof { t with Keyof.comments = merge_comments comments } + | Renders ({ Renders.comments; _ } as t) -> + Renders { t with Renders.comments = merge_comments comments } + | ReadOnly ({ ReadOnly.comments; _ } as t) -> + ReadOnly { t with ReadOnly.comments = merge_comments comments } + | Tuple ({ Tuple.comments; _ } as t) -> + Tuple { t with Tuple.comments = merge_comments comments } + | StringLiteral ({ StringLiteral.comments; _ } as t) -> + StringLiteral { t with StringLiteral.comments = merge_comments comments } + | NumberLiteral ({ NumberLiteral.comments; _ } as t) -> + NumberLiteral { t with NumberLiteral.comments = merge_comments comments } + | BigIntLiteral ({ BigIntLiteral.comments; _ } as t) -> + BigIntLiteral { t with BigIntLiteral.comments = merge_comments comments } + | BooleanLiteral ({ BooleanLiteral.comments; _ } as t) -> + BooleanLiteral { t with BooleanLiteral.comments = merge_comments comments } + ) + + let predicate_checks_contents env ~leading = + let open Ast.Type.Predicate in + if Peek.token env = T_LPAREN then ( + let leading = leading @ Peek.comments env in + Expect.token env T_LPAREN; + Eat.push_lex_mode env Lex_mode.NORMAL; + let exp = Parse.conditional env in + Eat.pop_lex_mode env; + Expect.token env T_RPAREN; + let trailing = Eat.trailing_comments env in + { kind = Declared exp; comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing () } + ) else + let trailing = Eat.trailing_comments env in + { + kind = Ast.Type.Predicate.Inferred; + comments = Flow_ast_utils.mk_comments_opt ~leading ~trailing (); + } + + let predicate = + with_loc (fun env -> + let leading = Peek.comments env in + Expect.token env T_CHECKS; + predicate_checks_contents env ~leading + ) + + let predicate_opt env = + let env = with_no_anon_function_type false env in + match Peek.token env with + | T_CHECKS -> Some (predicate env) + | _ -> None + + let no_annot_predicate env ~start_loc = + let env = with_no_anon_function_type false env in + with_loc + ~start_loc + (fun env -> + let leading = Peek.comments env in + Expect.token env T_CHECKS; + predicate_checks_contents env ~leading) + env + + let function_return_annotation_and_predicate env = + let open Ast.Function.ReturnAnnot in + if not (should_parse_types env) then error env Parse_error.UnexpectedTypeAnnotation; + let missing_loc = Peek.loc_skip_lookahead env in + let start_loc = Peek.loc env in + Expect.token env T_COLON; + match Peek.token env with + | T_CHECKS -> + let predicate = no_annot_predicate env ~start_loc in + (Missing missing_loc, Some predicate) + | _ -> + if is_start_of_type_guard env then + (TypeGuard (type_guard_annotation env ~start_loc), None) + else + let annotation = + let annotation = Available (with_loc ~start_loc _type env) in + if Peek.token env = T_CHECKS then + return_annotation_remove_trailing env annotation + else + annotation + in + let predicate = predicate_opt env in + (annotation, predicate) + + let function_return_annotation_and_predicate_opt env = + let open Ast.Function.ReturnAnnot in + match Peek.token env with + | T_COLON -> function_return_annotation_and_predicate env + | _ -> (Missing (Peek.loc_skip_lookahead env), None) + + let wrap f env = + let env = env |> with_strict true in + Eat.push_lex_mode env Lex_mode.TYPE; + let ret = f env in + Eat.pop_lex_mode env; + ret + + let _type = wrap _type + + let type_identifier = wrap type_identifier + + let type_params = wrap type_params + + let type_args = wrap type_args + + let _object ~is_class env = wrap (_object ~is_class ~allow_exact:false ~allow_spread:false) env + + let interface_helper = wrap interface_helper + + let function_param_list = wrap function_param_list + + let annotation = wrap annotation + + let annotation_opt = wrap annotation_opt + + let function_return_annotation_opt = wrap function_return_annotation_opt + + let predicate_opt = wrap predicate_opt + + let function_return_annotation_and_predicate_opt = + wrap function_return_annotation_and_predicate_opt + + let component_param_list = wrap component_param_list + + let generic = wrap generic + + let renders_annotation_opt = wrap renders_annotation_opt +end diff --git a/compiler/flow_parser/parser/type_parser.mli b/compiler/flow_parser/parser/type_parser.mli new file mode 100644 index 00000000000..bc73503c7ab --- /dev/null +++ b/compiler/flow_parser/parser/type_parser.mli @@ -0,0 +1,8 @@ +(* + * Copyright (c) Meta Platforms, Inc. and affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + *) + +module Type (_ : Parser_common.PARSER) : Parser_common.TYPE diff --git a/dune-project b/dune-project index f0a49fc9eaf..7f958407683 100644 --- a/dune-project +++ b/dune-project @@ -21,8 +21,13 @@ (ocaml (>= 5.0.0)) dune - (flow_parser - (= 0.267.0)) + (base + (>= v0.16.3)) + (ppxlib + (>= 0.32.1)) + (ppx_deriving :build) + (ppx_gen_rec :build) + wtf8 (ocamlformat (and :with-test (= 0.27.0))) (yojson diff --git a/rescript.opam b/rescript.opam index 0b010298ca9..a800542d7b7 100644 --- a/rescript.opam +++ b/rescript.opam @@ -9,7 +9,11 @@ bug-reports: "https://github.com/rescript-lang/rescript-compiler/issues" depends: [ "ocaml" {>= "5.0.0"} "dune" {>= "3.17"} - "flow_parser" {= "0.267.0"} + "base" {>= "v0.16.3"} + "ppxlib" {>= "0.32.1"} + "ppx_deriving" {build} + "ppx_gen_rec" {build} + "wtf8" "ocamlformat" {with-test & = "0.27.0"} "yojson" {= "3.0.0"} "ounit2" {with-test & = "2.2.7"} @@ -33,6 +37,3 @@ build: [ "@doc" {with-doc} ] ] -pin-depends: [ - ["flow_parser.0.267.0" "git+https://github.com/rescript-lang/flow.git#9ea4062c0b7e037415c4413a7634c459ebd5c31b"] -] diff --git a/rescript.opam.template b/rescript.opam.template deleted file mode 100644 index 5f168c67302..00000000000 --- a/rescript.opam.template +++ /dev/null @@ -1,3 +0,0 @@ -pin-depends: [ - ["flow_parser.0.267.0" "git+https://github.com/rescript-lang/flow.git#9ea4062c0b7e037415c4413a7634c459ebd5c31b"] -] diff --git a/tests/ounit_tests/ounit_flow_parser_tests.ml b/tests/ounit_tests/ounit_flow_parser_tests.ml new file mode 100644 index 00000000000..c1a8f07745f --- /dev/null +++ b/tests/ounit_tests/ounit_flow_parser_tests.ml @@ -0,0 +1,53 @@ +let ( >:: ), ( >::: ) = OUnit.(( >:: ), ( >::: )) + +let assert_classification expected source = + OUnit.assert_equal expected (Classify_function.classify source) + +let parse_expression source = + let open Parser_flow in + let env = Parser_env.init_env None source in + do_parse env Parse.expression false + +let suites = + __FILE__ + >::: [ + ( __LOC__ >:: fun _ -> + assert_classification + (Js_raw_info.Js_function {arity = 2; arrow = false}) + "function (x, y) { return x + y; }" ); + ( __LOC__ >:: fun _ -> + assert_classification + (Js_raw_info.Js_function {arity = 2; arrow = true}) + "(x, y) => x + y" ); + ( __LOC__ >:: fun _ -> + assert_classification + (Js_raw_info.Js_literal {comment = None}) + "{x: [1, -2, null, undefined]}" ); + ( __LOC__ >:: fun _ -> + assert_classification + (Js_raw_info.Js_literal {comment = Some "/* keep */"}) + "/* keep */ 1" ); + ( __LOC__ >:: fun _ -> + assert_classification Js_raw_info.Js_exp_unknown "value + 1"; + assert_classification Js_raw_info.Js_exp_unknown "{...value}" ); + ( __LOC__ >:: fun _ -> + let (_, expression), errors = parse_expression "/rescript/gi" in + OUnit.assert_equal [] errors; + match expression with + | Flow_ast.Expression.RegExpLiteral _ -> () + | _ -> OUnit.assert_failure "expected a regular expression literal" + ); + ( __LOC__ >:: fun _ -> + let _, errors = parse_expression "1 +" in + match errors with + | ({Loc.start; _end}, _) :: _ -> + OUnit.assert_equal 1 start.line; + OUnit.assert_equal 1 _end.line; + OUnit.assert_bool __LOC__ (_end.column >= start.column) + | [] -> OUnit.assert_failure "expected a parser error" ); + ( __LOC__ >:: fun _ -> + OUnit.assert_equal Js_raw_info.Js_stmt_comment + (Classify_function.classify_stmt "// only a comment"); + OUnit.assert_equal Js_raw_info.Js_stmt_unknown + (Classify_function.classify_stmt "console.log('hello')") ); + ] diff --git a/tests/ounit_tests/ounit_tests_main.ml b/tests/ounit_tests/ounit_tests_main.ml index eb14ecdd66f..e5dd0252319 100644 --- a/tests/ounit_tests/ounit_tests_main.ml +++ b/tests/ounit_tests/ounit_tests_main.ml @@ -22,6 +22,7 @@ let suites = Ounit_ast_mapper0_tests.suites; Ounit_pattern_printer_tests.suites; Ounit_js_analyzer_tests.suites; + Ounit_flow_parser_tests.suites; Ounit_jsx_loc_tests.suites; Ounit_analysis_config_tests.suites; Ounit_analysis_references_tests.suites;