diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1844c4b0b..d798386054 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -14,6 +14,7 @@
#### :boom: Breaking Change
+- Remove `%re`; use regexp literals instead. https://github.com/rescript-lang/rescript/pull/8610
- Reject malformed UTF-8 in documentation comments and invalid string or template literal escapes that were previously accepted, including empty or out-of-range braced Unicode escapes (`\u{}`, `\u{110000}`) and legacy decimal or octal escapes in templates (`\1`, `\01`, `\8`). These inputs now produce syntax diagnostics instead of compiling to invalid or inconsistent JavaScript. https://github.com/rescript-lang/rescript/pull/8606
- Reject tagged template literals in patterns. Patterns cannot invoke their tag; previously their raw payload was compiled as a plain string comparison. https://github.com/rescript-lang/rescript/pull/8606
- Remove runtime APIs that were deprecated for removal in ReScript 13, including the `Char` module, unsafe `Obj` operations, legacy `Pervasives` helpers, and `Array.unsafe_get`. https://github.com/rescript-lang/rescript/pull/8564
@@ -37,6 +38,7 @@
#### :bug: Bug fix
- Fix constant folding of pattern matches on unboxed variants whose payload overlaps a literal constructor, so inlined calls agree with runtime matching. Reject multi-argument unboxed constructors instead of crashing. https://github.com/rescript-lang/rescript/pull/8631
+- Preserve JSX closing tags when recovering from missing prop values. https://github.com/rescript-lang/rescript/pull/8610
- Fix escaped backticks and interpolation openers in backquoted `%raw`, `%ffi`, and `%re` payloads leaking into emitted JavaScript. https://github.com/rescript-lang/rescript/pull/8630
- Fix the side-effect analysis treating bigint exponentiation and bounds-checked array and string reads as pure, which let dead-code elimination drop an unused one that throws: `let _ = 2n ** -1n` no longer raised. https://github.com/rescript-lang/rescript/pull/8617
- Preserve record field `@as` annotations when formatting object types containing spreads. https://github.com/rescript-lang/rescript/pull/8619
@@ -79,6 +81,7 @@
#### :house: Internal
+- Give regexp literals a dedicated AST node. https://github.com/rescript-lang/rescript/pull/8610
- Developer playground: Make panes resizable with wrapping text. https://github.com/rescript-lang/rescript/pull/8628
- Normalize Lambda terms where they are built: a match guard stays structured data until its fallthrough is known, and `apply` and `mk_builtin` go through the folding constructors. https://github.com/rescript-lang/rescript/pull/8615
- Replace non-escaping local mutable blocks with scalar bindings when all uses are direct field accesses, generalizing reference unboxing to multi-field records and references captured by JavaScript closures. https://github.com/rescript-lang/rescript/pull/8617
diff --git a/analysis/src/completion_jsx.ml b/analysis/src/completion_jsx.ml
index feb9196afd..0cce7785ce 100644
--- a/analysis/src/completion_jsx.ml
+++ b/analysis/src/completion_jsx.ml
@@ -284,32 +284,6 @@ type jsx_props = {
children_start: (int * int) option;
}
-(**
-
-
-This is a special case for JSX props, where the above code is parsed
-as
Loc.end_ = (Location.none |> Loc.end_) ->
- true
- | _ -> false
-
let find_jsx_props_completable ~jsx_props ~end_pos ~pos_before_cursor
~first_char_before_cursor_no_white ~char_at_cursor ~pos_after_comp_name =
let all_labels =
@@ -392,14 +366,9 @@ let find_jsx_props_completable ~jsx_props ~end_pos ~pos_before_cursor
else if prop.exp.pexp_loc |> Loc.end_ = (Location.none |> Loc.end_) then (
if Debug.verbose () then
print_endline "[jsx_props_completable]--> Loc is broken";
- if
- Completion_expressions.is_expr_hole prop.exp
- || is_regexp_jsx_heuristic_expr prop.exp
- then (
+ if Completion_expressions.is_expr_hole prop.exp then (
if Debug.verbose () then
- print_endline
- "[jsx_props_completable]--> Expr was expr hole or regexp literal \
- heuristic";
+ print_endline "[jsx_props_completable]--> Expr was expr hole";
Some
(Cexpression
{
diff --git a/analysis/src/dump_ast.ml b/analysis/src/dump_ast.ml
index aa30c5d461..2819297b17 100644
--- a/analysis/src/dump_ast.ml
+++ b/analysis/src/dump_ast.ml
@@ -267,6 +267,8 @@ and print_expr_item expr ~pos ~indentation =
^ ")"
| Pexp_extension (({txt} as loc), _) ->
"Pexp_extension(%" ^ (loc |> print_loc_denominator_loc ~pos) ^ txt ^ ")"
+ | Pexp_regexp {pattern; flags} ->
+ "Pexp_regexp(/" ^ pattern ^ "/" ^ flags ^ ")"
| Pexp_template {source_segments; values} ->
"Pexp_template(source_segments=["
^ String.concat ", " (List.map (fun {Asttypes.txt} -> txt) source_segments)
diff --git a/analysis/src/utils.ml b/analysis/src/utils.ml
index 4850604eb5..be405cb20e 100644
--- a/analysis/src/utils.ml
+++ b/analysis/src/utils.ml
@@ -118,6 +118,7 @@ let identify_pexp pexp =
| Pexp_open _ -> "Pexp_open"
| Pexp_await _ -> "Pexp_await"
| Pexp_jsx_element _ -> "Pexp_jsx_element"
+ | Pexp_regexp _ -> "Pexp_regexp"
| Pexp_template _ -> "Pexp_template"
| Pexp_tagged_template _ -> "Pexp_tagged_template"
diff --git a/compiler/ext/config.ml b/compiler/ext/config.ml
index 30318442fa..84b5b3856c 100644
--- a/compiler/ext/config.ml
+++ b/compiler/ext/config.ml
@@ -2,9 +2,9 @@ let cmi_magic_number = "Caml1999I034"
(* Magic numbers for marshaled values of the *current* parsetree, whose layout
changes across compiler versions. *)
-and ast_impl_magic_number = "ResImpl01307"
+and ast_impl_magic_number = "ResImpl01308"
-and ast_intf_magic_number = "ResIntf01307"
+and ast_intf_magic_number = "ResIntf01308"
(* Magic numbers of the frozen Parsetree0 (OCaml 4.06) layout used on the
external-PPX wire. They must never be written in front of a
diff --git a/compiler/frontend/ast_exp_extension.ml b/compiler/frontend/ast_exp_extension.ml
index bc51df89a9..072d5d0a78 100644
--- a/compiler/frontend/ast_exp_extension.ml
+++ b/compiler/frontend/ast_exp_extension.ml
@@ -56,11 +56,7 @@ let handle_extension e (_self : Ast_mapper.mapper)
| Some msg -> " - Todo: " ^ msg)) );
]
| "ffi" -> Ast_exp_handle_external.handle_ffi ~loc ~payload
- | "raw" -> Ast_exp_handle_external.handle_raw ~kind:Raw_exp loc payload
- | "re" ->
- Exp.constraint_ ~loc
- (Ast_exp_handle_external.handle_raw ~kind:Raw_re loc payload)
- (Ast_comb.to_regexp_type loc)
+ | "raw" -> Ast_exp_handle_external.handle_raw loc payload
| "debugger" ->
{e with pexp_desc = Ast_exp_handle_external.handle_debugger loc payload}
| _ -> e
diff --git a/compiler/frontend/ast_exp_handle_external.ml b/compiler/frontend/ast_exp_handle_external.ml
index 0607617bdc..4ab1212186 100644
--- a/compiler/frontend/ast_exp_handle_external.ml
+++ b/compiler/frontend/ast_exp_handle_external.ml
@@ -34,20 +34,13 @@ let handle_debugger loc (payload : Ast_payload.t) =
| _ ->
Location.raise_errorf ~loc "%%debugger extension doesn't accept arguments"
-let handle_raw ~kind loc payload =
+let handle_raw loc payload =
let is_function = ref None in
- match Ast_payload.raw_as_string_exp_exn ~kind ~is_function payload with
- | None -> (
- match kind with
- | Raw_re ->
- Location.raise_errorf ~loc
- "%%re extension can only be applied to a string"
- | Raw_exp ->
- Location.raise_errorf ~loc
- "%%raw extension can only be applied to a string"
- | Raw_program ->
- Location.raise_errorf ~loc
- "%%%%raw extension can only be applied to a string")
+ match
+ Ast_payload.raw_as_string_exp_exn ~kind:Raw_exp ~is_function payload
+ with
+ | None ->
+ Location.raise_errorf ~loc "%%raw extension can only be applied to a string"
| Some exp ->
{
exp with
diff --git a/compiler/frontend/ast_exp_handle_external.mli b/compiler/frontend/ast_exp_handle_external.mli
index 74829c3547..c8cb803bee 100644
--- a/compiler/frontend/ast_exp_handle_external.mli
+++ b/compiler/frontend/ast_exp_handle_external.mli
@@ -26,11 +26,7 @@ val handle_debugger : Location.t -> Ast_payload.t -> Parsetree.expression_desc
val handle_ffi : loc:Location.t -> payload:Ast_payload.t -> Parsetree.expression
-val handle_raw :
- kind:Js_raw_info.raw_kind ->
- Location.t ->
- Ast_payload.t ->
- Parsetree.expression
+val handle_raw : Location.t -> Ast_payload.t -> Parsetree.expression
val handle_raw_structure :
Location.t -> Ast_payload.t -> Parsetree.structure_item
diff --git a/compiler/frontend/bs_builtin_ppx.ml b/compiler/frontend/bs_builtin_ppx.ml
index 8886205d14..15cb024374 100644
--- a/compiler/frontend/bs_builtin_ppx.ml
+++ b/compiler/frontend/bs_builtin_ppx.ml
@@ -112,6 +112,23 @@ let expr_mapper ~async_context ~in_function_def (self : mapper)
in_function_def := false;
match e.pexp_desc with
(* Its output should not be rewritten anymore *)
+ | Pexp_regexp {pattern; flags} ->
+ let loc = e.pexp_loc in
+ let source = "/" ^ pattern ^ "/" ^ flags in
+ Ast_payload.validate_raw_source ~kind:Raw_re ~loc ~offset:0 source;
+ let raw =
+ Ast_external_mk.local_external_apply loc
+ ~pval_prim:(Prim_name "#raw_expr")
+ ~pval_type:
+ (Ast_helper.Typ.arrow
+ [{attrs = []; lbl = Nolabel; typ = Ast_helper.Typ.any ()}]
+ (Ast_helper.Typ.any ()))
+ [Ast_helper.Exp.constant ~loc (Pconst_raw_source source)]
+ in
+ Ast_helper.Exp.constraint_ ~loc
+ ~attrs:(self.attributes self e.pexp_attributes)
+ {e with pexp_desc = raw; pexp_attributes = []}
+ (Ast_comb.to_regexp_type loc)
| Pexp_extension extension ->
Ast_exp_extension.handle_extension e self extension
| Pexp_constant (Pconst_integer (s, Some 'l')) ->
diff --git a/compiler/ml/ast_helper.ml b/compiler/ml/ast_helper.ml
index 7dbc41bb1a..b08d7bca33 100644
--- a/compiler/ml/ast_helper.ml
+++ b/compiler/ml/ast_helper.ml
@@ -205,6 +205,8 @@ module Exp = struct
let pack ?loc ?attrs a = mk ?loc ?attrs (Pexp_pack a)
let open_ ?loc ?attrs a b c = mk ?loc ?attrs (Pexp_open (a, b, c))
let extension ?loc ?attrs a = mk ?loc ?attrs (Pexp_extension a)
+ let regexp ?loc ?attrs pattern flags =
+ mk ?loc ?attrs (Pexp_regexp {pattern; flags})
let template ?loc ?attrs source_segments values =
mk ?loc ?attrs (Pexp_template {source_segments; values})
let tagged_template ?loc ?attrs tag raw_sources values =
diff --git a/compiler/ml/ast_helper.mli b/compiler/ml/ast_helper.mli
index 3ba39f4440..a4947466e4 100644
--- a/compiler/ml/ast_helper.mli
+++ b/compiler/ml/ast_helper.mli
@@ -226,6 +226,8 @@ module Exp : sig
val object_literal :
?loc:loc -> ?attrs:attrs -> (str * expression) list -> expression
+ val regexp : ?loc:loc -> ?attrs:attrs -> string -> string -> expression
+
val template :
?loc:loc -> ?attrs:attrs -> str list -> expression list -> expression
val letmodule :
diff --git a/compiler/ml/ast_iterator.ml b/compiler/ml/ast_iterator.ml
index a4b6fa9cd6..cbb9fb7219 100644
--- a/compiler/ml/ast_iterator.ml
+++ b/compiler/ml/ast_iterator.ml
@@ -390,6 +390,7 @@ module E = struct
iter_loc sub lid;
sub.expr sub e
| Pexp_extension x -> sub.extension sub x
+ | Pexp_regexp _ -> ()
| Pexp_template {values} -> List.iter (sub.expr sub) values
| Pexp_tagged_template {tag; values} ->
sub.expr sub tag;
diff --git a/compiler/ml/ast_mapper.ml b/compiler/ml/ast_mapper.ml
index 207d2880cb..029d95e300 100644
--- a/compiler/ml/ast_mapper.ml
+++ b/compiler/ml/ast_mapper.ml
@@ -352,6 +352,7 @@ module E = struct
| Pexp_for_await_of (p, e1, e2) ->
Exp.mk ~loc ~attrs
(Pexp_for_await_of (sub.pat sub p, sub.expr sub e1, sub.expr sub e2))
+ | Pexp_regexp {pattern; flags} -> regexp ~loc ~attrs pattern flags
| Pexp_template {source_segments; values} ->
Exp.template ~loc ~attrs
(List.map (map_loc sub) source_segments)
diff --git a/compiler/ml/ast_mapper_from0.ml b/compiler/ml/ast_mapper_from0.ml
index 858d93c7ae..d229e75362 100644
--- a/compiler/ml/ast_mapper_from0.ml
+++ b/compiler/ml/ast_mapper_from0.ml
@@ -123,7 +123,7 @@ let map_pattern_constant ~loc = function
| constant -> map_constant ~loc constant
let is_raw_source_extension = function
- | "raw" | "ffi" | "re" -> true
+ | "raw" | "ffi" -> true
| _ -> false
let map_raw_source_payload sub = function
@@ -1132,6 +1132,49 @@ module E = struct
| Pexp_pack me -> pack ~loc ~attrs (sub.module_expr sub me)
| Pexp_open (ovf, lid, e) ->
open_ ~loc ~attrs ovf (map_loc sub lid) (sub.expr sub e)
+ | Pexp_extension ({txt = "re"}, payload) -> (
+ let malformed ~loc =
+ Location.raise_errorf ~loc
+ "A PPX returned a malformed regexp payload. Expected a string \
+ containing one regexp literal."
+ in
+ match payload with
+ | PStr
+ [
+ {
+ pstr_desc =
+ Pstr_eval
+ ( {
+ pexp_desc = Pexp_constant (Pconst_string (source, _));
+ pexp_loc = source_loc;
+ pexp_attributes = source_attrs;
+ },
+ eval_attrs );
+ };
+ ] -> (
+ let env = Parser_env.init_env None source in
+ let (_, expression), errors =
+ Parser_flow.do_parse env Parser_flow.Parse.expression false
+ in
+ match expression with
+ | Flow_ast.Expression.RegExpLiteral {pattern; raw}
+ when errors = [] && Parser_env.Peek.token env = Token.T_EOF ->
+ (* Flow filters unknown flags in its [flags] field. Keep the raw
+ spelling so the bridge never silently changes a PPX's regexp. *)
+ let flags_start = String.length pattern + 2 in
+ let flags =
+ String.sub raw flags_start (String.length raw - flags_start)
+ in
+ (* Payload wrappers disappear at this boundary. Keep the expression's
+ location and transfer both levels of payload attributes to it. *)
+ regexp ~loc
+ ~attrs:
+ (attrs
+ @ sub.attributes sub eval_attrs
+ @ sub.attributes sub source_attrs)
+ pattern flags
+ | _ -> malformed ~loc:(sub.location sub source_loc))
+ | _ -> malformed ~loc)
| Pexp_extension x -> extension ~loc ~attrs (sub.extension sub x)
| Pexp_unreachable -> assert false
end
@@ -1369,6 +1412,9 @@ let default_mapper =
location = (fun _this l -> l);
extension =
(fun this (s, payload) ->
+ if s.txt = "re" then
+ Location.raise_errorf ~loc:(this.location this s.loc)
+ "A PPX returned a regexp extension outside an expression.";
let payload =
if is_raw_source_extension s.txt then
match map_raw_source_payload this payload with
diff --git a/compiler/ml/ast_mapper_to0.ml b/compiler/ml/ast_mapper_to0.ml
index 2ba915dd92..446d7e3ec6 100644
--- a/compiler/ml/ast_mapper_to0.ml
+++ b/compiler/ml/ast_mapper_to0.ml
@@ -652,6 +652,16 @@ module E = struct
~attrs:(for_await_of_attr :: attrs)
(sub.pat sub pat) start_expr end_expr Asttypes.Upto
(sub.expr sub body_expr)
+ | Pexp_regexp {pattern; flags} ->
+ (* %re is only a frozen PPX wire encoding, not source syntax. *)
+ extension ~loc ~attrs
+ ( Location.mkloc "re" loc,
+ Pt.PStr
+ [
+ Ast_helper0.Str.eval ~loc
+ (Ast_helper0.Exp.constant ~loc
+ (Pt.Pconst_string ("/" ^ pattern ^ "/" ^ flags, Some "js")));
+ ] )
| Pexp_template {source_segments; values} ->
let segments =
List.map
diff --git a/compiler/ml/ast_payload.ml b/compiler/ml/ast_payload.ml
index 05a536ddb0..76f9dda8f9 100644
--- a/compiler/ml/ast_payload.ml
+++ b/compiler/ml/ast_payload.ml
@@ -176,6 +176,31 @@ let constructor_tag_of_payload payload =
| Some (Lident "undefined") -> Some Pct_undefined
| Some _ | None -> None)))))
+let validate_raw_source ~(kind : Js_raw_info.raw_kind) ?is_function ~loc ~offset
+ str =
+ Bs_flow_ast_utils.check_flow_errors ~loc ~offset
+ (match kind with
+ | Raw_re | Raw_exp ->
+ let ((_loc, expression) as program), errors =
+ let open Parser_flow in
+ let env = Parser_env.init_env None str in
+ do_parse env Parse.expression false
+ in
+ (if kind = Raw_re then
+ match expression with
+ | RegExpLiteral _ -> ()
+ | _ ->
+ Location.raise_errorf ~loc
+ "Syntax error: a valid JS regex literal expected");
+ (match is_function with
+ | Some is_function -> (
+ match Classify_function.classify_exp program with
+ | Js_function {arity; _} -> is_function := Some arity
+ | _ -> ())
+ | None -> ());
+ errors
+ | Raw_program -> snd (Parser_flow.parse_program false None str))
+
let raw_as_string_exp_exn ~(kind : Js_raw_info.raw_kind) ?is_function (x : t) :
Parsetree.expression option =
let string_expression =
@@ -214,28 +239,7 @@ let raw_as_string_exp_exn ~(kind : Js_raw_info.raw_kind) ?is_function (x : t) :
in
match string_expression with
| Some (str, offset, ({pexp_loc = loc} as expression)) ->
- Bs_flow_ast_utils.check_flow_errors ~loc ~offset
- (match kind with
- | Raw_re | Raw_exp ->
- let ((_loc, expression) as program), errors =
- let open Parser_flow in
- let env = Parser_env.init_env None str in
- do_parse env Parse.expression false
- in
- (if kind = Raw_re then
- match expression with
- | RegExpLiteral _ -> ()
- | _ ->
- Location.raise_errorf ~loc
- "Syntax error: a valid JS regex literal expected");
- (match is_function with
- | Some is_function -> (
- match Classify_function.classify_exp program with
- | Js_function {arity; _} -> is_function := Some arity
- | _ -> ())
- | None -> ());
- errors
- | Raw_program -> snd (Parser_flow.parse_program false None str));
+ validate_raw_source ~kind ?is_function ~loc ~offset str;
Some {expression with pexp_desc = Pexp_constant (Pconst_raw_source str)}
| None -> None
diff --git a/compiler/ml/ast_payload.mli b/compiler/ml/ast_payload.mli
index 40a740ddcf..de44dcd354 100644
--- a/compiler/ml/ast_payload.mli
+++ b/compiler/ml/ast_payload.mli
@@ -53,6 +53,17 @@ val is_single_int : t -> int option
val constructor_tag_of_payload : t -> Parsetree.constructor_tag option
(** The literal denoted by a valid variant-constructor [@as] payload. *)
+val validate_raw_source :
+ kind:Js_raw_info.raw_kind ->
+ ?is_function:int option ref ->
+ loc:Location.t ->
+ offset:int ->
+ string ->
+ unit
+
+(** Validate JavaScript source using the same rules as raw extensions.
+ [offset] accounts for delimiters preceding the source in its location. *)
+
val raw_as_string_exp_exn :
kind:Js_raw_info.raw_kind ->
?is_function:int option ref ->
diff --git a/compiler/ml/depend.ml b/compiler/ml/depend.ml
index 80fc2dc8cb..28181b57e8 100644
--- a/compiler/ml/depend.ml
+++ b/compiler/ml/depend.ml
@@ -225,6 +225,7 @@ let rec add_expr bv exp =
| Pexp_apply {funct = e; args = el} ->
add_expr bv e;
List.iter (fun (_, e) -> add_expr bv e) el
+ | Pexp_regexp _ -> ()
| Pexp_template {values} -> List.iter (add_expr bv) values
| Pexp_tagged_template {tag; values} ->
add_expr bv tag;
diff --git a/compiler/ml/parsetree.ml b/compiler/ml/parsetree.ml
index 120e518a2c..e736c90b76 100644
--- a/compiler/ml/parsetree.ml
+++ b/compiler/ml/parsetree.ml
@@ -54,8 +54,8 @@ type constant =
otherwise the frontend rejects it. The string is JavaScript source, not a
decoded ReScript string value. *)
| Pconst_raw_source of string
- (* JavaScript source carried by a compiler extension such as [raw], [ffi], or
- [re]. For example, [%raw("x + 1")] stores ["x + 1"]. The extension
+ (* JavaScript source carried by a compiler extension such as [raw] or [ffi].
+ For example, [%raw("x + 1")] stores ["x + 1"]. The extension
interprets the string as JavaScript source rather than as a ReScript
runtime string value. *)
| Pconst_float of string * char option
@@ -389,6 +389,9 @@ and expression_desc =
(* for pattern of array_expr do body_expr *)
| Pexp_for_await_of of pattern * expression * expression
(* for await pattern of iterable_expr do body_expr *)
+ | Pexp_regexp of {pattern: string; flags: string}
+ (* Literal source without delimiters; escapes and flag order are preserved.
+ Validation happens during frontend lowering, not AST construction. *)
| Pexp_template of {source_segments: string loc list; values: expression list}
(* An ordinary backquoted expression. [source_segments] contains the validated
text between and around the interpolations, including escape spelling;
diff --git a/compiler/ml/pprintast.ml b/compiler/ml/pprintast.ml
index 201d62ffee..12a8cafaf5 100644
--- a/compiler/ml/pprintast.ml
+++ b/compiler/ml/pprintast.ml
@@ -781,6 +781,7 @@ and expression ctxt f x =
pp f "@[<2>`%s@;%a@]" l (simple_expr ctxt) payload
| Pexp_extension e -> extension ctxt f e
| Pexp_await e -> pp f "@[await@ %a@]" (simple_expr ctxt) e
+ | Pexp_regexp {pattern; flags} -> pp f "/%s/%s" pattern flags
| Pexp_template {source_segments; values} ->
let rec parts f (source_segments, values) =
match (source_segments, values) with
diff --git a/compiler/ml/printast.ml b/compiler/ml/printast.ml
index 7b16b1592d..bee2402693 100644
--- a/compiler/ml/printast.ml
+++ b/compiler/ml/printast.ml
@@ -382,6 +382,8 @@ and expression i ppf x =
| Pexp_extension (s, arg) ->
line i ppf "Pexp_extension \"%s\"\n" s.txt;
payload i ppf arg
+ | Pexp_regexp {pattern; flags} ->
+ line i ppf "Pexp_regexp %S %S\n" pattern flags
| Pexp_template {source_segments; values} ->
line i ppf "Pexp_template\n";
List.iter
diff --git a/compiler/ml/typecore.ml b/compiler/ml/typecore.ml
index de614b6ee8..0d6ca8b2a7 100644
--- a/compiler/ml/typecore.ml
+++ b/compiler/ml/typecore.ml
@@ -174,6 +174,7 @@ let iter_expression f e =
| Pexp_apply {funct = e; args = lel} ->
expr e;
List.iter (fun (_, e) -> expr e) lel
+ | Pexp_regexp _ -> ()
| Pexp_template {values} -> List.iter expr values
| Pexp_tagged_template {tag; values} ->
expr tag;
@@ -2568,6 +2569,7 @@ and type_expect_ ?deprecated_context ~context ?(recarg = Rejected) env sexp
| Pexp_fun {newtypes = []; params; body = sfun_body; async} ->
type_function ~async loc sexp.pexp_attributes env ty_expected params
sfun_body
+ | Pexp_regexp _ -> assert false (* Lowered by the built-in frontend mapper. *)
| Pexp_template {source_segments; values} ->
begin_def ();
let segments : Asttypes.template_segment list =
diff --git a/compiler/syntax/src/res_ast_debugger.ml b/compiler/syntax/src/res_ast_debugger.ml
index c2b3f4c986..7f191c78d4 100644
--- a/compiler/syntax/src/res_ast_debugger.ml
+++ b/compiler/syntax/src/res_ast_debugger.ml
@@ -763,6 +763,8 @@ module Sexp_ast = struct
]
| Pexp_extension ext ->
Sexp.list [Sexp.atom "Pexp_extension"; extension ext]
+ | Pexp_regexp {pattern; flags} ->
+ Sexp.list [Sexp.atom "Pexp_regexp"; string pattern; string flags]
| Pexp_template {source_segments; values} ->
Sexp.list
[
diff --git a/compiler/syntax/src/res_comments_table.ml b/compiler/syntax/src/res_comments_table.ml
index f9181092f6..c60c3a7602 100644
--- a/compiler/syntax/src/res_comments_table.ml
+++ b/compiler/syntax/src/res_comments_table.ml
@@ -993,7 +993,7 @@ and walk_expression expr t comments =
in
match expr.Parsetree.pexp_desc with
| _ when comments = [] -> ()
- | Pexp_constant _ ->
+ | Pexp_regexp _ | Pexp_constant _ ->
let leading, trailing = partition_leading_trailing comments expr.pexp_loc in
attach t.leading expr.pexp_loc leading;
attach t.trailing expr.pexp_loc trailing
diff --git a/compiler/syntax/src/res_core.ml b/compiler/syntax/src/res_core.ml
index cf1ee2593b..c2cb040e72 100644
--- a/compiler/syntax/src/res_core.ml
+++ b/compiler/syntax/src/res_core.ml
@@ -2075,15 +2075,7 @@ and parse_constrained_expr_region p =
and parse_regex ~start_pos p pattern flags =
Parser.next p;
let loc = mk_loc start_pos p.prev_end_pos in
- let payload =
- Parsetree.PStr
- [
- Ast_helper.Str.eval ~loc
- (Ast_helper.Exp.constant ~loc
- (Pconst_raw_source ("/" ^ pattern ^ "/" ^ flags)));
- ]
- in
- Ast_helper.Exp.extension (Location.mkloc "re" loc, payload)
+ Ast_helper.Exp.regexp ~loc pattern flags
(* Atomic expressions represent unambiguous expressions.
* This means that regardless of the context, these expressions
@@ -2161,14 +2153,12 @@ and parse_atomic_expr p =
Parser.next_regex_token p;
match p.token with
| Regex (pattern, flags) -> parse_regex ~start_pos p pattern flags
- | _ -> Ast_helper.Exp.extension (Location.mknoloc "re", Parsetree.PStr [])
- )
+ | _ -> assert false (* next_regex_token always returns Regex. *))
| ForwardslashDot -> (
Parser.next_regex_token p;
match p.token with
| Regex (pattern, flags) -> parse_regex ~start_pos p ("." ^ pattern) flags
- | _ -> Ast_helper.Exp.extension (Location.mknoloc "re", Parsetree.PStr [])
- )
+ | _ -> assert false (* next_regex_token always returns Regex. *))
| token -> (
let err_pos = p.prev_end_pos in
Parser.err ~start_pos:err_pos p
@@ -2805,15 +2795,28 @@ and parse_jsx_name p : Parsetree.jsx_tag_name Location.loc =
and parse_jsx_opening_or_self_closing_element (* start of the opening < *)
~start_pos p : Parsetree.expression =
let name = parse_jsx_name p in
- let jsx_props = parse_jsx_props p in
+ let jsx_props, recovery = parse_jsx_props p in
+ let element, closed = parse_jsx_element_rest ~start_pos name jsx_props p in
+ match (closed, recovery) with
+ | false, Some (checkpoint, props_rev) ->
+ (* A closing parent tag may have supplied an apparent regexp delimiter.
+ Retry only this element's latest ambiguous boundary, as a self-close. *)
+ Parser.restore p checkpoint;
+ Parser.eat_breadcrumb p;
+ Parser.err p (Diagnostics.message "Expected a JSX prop value");
+ fst (parse_jsx_element_rest ~start_pos name (List.rev props_rev) p)
+ | _ -> element
+
+and parse_jsx_element_rest ~start_pos name jsx_props p =
match p.Parser.token with
| Forwardslash ->
(* *)
Parser.next p;
let jsx_end_pos = p.end_pos in
+ let closed = p.token = GreaterThan in
Parser.expect GreaterThan p;
let loc = mk_loc start_pos jsx_end_pos in
- Ast_helper.Exp.jsx_unary_element ~loc name jsx_props
+ (Ast_helper.Exp.jsx_unary_element ~loc name jsx_props, closed)
| GreaterThan -> (
(* bar *)
let opening_tag_end = p.Parser.start_pos in
@@ -2846,6 +2849,9 @@ and parse_jsx_opening_or_self_closing_element (* start of the opening < *)
= Ast_helper.Jsx.longident_of_jsx_tag_name name.txt ->
let end_tag_name = closing_name in
let closing_tag_end = p.start_pos in
+ let closed =
+ Option.is_some closing_tag_start && p.token = GreaterThan
+ in
Parser.expect GreaterThan p;
let loc = mk_loc start_pos p.prev_end_pos in
let closing_tag =
@@ -2857,8 +2863,9 @@ and parse_jsx_opening_or_self_closing_element (* start of the opening < *)
jsx_closing_container_tag_end = closing_tag_end;
})
in
- Ast_helper.Exp.jsx_container_element ~loc name jsx_props opening_tag_end
- children closing_tag
+ ( Ast_helper.Exp.jsx_container_element ~loc name jsx_props
+ opening_tag_end children closing_tag,
+ closed )
| _ ->
let () =
if Grammar.is_structure_item_start token0 then (
@@ -2898,9 +2905,10 @@ and parse_jsx_opening_or_self_closing_element (* start of the opening < *)
jsx_closing_container_tag_end = closing_tag_end;
})
in
- Ast_helper.Exp.jsx_container_element
- ~loc:(mk_loc start_pos p.prev_end_pos)
- name jsx_props opening_tag_end children closing_tag)
+ ( Ast_helper.Exp.jsx_container_element
+ ~loc:(mk_loc start_pos p.prev_end_pos)
+ name jsx_props opening_tag_end children closing_tag,
+ false ))
| token ->
let () =
if Grammar.is_structure_item_start token then
@@ -2920,14 +2928,16 @@ and parse_jsx_opening_or_self_closing_element (* start of the opening < *)
Parser.err ~start_pos ~end_pos:p.prev_end_pos p
(Diagnostics.message msg)
in
- Ast_helper.Exp.jsx_container_element
- ~loc:(mk_loc start_pos p.prev_end_pos)
- name jsx_props opening_tag_end children None)
+ ( Ast_helper.Exp.jsx_container_element
+ ~loc:(mk_loc start_pos p.prev_end_pos)
+ name jsx_props opening_tag_end children None,
+ false ))
| token ->
Parser.err p (Diagnostics.unexpected token p.breadcrumbs);
- Ast_helper.Exp.jsx_unary_element
- ~loc:(mk_loc start_pos p.prev_end_pos)
- name jsx_props
+ ( Ast_helper.Exp.jsx_unary_element
+ ~loc:(mk_loc start_pos p.prev_end_pos)
+ name jsx_props,
+ false )
(*
* jsx ::=
@@ -2984,7 +2994,7 @@ and parse_jsx_fragment start_pos p =
* | lident = ?jsx_expr
* | {...jsx_expr}
*)
-and parse_jsx_prop p : Parsetree.jsx_prop option =
+and parse_jsx_prop ~on_ambiguous p : Parsetree.jsx_prop option =
match p.Parser.token with
| Question | Lident _ -> (
let optional = Parser.optional p Question in
@@ -2997,7 +3007,38 @@ and parse_jsx_prop p : Parsetree.jsx_prop option =
| Equal ->
Parser.next p;
let optional = Parser.optional p Question in
- let attr_expr = parse_primary_expr ~operand:(parse_atomic_expr p) p in
+ let missing () =
+ Parser.err p (Diagnostics.message "Expected a JSX prop value");
+ Recover.default_expr ()
+ in
+ let attr_expr =
+ match p.token with
+ | GreaterThan -> missing ()
+ | Forwardslash
+ when Parser.lookahead p (fun p ->
+ Parser.next p;
+ p.token = GreaterThan) ->
+ let checkpoint = Parser.checkpoint p in
+ let diagnostics = p.diagnostics in
+ let start_pos = p.start_pos in
+ Parser.next_regex_token p;
+ if p.diagnostics != diagnostics then (
+ Parser.restore p checkpoint;
+ missing ())
+ else (
+ on_ambiguous checkpoint
+ (Parsetree.JSXPropValue
+ ({txt = name; loc}, optional, Recover.default_expr ()));
+ let operand =
+ match p.token with
+ | Regex (pattern, flags) ->
+ parse_regex ~start_pos p pattern flags
+ | _ ->
+ assert false (* next_regex_token always returns Regex. *)
+ in
+ parse_primary_expr ~operand p)
+ | _ -> parse_primary_expr ~operand:(parse_atomic_expr p) p
+ in
Some (Parsetree.JSXPropValue ({txt = name; loc}, optional, attr_expr))
| _ -> Some (Parsetree.JSXPropPunning (false, {txt = name; loc})))
| Some (_name, _loc, `Upper) ->
@@ -3023,8 +3064,19 @@ and parse_jsx_prop p : Parsetree.jsx_prop option =
| _ -> None)
| _ -> None
-and parse_jsx_props p : Parsetree.jsx_prop list =
- parse_region ~grammar:Grammar.JsxAttribute ~f:parse_jsx_prop p
+and parse_jsx_props p =
+ let prefix_rev = ref [] in
+ let recovery = ref None in
+ let on_ambiguous checkpoint missing =
+ recovery := Some (checkpoint, missing :: !prefix_rev)
+ in
+ let f p =
+ let prop = parse_jsx_prop ~on_ambiguous p in
+ Option.iter (fun prop -> prefix_rev := prop :: !prefix_rev) prop;
+ prop
+ in
+ let props = parse_region ~grammar:Grammar.JsxAttribute ~f p in
+ (props, !recovery)
and parse_jsx_children p : Parsetree.jsx_children =
let rec loop p children =
@@ -7618,9 +7670,14 @@ and parse_extension ?(module_language = false) p =
else Parser.expect Percent p;
let attr_id = parse_attribute_id ~start_pos p in
let payload = parse_payload p in
+ if attr_id.txt = "re" then
+ Parser.err ~start_pos:attr_id.loc.loc_start ~end_pos:attr_id.loc.loc_end p
+ (Diagnostics.message
+ "The %re extension has been removed. Use a regexp literal such as \
+ /abc/i.");
let payload =
match (attr_id.txt, payload) with
- | ( ("raw" | "ffi" | "re"),
+ | ( ("raw" | "ffi"),
Parsetree.PStr
[
({
diff --git a/compiler/syntax/src/res_parens.ml b/compiler/syntax/src/res_parens.ml
index 0c25e46853..4c54d3d397 100644
--- a/compiler/syntax/src/res_parens.ml
+++ b/compiler/syntax/src/res_parens.ml
@@ -322,11 +322,11 @@ let jsx_prop_expr expr =
| _ when Parsetree_viewer.expr_is_await expr -> Parenthesized
| {
Parsetree.pexp_desc =
- ( Pexp_ident _ | Pexp_constant _ | Pexp_field _ | Pexp_construct _
- | Pexp_variant _ | Pexp_array _ | Pexp_pack _ | Pexp_record _
- | Pexp_object_literal _ | Pexp_extension _ | Pexp_letmodule _
- | Pexp_letexception _ | Pexp_open _ | Pexp_sequence _ | Pexp_let _
- | Pexp_tuple _ );
+ ( Pexp_ident _ | Pexp_constant _ | Pexp_regexp _ | Pexp_field _
+ | Pexp_construct _ | Pexp_variant _ | Pexp_array _ | Pexp_pack _
+ | Pexp_record _ | Pexp_object_literal _ | Pexp_extension _
+ | Pexp_letmodule _ | Pexp_letexception _ | Pexp_open _
+ | Pexp_sequence _ | Pexp_let _ | Pexp_tuple _ );
pexp_attributes = [];
} ->
Nothing
@@ -360,11 +360,11 @@ let jsx_child_expr expr =
| _ when Parsetree_viewer.expr_is_await expr -> Parenthesized
| {
Parsetree.pexp_desc =
- ( Pexp_ident _ | Pexp_constant _ | Pexp_field _ | Pexp_construct _
- | Pexp_variant _ | Pexp_array _ | Pexp_pack _ | Pexp_record _
- | Pexp_object_literal _ | Pexp_extension _ | Pexp_letmodule _
- | Pexp_letexception _ | Pexp_open _ | Pexp_sequence _ | Pexp_let _
- | Pexp_jsx_element _ );
+ ( Pexp_ident _ | Pexp_constant _ | Pexp_regexp _ | Pexp_field _
+ | Pexp_construct _ | Pexp_variant _ | Pexp_array _ | Pexp_pack _
+ | Pexp_record _ | Pexp_object_literal _ | Pexp_extension _
+ | Pexp_letmodule _ | Pexp_letexception _ | Pexp_open _
+ | Pexp_sequence _ | Pexp_let _ | Pexp_jsx_element _ );
pexp_attributes = [];
} ->
Nothing
diff --git a/compiler/syntax/src/res_parser.ml b/compiler/syntax/src/res_parser.ml
index 6dc53174e6..1e89f95436 100644
--- a/compiler/syntax/src/res_parser.ml
+++ b/compiler/syntax/src/res_parser.ml
@@ -21,6 +21,35 @@ type t = {
mutable regions: region_status ref list;
}
+(* Recovery checkpoints are only allocated at ambiguous JSX boundaries.
+ Copy mutable scanner and diagnostic-region state as well as parser fields. *)
+type checkpoint = t
+
+let checkpoint p =
+ {
+ p with
+ scanner = {p.scanner with mode = p.scanner.mode};
+ regions = List.map (fun region -> ref !region) p.regions;
+ }
+
+let restore p saved =
+ p.scanner.err <- saved.scanner.err;
+ p.scanner.ch <- saved.scanner.ch;
+ p.scanner.offset <- saved.scanner.offset;
+ p.scanner.offset16 <- saved.scanner.offset16;
+ p.scanner.line_offset <- saved.scanner.line_offset;
+ p.scanner.lnum <- saved.scanner.lnum;
+ p.scanner.mode <- saved.scanner.mode;
+ p.token <- saved.token;
+ p.start_pos <- saved.start_pos;
+ p.end_pos <- saved.end_pos;
+ p.prev_end_pos <- saved.prev_end_pos;
+ p.breadcrumbs <- saved.breadcrumbs;
+ p.errors <- saved.errors;
+ p.diagnostics <- saved.diagnostics;
+ p.comments <- saved.comments;
+ p.regions <- List.map (fun region -> ref !region) saved.regions
+
let err ?start_pos ?end_pos p error =
match p.regions with
| ({contents = Report} as region) :: _ ->
diff --git a/compiler/syntax/src/res_parser.mli b/compiler/syntax/src/res_parser.mli
index c55a0e3ec7..f280387de9 100644
--- a/compiler/syntax/src/res_parser.mli
+++ b/compiler/syntax/src/res_parser.mli
@@ -20,6 +20,10 @@ type t = {
mutable regions: region_status ref list;
}
+type checkpoint
+val checkpoint : t -> checkpoint
+val restore : t -> checkpoint -> unit
+
val make : string -> string -> t
val expect : ?grammar:Grammar.t -> Token.t -> t -> unit
diff --git a/compiler/syntax/src/res_printer.ml b/compiler/syntax/src/res_printer.ml
index 892e22b574..d47086938a 100644
--- a/compiler/syntax/src/res_printer.ml
+++ b/compiler/syntax/src/res_printer.ml
@@ -3462,20 +3462,9 @@ and print_expression ~state (e : Parsetree.expression) cmt_tbl =
Doc.soft_line;
Doc.rbrace;
])
- | Pexp_extension extension -> (
- match extension with
- | ( {txt = "re"},
- PStr
- [
- {
- pstr_desc =
- Pstr_eval
- ({pexp_desc = Pexp_constant (Pconst_raw_source expr)}, []);
- };
- ] ) ->
- Doc.text expr
- | extension ->
- print_extension ~state ~at_module_lvl:false extension cmt_tbl)
+ | Pexp_extension extension ->
+ print_extension ~state ~at_module_lvl:false extension cmt_tbl
+ | Pexp_regexp {pattern; flags} -> Doc.text ("/" ^ pattern ^ "/" ^ flags)
| Pexp_template {source_segments; values} ->
print_template_literal ~state ~source_segments ~values cmt_tbl
| Pexp_tagged_template {tag; raw_sources; values} ->
diff --git a/tests/analysis_tests/tests/src/CompletionJsxProps.res b/tests/analysis_tests/tests/src/CompletionJsxProps.res
index e9fe8dafe6..70dd2be3b4 100644
--- a/tests/analysis_tests/tests/src/CompletionJsxProps.res
+++ b/tests/analysis_tests/tests/src/CompletionJsxProps.res
@@ -44,3 +44,15 @@ module CompletableComponentLazy = {
// let _ = {React.null}
+// ^com
+
+// let _ =
{React.null}
+// ^com
+
+// let _ =
+// ^com
+
+// let _ =
/ disabled= />
+// ^com
diff --git a/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt b/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
index 6cc8610bb3..ea45d4eb44 100644
--- a/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
+++ b/tests/analysis_tests/tests/src/expected/CompletionJsxProps.res.txt
@@ -474,3 +474,70 @@ Path CompletableComponentLazy.make
}
]
+Complete src/CompletionJsxProps.res 47:22
+posCursor:[47:22] posNoWhite:[47:21] Found expr:[47:11->47:42]
+JSX 47:15] muted[47:16->47:21]=...__ghost__[0:-1->0:-1]> _children:47:25
+Completable: Cexpression CJsxPropValue [div] muted
+Package opens Stdlib.place holder Pervasives.JsxModules.place holder
+Resolved opens 1 Stdlib
+ContextPath CJsxPropValue [div] muted
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[
+ { "detail": "bool", "kind": 4, "label": "true", "tags": [] },
+ { "detail": "bool", "kind": 4, "label": "false", "tags": [] }
+]
+
+Complete src/CompletionJsxProps.res 50:29
+posCursor:[50:29] posNoWhite:[50:28] Found expr:[50:11->50:49]
+JSX 50:15] onMouseEnter[50:16->50:28]=...__ghost__[0:-1->0:-1]> _children:50:32
+Completable: Cexpression CJsxPropValue [div] onMouseEnter
+Package opens Stdlib.place holder Pervasives.JsxModules.place holder
+Resolved opens 1 Stdlib
+ContextPath CJsxPropValue [div] onMouseEnter
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[
+ {
+ "detail": "JsxEvent.Mouse.t => unit",
+ "insertText": "{${1:event} => ${0:event}}",
+ "insertTextFormat": 2,
+ "kind": 12,
+ "label": "event => event",
+ "sortText": "A",
+ "tags": []
+ }
+]
+
+Complete src/CompletionJsxProps.res 53:33
+posCursor:[53:33] posNoWhite:[53:31] Found expr:[53:11->53:41]
+JSX 53:15] > _children:53:16
+posCursor:[53:33] posNoWhite:[53:31] Found expr:[53:16->53:35]
+JSX 53:22] disabled[53:23->53:31]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [input] disabled
+Package opens Stdlib.place holder Pervasives.JsxModules.place holder
+Resolved opens 1 Stdlib
+ContextPath CJsxPropValue [input] disabled
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[
+ { "detail": "bool", "kind": 4, "label": "true", "tags": [] },
+ { "detail": "bool", "kind": 4, "label": "false", "tags": [] }
+]
+
+Complete src/CompletionJsxProps.res 56:45
+posCursor:[56:45] posNoWhite:[56:43] Found expr:[56:11->56:53]
+JSX 56:15] > _children:56:16
+posCursor:[56:45] posNoWhite:[56:43] Found expr:[56:16->56:47]
+JSX 56:22] pattern[56:23->56:30]=...[56:31->56:34] disabled[56:35->56:43]=...__ghost__[0:-1->0:-1]> _children:None
+Completable: Cexpression CJsxPropValue [input] disabled
+Package opens Stdlib.place holder Pervasives.JsxModules.place holder
+Resolved opens 1 Stdlib
+ContextPath CJsxPropValue [input] disabled
+Path ReactDOM.domProps
+Path JsxDOM.domProps
+[
+ { "detail": "bool", "kind": 4, "label": "true", "tags": [] },
+ { "detail": "bool", "kind": 4, "label": "false", "tags": [] }
+]
+
diff --git a/tests/build_tests/super_errors/expected/syntaxErrorsRemovedReExtension.res.expected b/tests/build_tests/super_errors/expected/syntaxErrorsRemovedReExtension.res.expected
new file mode 100644
index 0000000000..5da2a14b57
--- /dev/null
+++ b/tests/build_tests/super_errors/expected/syntaxErrorsRemovedReExtension.res.expected
@@ -0,0 +1,9 @@
+
+ [1;31mSyntax error![0m
+ [36m/.../fixtures/syntaxErrorsRemovedReExtension.res[0m:[2m1:10-12[0m
+
+ [1;31m1[0m [2mβ[0m let re = [1;31m%re[0m("/abc/i")
+ 2 [2mβ[0m // Use the literal syntax instead.
+ 3 [2mβ[0m let literal = /abc/i
+
+ The %re extension has been removed. Use a regexp literal such as /abc/i.
\ No newline at end of file
diff --git a/tests/build_tests/super_errors/fixtures/syntaxErrorsRemovedReExtension.res b/tests/build_tests/super_errors/fixtures/syntaxErrorsRemovedReExtension.res
new file mode 100644
index 0000000000..6b21713fab
--- /dev/null
+++ b/tests/build_tests/super_errors/fixtures/syntaxErrorsRemovedReExtension.res
@@ -0,0 +1,3 @@
+let re = %re("/abc/i")
+// Use the literal syntax instead.
+let literal = /abc/i
diff --git a/tests/ounit_tests/ounit_ast_mapper0_tests.ml b/tests/ounit_tests/ounit_ast_mapper0_tests.ml
index 79b7a6bf3d..5bc5a592b4 100644
--- a/tests/ounit_tests/ounit_ast_mapper0_tests.ml
+++ b/tests/ounit_tests/ounit_ast_mapper0_tests.ml
@@ -1055,7 +1055,157 @@ let test_raw_extension_payloads_roundtrip_through_ast0 _ =
assert_raw_extension_payload ~name ~expected:encoded expression;
assert_raw_extension_payload ~name ~expected:encoded
(map_expr0 (map_expr_to0 expression)))
- ["raw"; "ffi"; "re"]
+ ["raw"; "ffi"]
+
+let test_removed_regexp_extension _ =
+ List.iter
+ (fun source ->
+ let result =
+ Res_driver.parse_implementation_from_source
+ ~display_filename:"Regexp.res" ~source
+ in
+ OUnit.assert_bool "legacy regexp syntax is rejected" result.invalid;
+ OUnit.assert_equal
+ [
+ "The %re extension has been removed. Use a regexp literal such as \
+ /abc/i.";
+ ]
+ (List.map Res_diagnostics.explain result.diagnostics))
+ ["let re = %re(\"/abc/i\")"; "let re = %re(`/abc/i`)"; "%%re(\"/abc/i\")"]
+
+let test_regexp_parser_locations _ =
+ let source = "let re = /a/g" in
+ let result =
+ Res_driver.parse_implementation_from_source ~display_filename:"Regexp.res"
+ ~source
+ in
+ OUnit.assert_bool "valid regexp" (not result.invalid);
+ match result.parsetree with
+ | [
+ {
+ Parsetree.pstr_desc =
+ Pstr_value
+ ( _,
+ [
+ {
+ pvb_expr =
+ {
+ pexp_desc = Pexp_regexp {pattern = "a"; flags = "g"};
+ pexp_loc;
+ };
+ };
+ ] );
+ };
+ ] ->
+ OUnit.assert_equal 9 pexp_loc.loc_start.pos_cnum;
+ OUnit.assert_equal 13 pexp_loc.loc_end.pos_cnum
+ | _ -> assert_failure "Expected a located regexp expression"
+
+let test_regexp_roundtrip_through_ast0 _ =
+ let loc = source_loc 10 31 in
+ let attrs = [attr "test.regexp" (Parsetree.PStr [])] in
+ let expression = Ast_helper.Exp.regexp ~loc ~attrs {|a\/[/]\d|} "ig" in
+ let expression0 = map_expr_to0 expression in
+ (match expression0.pexp_desc with
+ | Pexp_extension
+ ( {txt = "re"; loc = name_loc},
+ PStr
+ [
+ {
+ pstr_desc =
+ Pstr_eval
+ ( {
+ pexp_desc =
+ Pexp_constant (Pconst_string (source, Some "js"));
+ pexp_loc;
+ pexp_attributes = [];
+ },
+ [] );
+ pstr_loc;
+ };
+ ] ) ->
+ OUnit.assert_equal {|/a\/[/]\d/ig|} source;
+ OUnit.assert_equal loc name_loc;
+ OUnit.assert_equal loc pexp_loc;
+ OUnit.assert_equal loc pstr_loc
+ | _ -> assert_failure "Expected the legacy regexp extension wire shape");
+ OUnit.assert_equal expression (map_expr0 expression0)
+
+let regexp_payload0 ?(delimiter = Some "js") source =
+ let outer_loc = source_loc 10 50 in
+ Ast_helper0.Exp.extension ~loc:outer_loc
+ ~attrs:[attr "test.outer" (Parsetree0.PStr [])]
+ ( Location.mkloc "re" (source_loc 11 13),
+ Parsetree0.PStr
+ [
+ Ast_helper0.Str.eval ~loc:(source_loc 14 49)
+ ~attrs:[attr "test.eval" (Parsetree0.PStr [])]
+ (Ast_helper0.Exp.constant ~loc:(source_loc 15 48)
+ ~attrs:[attr "test.payload" (Parsetree0.PStr [])]
+ (Parsetree0.Pconst_string (source, delimiter)));
+ ] )
+
+let test_ppx_regexp_payloads _ =
+ List.iter
+ (fun delimiter ->
+ let expression =
+ map_expr0 (regexp_payload0 ~delimiter {| /a\/[/]\d/ig |})
+ in
+ let expected =
+ Ast_helper.Exp.regexp ~loc:(source_loc 10 50)
+ ~attrs:
+ (List.map
+ (fun name -> attr name (Parsetree.PStr []))
+ ["test.outer"; "test.eval"; "test.payload"])
+ {|a\/[/]\d|} "ig"
+ in
+ OUnit.assert_equal expected expression;
+ OUnit.assert_equal expected (map_expr0 (map_expr_to0 expression)))
+ [None; Some "js"; Some "*j"; Some "quoted"];
+ (* The JS parser's filtered flags must not silently change PPX output. *)
+ match (map_expr0 (regexp_payload0 "/a/zig")).pexp_desc with
+ | Pexp_regexp {pattern = "a"; flags = "zig"} -> ()
+ | _ -> assert_failure "Expected the original regexp flags"
+
+let test_malformed_ppx_regexp_payloads _ =
+ let reject expression0 expected_loc =
+ match map_expr0 expression0 with
+ | _ ->
+ assert_failure
+ "Expected malformed PPX regexp to be rejected at the bridge"
+ | exception Location.Error error ->
+ OUnit.assert_equal expected_loc error.loc;
+ OUnit.assert_equal
+ "A PPX returned a malformed regexp payload. Expected a string \
+ containing one regexp literal."
+ error.msg
+ in
+ List.iter
+ (fun source -> reject (regexp_payload0 source) (source_loc 15 48))
+ ["not a literal"; ""; "/a"; "/[/"; "/a/; other()"; "/a/ + /b/"];
+ reject
+ (Ast_helper0.Exp.extension ~loc (Location.mknoloc "re", Parsetree0.PStr []))
+ loc;
+ reject
+ (Ast_helper0.Exp.extension ~loc
+ ( Location.mknoloc "re",
+ Parsetree0.PStr
+ [
+ Ast_helper0.Str.eval
+ (Ast_helper0.Exp.constant
+ (Parsetree0.Pconst_integer ("1", None)));
+ ] ))
+ loc;
+ match
+ Ast_mapper_from0.default_mapper.extension Ast_mapper_from0.default_mapper
+ (Location.mkloc "re" (source_loc 1 3), Parsetree0.PStr [])
+ with
+ | _ ->
+ assert_failure "Expected non-expression regexp extension to be rejected"
+ | exception Location.Error error ->
+ OUnit.assert_equal (source_loc 1 3) error.loc;
+ OUnit.assert_equal
+ "A PPX returned a regexp extension outside an expression." error.msg
let test_tagged_templates_roundtrip_through_ast0 _ =
let head_loc = source_loc 4 16 in
@@ -1353,6 +1503,11 @@ let suites =
>:: test_ppx_byte_strings_convert_to_valid_utf8;
"string_literals_roundtrip_through_ast0"
>:: test_string_literals_roundtrip_through_ast0;
+ "removed_regexp_extension" >:: test_removed_regexp_extension;
+ "regexp_parser_locations" >:: test_regexp_parser_locations;
+ "regexp_roundtrip_through_ast0" >:: test_regexp_roundtrip_through_ast0;
+ "ppx_regexp_payloads" >:: test_ppx_regexp_payloads;
+ "malformed_ppx_regexp_payloads" >:: test_malformed_ppx_regexp_payloads;
"raw_extension_payloads_roundtrip_through_ast0"
>:: test_raw_extension_payloads_roundtrip_through_ast0;
"tagged_templates_roundtrip_through_ast0"
diff --git a/tests/ounit_tests/ounit_jsx_loc_tests.ml b/tests/ounit_tests/ounit_jsx_loc_tests.ml
index f05165d044..13c21966a0 100644
--- a/tests/ounit_tests/ounit_jsx_loc_tests.ml
+++ b/tests/ounit_tests/ounit_jsx_loc_tests.ml
@@ -85,9 +85,265 @@ let _ =
assert_same_loc original_loc loc
| _ -> assert_failure "Expected a leading JSX spread prop"
+let test_missing_jsx_prop_values _ =
+ List.iter
+ (fun jsx ->
+ let result =
+ Res_driver.parse_implementation_from_source
+ ~display_filename:"JsxLocTest.res"
+ ~source:("let element = " ^ jsx ^ "\nlet after = 42")
+ in
+ OUnit.assert_bool "missing value is a syntax error" result.invalid;
+ assert_equal
+ ["Expected a JSX prop value"]
+ (List.map Res_diagnostics.explain result.diagnostics);
+ (match get_jsx_props result.parsetree with
+ | [
+ Parsetree.JSXPropValue
+ ( _,
+ _,
+ {pexp_desc = Pexp_extension ({txt = "rescript.exprhole"}, PStr [])}
+ );
+ ] ->
+ ()
+ | _ -> assert_failure "Expected an expression hole, not a regexp");
+ match result.parsetree with
+ | [
+ {
+ Parsetree.pstr_desc =
+ Pstr_value (_, [{pvb_expr = {pexp_desc = Pexp_jsx_element element}}]);
+ };
+ {
+ pstr_desc =
+ Pstr_value (_, [{pvb_pat = {ppat_desc = Ppat_var {txt = "after"}}}]);
+ };
+ ] -> (
+ match element with
+ | Jsx_unary_element _ -> ()
+ | Jsx_container_element
+ {
+ jsx_container_element_closing_tag = Some _;
+ jsx_container_element_children = [_];
+ } ->
+ ()
+ | _ -> assert_failure "Expected the children and closing tag to survive"
+ )
+ | _ -> assert_failure "Expected the following binding to survive")
+ [
+ "";
+ "";
+ "";
+ "{42}";
+ "{42}";
+ ]
+
+let test_nested_missing_jsx_prop_values _ =
+ List.iter
+ (fun source ->
+ let result =
+ Res_driver.parse_implementation_from_source
+ ~display_filename:"JsxLocTest.res"
+ ~source:("let element = " ^ source ^ "\nlet after = 42")
+ in
+ assert_equal
+ ["Expected a JSX prop value"]
+ (List.map Res_diagnostics.explain result.diagnostics);
+ match result.parsetree with
+ | [
+ {
+ Parsetree.pstr_desc =
+ Pstr_value
+ ( _,
+ [
+ {
+ pvb_expr =
+ {
+ pexp_desc =
+ Pexp_jsx_element
+ (Jsx_container_element
+ {
+ jsx_container_element_closing_tag = Some _;
+ jsx_container_element_children =
+ [
+ {
+ pexp_desc =
+ Pexp_jsx_element
+ (Jsx_unary_element
+ {
+ jsx_unary_element_props =
+ [
+ JSXPropValue
+ ( _,
+ _,
+ {
+ pexp_desc =
+ Pexp_extension
+ ( {
+ txt =
+ "rescript.exprhole";
+ },
+ PStr [] );
+ } );
+ ];
+ });
+ };
+ ];
+ });
+ };
+ };
+ ] );
+ };
+ {
+ pstr_desc =
+ Pstr_value (_, [{pvb_pat = {ppat_desc = Ppat_var {txt = "after"}}}]);
+ };
+ ] ->
+ ()
+ | _ ->
+ assert_failure
+ "Expected the child, parent closing tag and following binding to \
+ survive")
+ [
+ "
";
+ "";
+ " /* comment */ ";
+ ]
+
+let test_regexp_jsx_prop_values _ =
+ List.iter
+ (fun pattern ->
+ let result =
+ Res_driver.parse_implementation_from_source
+ ~display_filename:"JsxLocTest.res"
+ ~source:("let element = ")
+ in
+ OUnit.assert_bool "regexp JSX value remains valid" (not result.invalid);
+ match get_jsx_props result.parsetree with
+ | [
+ Parsetree.JSXPropValue (_, _, {pexp_desc = Pexp_regexp literal; pexp_loc});
+ ] ->
+ assert_equal pattern literal.pattern;
+ assert_equal "g" literal.flags;
+ OUnit.assert_bool "regexp retains its location"
+ (pexp_loc <> Location.none)
+ | _ -> assert_failure "Expected a regexp JSX value")
+ ["abc"; ">"; ">word"; "."; "><"];
+ List.iter
+ (fun source ->
+ let result =
+ Res_driver.parse_implementation_from_source
+ ~display_filename:"JsxLocTest.res" ~source
+ in
+ OUnit.assert_bool "complete regexp-valued elements remain valid"
+ (not result.invalid))
+ [
+ "let element =
{42}";
+ "let element = / second=/>/ />";
+ "let element =
/ />
";
+ "let element = / >/ />";
+ ]
+
+let test_regexp_before_later_jsx_errors _ =
+ List.iter
+ (fun (jsx, expected_diagnostic) ->
+ let result =
+ Res_driver.parse_implementation_from_source
+ ~display_filename:"JsxLocTest.res"
+ ~source:("let element = " ^ jsx ^ "\nlet after = 42")
+ in
+ assert_equal [expected_diagnostic]
+ (List.map Res_diagnostics.explain result.diagnostics);
+ let regexp_count = ref 0 in
+ let hole_count = ref 0 in
+ let iterator =
+ {
+ Ast_iterator.default_iterator with
+ expr =
+ (fun self expr ->
+ (match expr.Parsetree.pexp_desc with
+ | Pexp_jsx_element element ->
+ let props =
+ match element with
+ | Jsx_unary_element {jsx_unary_element_props} ->
+ jsx_unary_element_props
+ | Jsx_container_element {jsx_container_element_props} ->
+ jsx_container_element_props
+ | Jsx_fragment _ -> []
+ in
+ List.iter
+ (function
+ | Parsetree.JSXPropValue ({txt = "a"}, _, regexp) -> (
+ let wire =
+ Ast_mapper_to0.default_mapper.expr
+ Ast_mapper_to0.default_mapper regexp
+ in
+ match wire.Parsetree0.pexp_desc with
+ | Pexp_extension
+ ( {txt = "re"},
+ PStr
+ [
+ {
+ pstr_desc =
+ Pstr_eval
+ ( {
+ pexp_desc =
+ Pexp_constant
+ (Pconst_string ("/>/", _));
+ },
+ [] );
+ };
+ ] ) ->
+ incr regexp_count
+ | _ ->
+ assert_failure
+ "The earlier regexp must survive recovery")
+ | Parsetree.JSXPropValue
+ ( {txt = "b"},
+ _,
+ {
+ pexp_desc =
+ Pexp_extension
+ ({txt = "rescript.exprhole"}, PStr []);
+ } ) ->
+ incr hole_count
+ | _ -> ())
+ props
+ | _ -> ());
+ Ast_iterator.default_iterator.expr self expr);
+ }
+ in
+ iterator.structure iterator result.parsetree;
+ assert_equal 1 !regexp_count;
+ assert_equal
+ (if expected_diagnostic = "Expected a JSX prop value" then 1 else 0)
+ !hole_count;
+ match result.parsetree with
+ | [
+ _;
+ {
+ pstr_desc =
+ Pstr_value (_, [{pvb_pat = {ppat_desc = Ppat_var {txt = "after"}}}]);
+ };
+ ] ->
+ ()
+ | _ -> assert_failure "Expected the following binding to survive")
+ [
+ ("/ b= />", "Expected a JSX prop value");
+ ("/ >", "Expected a JSX prop value");
+ ("
/ b= />
", "Expected a JSX prop value");
+ ( "
/ b=? /> /* comment */
",
+ "Expected a JSX prop value" );
+ ( "/ >{let bad = }",
+ "This let-binding misses an expression" );
+ ]
+
let suites =
__FILE__
>::: [
+ "missing_prop_values" >:: test_missing_jsx_prop_values;
+ "nested_missing_prop_values" >:: test_nested_missing_jsx_prop_values;
+ "regexp_prop_values" >:: test_regexp_jsx_prop_values;
+ "regexp_before_later_errors" >:: test_regexp_before_later_jsx_errors;
"prop_value_roundtrip" >:: test_jsx_prop_value_loc_roundtrip;
"spread_roundtrip" >:: test_jsx_spread_loc_roundtrip;
]
diff --git a/tests/ounit_tests/ounit_lambda_constant_tests.ml b/tests/ounit_tests/ounit_lambda_constant_tests.ml
index 5c41811a44..0b875eccd2 100644
--- a/tests/ounit_tests/ounit_lambda_constant_tests.ml
+++ b/tests/ounit_tests/ounit_lambda_constant_tests.ml
@@ -191,6 +191,95 @@ let suites =
__FILE__
>::: switch_tests
@ [
+ ( "regexp literals lower to raw JavaScript" >:: fun _ ->
+ let expression = Ast_helper.Exp.regexp {|a\/b\d|} "ig" in
+ let wire =
+ Ast_mapper_to0.default_mapper.expr Ast_mapper_to0.default_mapper
+ expression
+ in
+ let payload_loc = {Location.none with loc_ghost = false} in
+ let payload_attr =
+ (Location.mknoloc "test.payload", Parsetree0.PStr [])
+ in
+ let legacy ?(delimiter = Some "js") ?(attrs = []) () =
+ Ast_helper0.Exp.extension
+ ( Location.mknoloc "re",
+ Parsetree0.PStr
+ [
+ Ast_helper0.Str.eval
+ (Ast_helper0.Exp.constant ~loc:payload_loc ~attrs
+ (Parsetree0.Pconst_string ({|/a\/b\d/ig|}, delimiter)));
+ ] )
+ in
+ List.iter
+ (fun wire ->
+ let expression =
+ Ast_mapper_from0.default_mapper.expr
+ Ast_mapper_from0.default_mapper wire
+ in
+ (match expression.Parsetree.pexp_desc with
+ | Pexp_regexp {pattern; flags} ->
+ OUnit.assert_equal {|a\/b\d|} pattern;
+ OUnit.assert_equal "ig" flags
+ | _ ->
+ OUnit.assert_failure
+ "AST0 regexp must leave the bridge as a literal");
+ let lowered =
+ Bs_builtin_ppx.mapper.expr Bs_builtin_ppx.mapper expression
+ in
+ let raw =
+ match lowered.Parsetree.pexp_desc with
+ | Pexp_constraint
+ ( raw,
+ {
+ ptyp_desc =
+ Ptyp_constr
+ ( {
+ txt =
+ Longident.Ldot (Lident "Stdlib_RegExp", "t");
+ },
+ [] );
+ } ) ->
+ raw
+ | _ ->
+ OUnit.assert_failure "expected the regexp type constraint"
+ in
+ let structure =
+ [
+ Ast_helper.Str.value Asttypes.Nonrecursive
+ [
+ Ast_helper.Vb.mk
+ (Ast_helper.Pat.var (Location.mknoloc "regexp"))
+ raw;
+ ];
+ ]
+ in
+ let typed, _, _ =
+ Typemod.type_structure Env.initial_safe_string structure
+ Location.none
+ in
+ let implementation =
+ Translmod.transl_implementation "RegexpTest"
+ (typed, Typedtree.Tcoerce_none)
+ in
+ let sources = ref [] in
+ let rec collect lambda =
+ (match lambda with
+ | Lambda.Lprim {primitive = Praw_js_code {code}; args = []}
+ ->
+ sources := code :: !sources
+ | _ -> ());
+ Lambda_traverse.iter collect lambda
+ in
+ collect implementation.lambda;
+ OUnit.assert_equal ~printer:Ext_obj.dump [{|/a\/b\d/ig|}]
+ !sources)
+ [
+ wire;
+ legacy ();
+ legacy ~attrs:[payload_attr] ();
+ legacy ~delimiter:None ();
+ ] );
( "typed string constants" >:: fun _ ->
Lambda.const_string "value" =~ Lambda.Const_string "value" );
( "compiler-generated strings normalize malformed bytes" >:: fun _ ->
diff --git a/tests/syntax_tests/data/ast-mapping/expected/regexp.res.txt b/tests/syntax_tests/data/ast-mapping/expected/regexp.res.txt
new file mode 100644
index 0000000000..5818664601
--- /dev/null
+++ b/tests/syntax_tests/data/ast-mapping/expected/regexp.res.txt
@@ -0,0 +1,13 @@
+let plain = /abc/
+let escaped = /a\/b\\c\d/ig
+let characterClass = /[/\]]+/u
+let unicode = /δΈη+/g
+let dot = /./s
+let flags = /a/dgimsuy
+@foo
+let attributed = /a/g
+let comments = [/* before */ /a/g /* after */, /b/]
+let use = f(/a/i, /b/m)
+let expressionAttributes = @foo /a/g
+let choice = flag ? /a/i : /b/m
+let indexed = /a/g["lastIndex"]
diff --git a/tests/syntax_tests/data/ast-mapping/regexp.res b/tests/syntax_tests/data/ast-mapping/regexp.res
new file mode 100644
index 0000000000..5818664601
--- /dev/null
+++ b/tests/syntax_tests/data/ast-mapping/regexp.res
@@ -0,0 +1,13 @@
+let plain = /abc/
+let escaped = /a\/b\\c\d/ig
+let characterClass = /[/\]]+/u
+let unicode = /δΈη+/g
+let dot = /./s
+let flags = /a/dgimsuy
+@foo
+let attributed = /a/g
+let comments = [/* before */ /a/g /* after */, /b/]
+let use = f(/a/i, /b/m)
+let expressionAttributes = @foo /a/g
+let choice = flag ? /a/i : /b/m
+let indexed = /a/g["lastIndex"]
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
index abacb8dbbf..ee7b5aedc4 100644
--- a/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
+++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/jsx.res.txt
@@ -124,9 +124,58 @@
16 β
17 β // spread children
18 β let x =
...c
+ 19 β // Missing JSX values preserve closing tags and following bindings.
+ 20 β let missing =
Spreading JSX children is no longer supported.
+
+ Syntax error!
+ syntax_tests/data/parsing/errors/expressions/jsx.res:20:28
+
+ 18 β let x =
...c
+ 19 β // Missing JSX values preserve closing tags and following bindings.
+ 20 β let missing =
+ 21 β let afterMissing = 42
+ 22 β let missingOptional =
+
+ Expected a JSX prop value
+
+
+ Syntax error!
+ syntax_tests/data/parsing/errors/expressions/jsx.res:22:37
+
+ 20 β let missing =
+ 21 β let afterMissing = 42
+ 22 β let missingOptional =
+ 23 β let missingContainer = {42}
+ 24 β let afterContainer = 43
+
+ Expected a JSX prop value
+
+
+ Syntax error!
+ syntax_tests/data/parsing/errors/expressions/jsx.res:23:37
+
+ 21 β let afterMissing = 42
+ 22 β let missingOptional =
+ 23 β let missingContainer = {42}
+ 24 β let afterContainer = 43
+ 25 β let nestedMissing =
+
+ Expected a JSX prop value
+
+
+ Syntax error!
+ syntax_tests/data/parsing/errors/expressions/jsx.res:25:39
+
+ 23 β let missingContainer = {42}
+ 24 β let afterContainer = 43
+ 25 β let nestedMissing =
+ 26 β let afterNested = 44
+
+ Expected a JSX prop value
+
let x =
let x =
let x =
@@ -138,4 +187,12 @@ let x =
let x =
let x =
let x =
-let x =
+let afterNested = 44
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt
index ccd4e67318..37f64af9c2 100644
--- a/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt
+++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/regexCharacterClasses.res.txt
@@ -16,5 +16,5 @@
unterminated regex
-let re = [%re {js|/[]/|js}]
-;;[%re {js|//|js}]
\ No newline at end of file
+let re = /[]/
+;;//
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/expected/removedReExtension.res.txt b/tests/syntax_tests/data/parsing/errors/expressions/expected/removedReExtension.res.txt
new file mode 100644
index 0000000000..d9f16f6fa0
--- /dev/null
+++ b/tests/syntax_tests/data/parsing/errors/expressions/expected/removedReExtension.res.txt
@@ -0,0 +1,23 @@
+
+ Syntax error!
+ syntax_tests/data/parsing/errors/expressions/removedReExtension.res:1:14-16
+
+ 1 β let legacy = %re("/abc/i")
+ 2 β let backquoted = %re(`/abc/i`)
+ 3 β let literal = /abc/i
+
+ The %re extension has been removed. Use a regexp literal such as /abc/i.
+
+
+ Syntax error!
+ syntax_tests/data/parsing/errors/expressions/removedReExtension.res:2:18-20
+
+ 1 β let legacy = %re("/abc/i")
+ 2 β let backquoted = %re(`/abc/i`)
+ 3 β let literal = /abc/i
+
+ The %re extension has been removed. Use a regexp literal such as /abc/i.
+
+let legacy = [%re {js|/abc/i|js}]
+let backquoted = [%re `/abc/i`]
+let literal = /abc/i
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/jsx.res b/tests/syntax_tests/data/parsing/errors/expressions/jsx.res
index 02c0a75f0e..abbe57ec32 100644
--- a/tests/syntax_tests/data/parsing/errors/expressions/jsx.res
+++ b/tests/syntax_tests/data/parsing/errors/expressions/jsx.res
@@ -15,4 +15,12 @@ let x =
let x =
// spread children
-let x =
+let afterNested = 44
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/errors/expressions/removedReExtension.res b/tests/syntax_tests/data/parsing/errors/expressions/removedReExtension.res
new file mode 100644
index 0000000000..27d52e684f
--- /dev/null
+++ b/tests/syntax_tests/data/parsing/errors/expressions/removedReExtension.res
@@ -0,0 +1,3 @@
+let legacy = %re("/abc/i")
+let backquoted = %re(`/abc/i`)
+let literal = /abc/i
\ No newline at end of file
diff --git a/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt b/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt
index 0549328722..7672385627 100644
--- a/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt
+++ b/tests/syntax_tests/data/parsing/grammar/expressions/expected/regex.res.txt
@@ -1,374 +1,373 @@
-let re = [%re {js|/a*[^a|b]/|js}]
-let re = [%re {js|/.*[^a|b]/|js}]
-let re = [%re {js|/(?Pa)/|js}]
-let re = [%re {js|/(?Pa)/|js}]
-let re = [%re {js|/(?Pa)/|js}]
-let re = [%re {js|/(?Pa)(?P=foo_123/|js}]
-let re = [%re {js|/(?Pa)(?P=1)/|js}]
-let re = [%re {js|/(?Pa)(?P=!)/|js}]
-let re = [%re {js|/(?Pa)(?P=foo_124/|js}]
-let re = [%re {js|/(?Pa)/|js}]
-let re = [%re {js|/(?Pa)(?P=foo_123)/|js}]
-let re = [%re {js|/\\1/|js}]
-let re = [%re {js|/[\\1]/|js}]
-let re = [%re {js|/\\09/|js}]
-let re = [%re {js|/\\141/|js}]
-let re = [%re {js|/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/|js}]
-let re = [%re {js|/\0/|js}]
-let re = [%re {js|/[\0a]/|js}]
-let re = [%re {js|/[a\0]/|js}]
-let re = [%re {js|/[^a\0]/|js}]
-let re = [%re {js|/\a[\b]\f\n\r\t\v/|js}]
-let re = [%re {js|/[\a][\b][\f][\n][\r][\t][\v]/|js}]
-let re = [%re {js|/\u/|js}]
-let re = [%re {js|/\xff/|js}]
-let re = [%re {js|/\x00ffffffffffffff/|js}]
-let re = [%re {js|/\x00f/|js}]
-let re = [%re {js|/\x00fe/|js}]
+let re = /a*[^a|b]/
+let re = /.*[^a|b]/
+let re = /(?Pa)/
+let re = /(?Pa)/
+let re = /(?Pa)/
+let re = /(?Pa)(?P=foo_123/
+let re = /(?Pa)(?P=1)/
+let re = /(?Pa)(?P=!)/
+let re = /(?Pa)(?P=foo_124/
+let re = /(?Pa)/
+let re = /(?Pa)(?P=foo_123)/
+let re = /\\1/
+let re = /[\\1]/
+let re = /\\09/
+let re = /\\141/
+let re = /(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/
+let re = /\0/
+let re = /[\0a]/
+let re = /[a\0]/
+let re = /[^a\0]/
+let re = /\a[\b]\f\n\r\t\v/
+let re = /[\a][\b][\f][\n][\r][\t][\v]/
+let re = /\u/
+let re = /\xff/
+let re = /\x00ffffffffffffff/
+let re = /\x00f/
+let re = /\x00fe/
let re =
- [%re
- {js|/^\w+=(\\[\000-\277]|[^\n\\])*", "SRC=eval.c g.c blah blah blah \\\\\n\tapes.c/|js}]
-let re = [%re {js|/a.b/|js}]
-let re = [%re {js|/a.b/|js}]
-let re = [%re {js|/a.*b/|js}]
-let re = [%re {js|/a.{4,5}b/|js}]
-let re = [%re {js|/a.b/|js}]
-let re = [%re {js|/(?s)a.b/|js}]
-let re = [%re {js|/(?s)a.*b/|js}]
-let re = [%re {js|/(?s)a.{4,5}b/|js}]
-let re = [%re {js|/(?s)a.b/|js}]
-let re = [%re {js|/)/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/ab*c/|js}]
-let re = [%re {js|/ab*bc/|js}]
-let re = [%re {js|/ab*bc/|js}]
-let re = [%re {js|/ab*bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab?bc/|js}]
-let re = [%re {js|/ab?bc/|js}]
-let re = [%re {js|/ab?bc/|js}]
-let re = [%re {js|/ab?c/|js}]
-let re = [%re {js|/^abc$/|js}]
-let re = [%re {js|/^abc$/|js}]
-let re = [%re {js|/^abc/|js}]
-let re = [%re {js|/^abc$/|js}]
-let re = [%re {js|/abc$/|js}]
-let re = [%re {js|/^/|js}]
-let re = [%re {js|/$/|js}]
-let re = [%re {js|/a.c/|js}]
-let re = [%re {js|/a.c/|js}]
-let re = [%re {js|/a.*c/|js}]
-let re = [%re {js|/a.*c/|js}]
-let re = [%re {js|/a[bc]d/|js}]
-let re = [%re {js|/a[bc]d/|js}]
-let re = [%re {js|/a[b-d]e/|js}]
-let re = [%re {js|/a[b-d]e/|js}]
-let re = [%re {js|/a[b-d]/|js}]
-let re = [%re {js|/a[-b]/|js}]
-let re = [%re {js|/a[\\-b]/|js}]
-let re = [%re {js|/a[]b/|js}]
-let re = [%re {js|/a\\/|js}]
-let re = [%re {js|/a]/|js}]
-let re = [%re {js|/a[]]b/|js}]
-let re = [%re {js|/a[\\]]b/|js}]
-let re = [%re {js|/a[^bc]d/|js}]
-let re = [%re {js|/a[^bc]d/|js}]
-let re = [%re {js|/a[^-b]c/|js}]
-let re = [%re {js|/a[^-b]c/|js}]
-let re = [%re {js|/a[^]b]c/|js}]
-let re = [%re {js|/a[^]b]c/|js}]
-let re = [%re {js|/\\ba\\b/|js}]
-let re = [%re {js|/\\ba\\b/|js}]
-let re = [%re {js|/\\ba\\b/|js}]
-let re = [%re {js|/\\by\\b/|js}]
-let re = [%re {js|/\\by\\b/|js}]
-let re = [%re {js|/\\by\\b/|js}]
-let re = [%re {js|/x\\b/|js}]
-let re = [%re {js|/x\\B/|js}]
-let re = [%re {js|/\\Bz/|js}]
-let re = [%re {js|/z\\B/|js}]
-let re = [%re {js|/\\Bx/|js}]
-let re = [%re {js|/\\Ba\\B/|js}]
-let re = [%re {js|/\\Ba\\B/|js}]
-let re = [%re {js|/\\Ba\\B/|js}]
-let re = [%re {js|/\\By\\B/|js}]
-let re = [%re {js|/\\By\\B/|js}]
-let re = [%re {js|/\\By\\b/|js}]
-let re = [%re {js|/\\by\\B/|js}]
-let re = [%re {js|/\\By\\B/|js}]
-let re = [%re {js|/ab|cd/|js}]
-let re = [%re {js|/ab|cd/|js}]
-let re = [%re {js|/()ef/|js}]
-let re = [%re {js|/$b/|js}]
-let re = [%re {js|/a\\(b/|js}]
-let re = [%re {js|/a\\(*b/|js}]
-let re = [%re {js|/a\\(*b/|js}]
-let re = [%re {js|/a\\\\b/|js}]
-let re = [%re {js|/((a))/|js}]
-let re = [%re {js|/(a)b(c)/|js}]
-let re = [%re {js|/a+b+c/|js}]
-let re = [%re {js|/(a+|b)*/|js}]
-let re = [%re {js|/(a+|b)+/|js}]
-let re = [%re {js|/(a+|b)?/|js}]
-let re = [%re {js|/)(/|js}]
-let re = [%re {js|/[^ab]*/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/a*/|js}]
-let re = [%re {js|/a|b|c|d|e/|js}]
-let re = [%re {js|/(a|b|c|d|e)f/|js}]
-let re = [%re {js|/abcd*efg/|js}]
-let re = [%re {js|/ab*/|js}]
-let re = [%re {js|/ab*/|js}]
-let re = [%re {js|/(ab|cd)e/|js}]
-let re = [%re {js|/[abhgefdc]ij/|js}]
-let re = [%re {js|/^(ab|cd)e/|js}]
-let re = [%re {js|/(abc|)ef/|js}]
-let re = [%re {js|/(a|b)c*d/|js}]
-let re = [%re {js|/(ab|ab*)bc/|js}]
-let re = [%re {js|/a([bc]*)c*/|js}]
-let re = [%re {js|/a([bc]*)(c*d)/|js}]
-let re = [%re {js|/a([bc]+)(c*d)/|js}]
-let re = [%re {js|/a([bc]*)(c+d)/|js}]
-let re = [%re {js|/a[bcd]*dcdcde/|js}]
-let re = [%re {js|/a[bcd]+dcdcde/|js}]
-let re = [%re {js|/(ab|a)b*c/|js}]
-let re = [%re {js|/((a)(b)c)(d)/|js}]
-let re = [%re {js|/[a-zA-Z_][a-zA-Z0-9_]*/|js}]
-let re = [%re {js|/^a(bc+|b[eh])g|.h$/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(((((((((a)))))))))/|js}]
-let re = [%re {js|/multiple words of text/|js}]
-let re = [%re {js|/multiple words/|js}]
-let re = [%re {js|/(.*)c(.*)/|js}]
-let re = [%re {js|/\\((.*), (.*)\\)/|js}]
-let re = [%re {js|/[k]/|js}]
-let re = [%re {js|/a[-]?c/|js}]
-let re = [%re {js|/(abc)\\1/|js}]
-let re = [%re {js|/([a-c]*)\\1/|js}]
-let re = [%re {js|/^(.+)?B/|js}]
-let re = [%re {js|/(a+).\\1$/|js}]
-let re = [%re {js|/^(a+).\\1$/|js}]
-let re = [%re {js|/(abc)\\1/|js}]
-let re = [%re {js|/([a-c]+)\\1/|js}]
-let re = [%re {js|/(a)\\1/|js}]
-let re = [%re {js|/(a+)\\1/|js}]
-let re = [%re {js|/(a+)+\\1/|js}]
-let re = [%re {js|/(a).+\\1/|js}]
-let re = [%re {js|/(a)ba*\\1/|js}]
-let re = [%re {js|/(aa|a)a\\1$/|js}]
-let re = [%re {js|/(a|aa)a\\1$/|js}]
-let re = [%re {js|/(a+)a\\1$/|js}]
-let re = [%re {js|/([abc]*)\\1/|js}]
-let re = [%re {js|/(a)(b)c|ab/|js}]
-let re = [%re {js|/(a)+x/|js}]
-let re = [%re {js|/([ac])+x/|js}]
-let re = [%re {js|/([^.]*)\\.([^:]*):[T ]+(.*)/|js}]
-let re = [%re {js|/([^N]*N)+/|js}]
-let re = [%re {js|/([^N]*N)+/|js}]
-let re = [%re {js|/([abc]*)x/|js}]
-let re = [%re {js|/([abc]*)x/|js}]
-let re = [%re {js|/([xyz]*)x/|js}]
-let re = [%re {js|/(a)+b|aac/|js}]
-let re = [%re {js|/(?Paaa)a/|js}]
-let re = [%re {js|/(?Paaa)a/|js}]
-let re = [%re {js|/(?Paa)(?P=id)/|js}]
-let re = [%re {js|/(?Paa)(?P=xd)/|js}]
-let re = [%re {js|/\\1/|js}]
-let re = [%re {js|/\\09/|js}]
-let re = [%re {js|/\\141/|js}]
-let re = [%re {js|/(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/ab*c/|js}]
-let re = [%re {js|/ab*bc/|js}]
-let re = [%re {js|/ab*bc/|js}]
-let re = [%re {js|/ab*bc/|js}]
-let re = [%re {js|/ab{0,}bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab{1,}bc/|js}]
-let re = [%re {js|/ab+bc/|js}]
-let re = [%re {js|/ab{1,}bc/|js}]
-let re = [%re {js|/ab{1,3}bc/|js}]
-let re = [%re {js|/ab{3,4}bc/|js}]
-let re = [%re {js|/ab{4,5}bc/|js}]
-let re = [%re {js|/ab?bc/|js}]
-let re = [%re {js|/ab?bc/|js}]
-let re = [%re {js|/ab{0,1}bc/|js}]
-let re = [%re {js|/ab?bc/|js}]
-let re = [%re {js|/ab?c/|js}]
-let re = [%re {js|/ab{0,1}c/|js}]
-let re = [%re {js|/^abc$/|js}]
-let re = [%re {js|/^abc$/|js}]
-let re = [%re {js|/^abc/|js}]
-let re = [%re {js|/^abc$/|js}]
-let re = [%re {js|/abc$/|js}]
-let re = [%re {js|/^/|js}]
-let re = [%re {js|/$/|js}]
-let re = [%re {js|/a.c/|js}]
-let re = [%re {js|/a.c/|js}]
-let re = [%re {js|/a.*c/|js}]
-let re = [%re {js|/a.*c/|js}]
-let re = [%re {js|/a[bc]d/|js}]
-let re = [%re {js|/a[bc]d/|js}]
-let re = [%re {js|/a[b-d]e/|js}]
-let re = [%re {js|/a[b-d]e/|js}]
-let re = [%re {js|/a[b-d]/|js}]
-let re = [%re {js|/a[-b]/|js}]
-let re = [%re {js|/a[b-]/|js}]
-let re = [%re {js|/a[b-a]/|js}]
-let re = [%re {js|/a[]b/|js}]
-let re = [%re {js|/a]/|js}]
-let re = [%re {js|/a[]]b/|js}]
-let re = [%re {js|/a[^bc]d/|js}]
-let re = [%re {js|/a[^bc]d/|js}]
-let re = [%re {js|/a[^-b]c/|js}]
-let re = [%re {js|/a[^-b]c/|js}]
-let re = [%re {js|/a[^]b]c/|js}]
-let re = [%re {js|/a[^]b]c/|js}]
-let re = [%re {js|/ab|cd/|js}]
-let re = [%re {js|/ab|cd/|js}]
-let re = [%re {js|/()ef/|js}]
-let re = [%re {js|/(*)b/|js}]
-let re = [%re {js|/$b/|js}]
-let re = [%re {js|/a\\/|js}]
-let re = [%re {js|/a\\(b/|js}]
-let re = [%re {js|/a\\(*b/|js}]
-let re = [%re {js|/a\\(*b/|js}]
-let re = [%re {js|/a\\\\b/|js}]
-let re = [%re {js|/((a))/|js}]
-let re = [%re {js|/(a)b(c)/|js}]
-let re = [%re {js|/a+b+c/|js}]
-let re = [%re {js|/a{1,}b{1,}c/|js}]
-let re = [%re {js|/a**/|js}]
-let re = [%re {js|/a.+?c/|js}]
-let re = [%re {js|/(a+|b)*/|js}]
-let re = [%re {js|/(a+|b){0,}/|js}]
-let re = [%re {js|/(a+|b)+/|js}]
-let re = [%re {js|/(a+|b){1,}/|js}]
-let re = [%re {js|/(a+|b)?/|js}]
-let re = [%re {js|/(a+|b){0,1}/|js}]
-let re = [%re {js|/)(/|js}]
-let re = [%re {js|/[^ab]*/|js}]
-let re = [%re {js|/abc/|js}]
-let re = [%re {js|/a*/|js}]
-let re = [%re {js|/([abc])*d/|js}]
-let re = [%re {js|/([abc])*bcd/|js}]
-let re = [%re {js|/a|b|c|d|e/|js}]
-let re = [%re {js|/(a|b|c|d|e)f/|js}]
-let re = [%re {js|/abcd*efg/|js}]
-let re = [%re {js|/ab*/|js}]
-let re = [%re {js|/ab*/|js}]
-let re = [%re {js|/(ab|cd)e/|js}]
-let re = [%re {js|/[abhgefdc]ij/|js}]
-let re = [%re {js|/^(ab|cd)e/|js}]
-let re = [%re {js|/(abc|)ef/|js}]
-let re = [%re {js|/(a|b)c*d/|js}]
-let re = [%re {js|/(ab|ab*)bc/|js}]
-let re = [%re {js|/a([bc]*)c*/|js}]
-let re = [%re {js|/a([bc]*)(c*d)/|js}]
-let re = [%re {js|/a([bc]+)(c*d)/|js}]
-let re = [%re {js|/a([bc]*)(c+d)/|js}]
-let re = [%re {js|/a[bcd]*dcdcde/|js}]
-let re = [%re {js|/a[bcd]+dcdcde/|js}]
-let re = [%re {js|/(ab|a)b*c/|js}]
-let re = [%re {js|/((a)(b)c)(d)/|js}]
-let re = [%re {js|/[a-zA-Z_][a-zA-Z0-9_]*/|js}]
-let re = [%re {js|/^a(bc+|b[eh])g|.h$/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/(bc+d$|ef*g.|h?i(j|k))/|js}]
-let re = [%re {js|/((((((((((a))))))))))/|js}]
-let re = [%re {js|/((((((((((a))))))))))\\10/|js}]
-let re = [%re {js|/((((((((((a))))))))))\\41/|js}]
-let re = [%re {js|/(?i)((((((((((a))))))))))\\41/|js}]
-let re = [%re {js|/(((((((((a)))))))))/|js}]
-let re = [%re {js|/multiple words of text/|js}]
-let re = [%re {js|/multiple words/|js}]
-let re = [%re {js|/(.*)c(.*)/|js}]
-let re = [%re {js|/\\((.*), (.*)\\)/|js}]
-let re = [%re {js|/[k]/|js}]
-let re = [%re {js|/a[-]?c/|js}]
-let re = [%re {js|/(abc)\\1/|js}]
-let re = [%re {js|/([a-c]*)\\1/|js}]
-let re = [%re {js|/a(?!b)./|js}]
-let re = [%re {js|/a(?=d)./|js}]
-let re = [%re {js|/a(?=c|d)./|js}]
-let re = [%re {js|/a(?:b|c|d)(.)/|js}]
-let re = [%re {js|/a(?:b|c|d)*(.)/|js}]
-let re = [%re {js|/a(?:b|c|d)+?(.)/|js}]
-let re = [%re {js|/a(?:b|(c|e){1,2}?|d)+?(.)/|js}]
-let re = [%re {js|/^(.+)?B/|js}]
-let re = [%re {js|/(?]*?b/|js}]
-let re = [%re {js|/^a*?$/|js}]
-let re = [%re {js|/^((a)c)?(ab)$/|js}]
-let re = [%re {js|/^([ab]*?)(?=(b)?)c/|js}]
-let re = [%re {js|/^([ab]*?)(?!(b))c/|js}]
-let re = [%re {js|/^([ab]*?)(?aaa)a/
+let re = /(?Paaa)a/
+let re = /(?Paa)(?P=id)/
+let re = /(?Paa)(?P=xd)/
+let re = /\\1/
+let re = /\\09/
+let re = /\\141/
+let re = /(a)(b)(c)(d)(e)(f)(g)(h)(i)(j)(k)(l)\\119/
+let re = /abc/
+let re = /abc/
+let re = /abc/
+let re = /abc/
+let re = /abc/
+let re = /abc/
+let re = /ab*c/
+let re = /ab*bc/
+let re = /ab*bc/
+let re = /ab*bc/
+let re = /ab{0,}bc/
+let re = /ab+bc/
+let re = /ab+bc/
+let re = /ab+bc/
+let re = /ab{1,}bc/
+let re = /ab+bc/
+let re = /ab{1,}bc/
+let re = /ab{1,3}bc/
+let re = /ab{3,4}bc/
+let re = /ab{4,5}bc/
+let re = /ab?bc/
+let re = /ab?bc/
+let re = /ab{0,1}bc/
+let re = /ab?bc/
+let re = /ab?c/
+let re = /ab{0,1}c/
+let re = /^abc$/
+let re = /^abc$/
+let re = /^abc/
+let re = /^abc$/
+let re = /abc$/
+let re = /^/
+let re = /$/
+let re = /a.c/
+let re = /a.c/
+let re = /a.*c/
+let re = /a.*c/
+let re = /a[bc]d/
+let re = /a[bc]d/
+let re = /a[b-d]e/
+let re = /a[b-d]e/
+let re = /a[b-d]/
+let re = /a[-b]/
+let re = /a[b-]/
+let re = /a[b-a]/
+let re = /a[]b/
+let re = /a]/
+let re = /a[]]b/
+let re = /a[^bc]d/
+let re = /a[^bc]d/
+let re = /a[^-b]c/
+let re = /a[^-b]c/
+let re = /a[^]b]c/
+let re = /a[^]b]c/
+let re = /ab|cd/
+let re = /ab|cd/
+let re = /()ef/
+let re = /(*)b/
+let re = /$b/
+let re = /a\\/
+let re = /a\\(b/
+let re = /a\\(*b/
+let re = /a\\(*b/
+let re = /a\\\\b/
+let re = /((a))/
+let re = /(a)b(c)/
+let re = /a+b+c/
+let re = /a{1,}b{1,}c/
+let re = /a**/
+let re = /a.+?c/
+let re = /(a+|b)*/
+let re = /(a+|b){0,}/
+let re = /(a+|b)+/
+let re = /(a+|b){1,}/
+let re = /(a+|b)?/
+let re = /(a+|b){0,1}/
+let re = /)(/
+let re = /[^ab]*/
+let re = /abc/
+let re = /a*/
+let re = /([abc])*d/
+let re = /([abc])*bcd/
+let re = /a|b|c|d|e/
+let re = /(a|b|c|d|e)f/
+let re = /abcd*efg/
+let re = /ab*/
+let re = /ab*/
+let re = /(ab|cd)e/
+let re = /[abhgefdc]ij/
+let re = /^(ab|cd)e/
+let re = /(abc|)ef/
+let re = /(a|b)c*d/
+let re = /(ab|ab*)bc/
+let re = /a([bc]*)c*/
+let re = /a([bc]*)(c*d)/
+let re = /a([bc]+)(c*d)/
+let re = /a([bc]*)(c+d)/
+let re = /a[bcd]*dcdcde/
+let re = /a[bcd]+dcdcde/
+let re = /(ab|a)b*c/
+let re = /((a)(b)c)(d)/
+let re = /[a-zA-Z_][a-zA-Z0-9_]*/
+let re = /^a(bc+|b[eh])g|.h$/
+let re = /(bc+d$|ef*g.|h?i(j|k))/
+let re = /(bc+d$|ef*g.|h?i(j|k))/
+let re = /(bc+d$|ef*g.|h?i(j|k))/
+let re = /(bc+d$|ef*g.|h?i(j|k))/
+let re = /(bc+d$|ef*g.|h?i(j|k))/
+let re = /((((((((((a))))))))))/
+let re = /((((((((((a))))))))))\\10/
+let re = /((((((((((a))))))))))\\41/
+let re = /(?i)((((((((((a))))))))))\\41/
+let re = /(((((((((a)))))))))/
+let re = /multiple words of text/
+let re = /multiple words/
+let re = /(.*)c(.*)/
+let re = /\\((.*), (.*)\\)/
+let re = /[k]/
+let re = /a[-]?c/
+let re = /(abc)\\1/
+let re = /([a-c]*)\\1/
+let re = /a(?!b)./
+let re = /a(?=d)./
+let re = /a(?=c|d)./
+let re = /a(?:b|c|d)(.)/
+let re = /a(?:b|c|d)*(.)/
+let re = /a(?:b|c|d)+?(.)/
+let re = /a(?:b|(c|e){1,2}?|d)+?(.)/
+let re = /^(.+)?B/
+let re = /(?]*?b/
+let re = /^a*?$/
+let re = /^((a)c)?(ab)$/
+let re = /^([ab]*?)(?=(b)?)c/
+let re = /^([ab]*?)(?!(b))c/
+let re = /^([ab]*?)(? {
+ Mocha.test("fresh instances and independent lastIndex", () => {
+ let first = make();
+ let second = make();
+ Test_utils.eq("File \"regexp_literal_test.res\", line 10, characters 7-14", first === second, false);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 11, characters 7-14", first.test("aa"), true);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 12, characters 7-14", first.lastIndex, 1);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 13, characters 7-14", second.lastIndex, 0);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 14, characters 7-14", first.test("aa"), true);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 15, characters 7-14", first.test("aa"), false);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 16, characters 7-14", first.lastIndex, 0);
+ });
+ Mocha.test("escapes and flags survive lowering", () => {
+ Test_utils.eq("File \"regexp_literal_test.res\", line 19, characters 7-14", /a\/b\d/i.test("A/b2"), true);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 20, characters 7-14", /[/]/.test("/"), true);
+ Test_utils.eq("File \"regexp_literal_test.res\", line 21, characters 7-14", /δΈη/u.test("δΈη"), true);
+ });
+});
+
+export {
+ make,
+}
+/* Not a pure module */
diff --git a/tests/tests/src/regexp_literal_test.res b/tests/tests/src/regexp_literal_test.res
new file mode 100644
index 0000000000..b59d29b55c
--- /dev/null
+++ b/tests/tests/src/regexp_literal_test.res
@@ -0,0 +1,23 @@
+open Mocha
+open Test_utils
+
+let make = () => /a/g
+
+describe(__MODULE__, () => {
+ test("fresh instances and independent lastIndex", () => {
+ let first = make()
+ let second = make()
+ eq(__LOC__, first === second, false)
+ eq(__LOC__, first->RegExp.test("aa"), true)
+ eq(__LOC__, first->RegExp.lastIndex, 1)
+ eq(__LOC__, second->RegExp.lastIndex, 0)
+ eq(__LOC__, first->RegExp.test("aa"), true)
+ eq(__LOC__, first->RegExp.test("aa"), false)
+ eq(__LOC__, first->RegExp.lastIndex, 0)
+ })
+ test("escapes and flags survive lowering", () => {
+ eq(__LOC__, /a\/b\d/i->RegExp.test("A/b2"), true)
+ eq(__LOC__, /[/]/->RegExp.test("/"), true)
+ eq(__LOC__, /δΈη/u->RegExp.test("δΈη"), true)
+ })
+})