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 @@ + + Syntax error! + /.../fixtures/syntaxErrorsRemovedReExtension.res:1:10-12 + + 1 β”‚ let re = %re("/abc/i") + 2 β”‚ // Use the literal syntax instead. + 3 β”‚ 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 =
c
\ No newline at end of file +let x =
c
+let missing = +let afterMissing = 42 +let missingOptional = +let missingContainer = ((42) + [@res.braces ]) +let afterContainer = 43 +let nestedMissing =
+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 =
...c
\ No newline at end of file +let x =
...c
+// Missing JSX values preserve closing tags and following bindings. +let missing = +let afterMissing = 42 +let missingOptional = +let missingContainer = {42} +let afterContainer = 43 +let nestedMissing =
+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) + }) +})