Skip to content

Change completions of regex literals #7425

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
May 22, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
#### :nail_care: Polish

- Suggest awaiting promise before using it when types mismatch. https://github.com/rescript-lang/rescript/pull/7498
- Complete from `RegExp` stdlib module for regexes. https://github.com/rescript-lang/rescript/pull/7425

# 12.0.0-alpha.13

Expand Down
7 changes: 6 additions & 1 deletion analysis/src/TypeUtils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1265,4 +1265,9 @@ let completionPathFromMaybeBuiltin path =
| Some ("result", _) -> Some ["Stdlib"; "Result"]
| Some ("dict", _) -> Some ["Stdlib"; "Dict"]
| Some ("char", _) -> Some ["Stdlib"; "Char"]
| _ -> None
| _ -> (
match path |> Utils.expandPath |> List.rev with
| [mainModule; "t"] when String.starts_with ~prefix:"Stdlib_" mainModule ->
(* Route Stdlib_X to Stdlib.X for proper completions without the Stdlib_ prefix *)
Some (String.split_on_char '_' mainModule)
| _ -> None)
4 changes: 2 additions & 2 deletions compiler/frontend/ast_comb.ml
Original file line number Diff line number Diff line change
Expand Up @@ -57,9 +57,9 @@ let tuple_type_pair ?loc kind arity =
result )
| [] -> assert false

let re_id = Ast_literal.Lid.js_re_id
let regexp_id = Ast_literal.Lid.regexp_id

let to_js_re_type loc = Typ.constr ~loc {txt = re_id; loc} []
let to_regexp_type loc = Typ.constr ~loc {txt = regexp_id; loc} []

let to_undefined_type loc x =
Typ.constr ~loc {txt = Ast_literal.Lid.js_undefined; loc} [x]
Expand Down
2 changes: 1 addition & 1 deletion compiler/frontend/ast_comb.mli
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ val tuple_type_pair :

val to_undefined_type : Location.t -> Parsetree.core_type -> Parsetree.core_type

val to_js_re_type : Location.t -> Parsetree.core_type
val to_regexp_type : Location.t -> Parsetree.core_type

val single_non_rec_value :
?attrs:Parsetree.attributes ->
Expand Down
2 changes: 1 addition & 1 deletion compiler/frontend/ast_exp_extension.ml
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@ let handle_extension e (self : Bs_ast_mapper.mapper)
| "re" ->
Exp.constraint_ ~loc
(Ast_exp_handle_external.handle_raw ~kind:Raw_re loc payload)
(Ast_comb.to_js_re_type loc)
(Ast_comb.to_regexp_type loc)
| "external" -> (
Location.deprecated loc
"%external is deprecated, use %raw or regular FFI syntax instead.";
Expand Down
3 changes: 1 addition & 2 deletions compiler/frontend/ast_literal.ml
Original file line number Diff line number Diff line change
Expand Up @@ -70,8 +70,7 @@ module Lid = struct
(* FIXME: Use primitive module *)
let js_null_undefined : t = Ldot (Lident "Js", "null_undefined")

(* FIXME: Use primitive module *)
let js_re_id : t = Ldot (Ldot (Lident "Js", "Re"), "t")
let regexp_id : t = Ldot (Lident "Stdlib_RegExp", "t")
end

module No_loc = struct
Expand Down
2 changes: 1 addition & 1 deletion compiler/frontend/ast_literal.mli
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ module Lid : sig

val js_null_undefined : t

val js_re_id : t
val regexp_id : t
end

type expression_lit = Parsetree.expression lit
Expand Down
3 changes: 3 additions & 0 deletions tests/analysis_tests/tests/src/CompletionRegexp.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
let emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
// emailPattern->
// ^com
Original file line number Diff line number Diff line change
Expand Up @@ -582,15 +582,14 @@ Resolved opens 1 Stdlib
ContextPath Value[r]->la
ContextPath Value[r]
Path r
CPPipe pathFromEnv:Js.Re found:false
Path Js.Re.la
Path Stdlib.RegExp.la
Path la
[{
"label": "Js.Re.lastIndex",
"label": "RegExp.lastIndex",
"kind": 12,
"tags": [],
"detail": "t => int",
"documentation": {"kind": "markdown", "value": "\nReturns the index where the next match will start its search. This property\nwill be modified when the RegExp object is used, if the global (\"g\") flag is\nset.\n\n## Examples\n\n```rescript\nlet re = /ab*TODO/g\nlet str = \"abbcdefabh\"\n\nlet break = ref(false)\nwhile !break.contents {\n switch Js.Re.exec_(re, str) {\n | Some(result) => Js.Nullable.iter(Js.Re.captures(result)[0], (. match_) => {\n let next = Belt.Int.toString(Js.Re.lastIndex(re))\n Js.log(\"Found \" ++ (match_ ++ (\". Next match starts at \" ++ next)))\n })\n | None => break := true\n }\n}\n```\n\nSee\n[`RegExp: lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex)\non MDN.\n"}
"documentation": {"kind": "markdown", "value": "\n`lastIndex(regexp)` returns the index the next match will start from.\n\nSee [`RegExp.lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex) on MDN.\n\n## Examples\n```rescript\n// Match the first word in a sentence\nlet regexp = RegExp.fromString(\"\\\\w+\")\nlet someStr = \"Many words here.\"\n\nConsole.log(regexp->RegExp.lastIndex) // Logs `0` to the console\n\nregexp->RegExp.exec(someStr)->ignore\n\nConsole.log(regexp->RegExp.lastIndex) // Logs `4` to the console\n```\n"}
}]

Complete src/CompletionPipeChain.res 112:7
Expand Down
78 changes: 78 additions & 0 deletions tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
Complete src/CompletionRegexp.res 1:17
posCursor:[1:17] posNoWhite:[1:16] Found expr:[1:3->0:-1]
Completable: Cpath Value[emailPattern]->
Package opens Stdlib.place holder Pervasives.JsxModules.place holder
Resolved opens 1 Stdlib
ContextPath Value[emailPattern]->
ContextPath Value[emailPattern]
Path emailPattern
Path Stdlib.RegExp.
Path
[{
"label": "RegExp.lastIndex",
"kind": 12,
"tags": [],
"detail": "t => int",
"documentation": {"kind": "markdown", "value": "\n`lastIndex(regexp)` returns the index the next match will start from.\n\nSee [`RegExp.lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex) on MDN.\n\n## Examples\n```rescript\n// Match the first word in a sentence\nlet regexp = RegExp.fromString(\"\\\\w+\")\nlet someStr = \"Many words here.\"\n\nConsole.log(regexp->RegExp.lastIndex) // Logs `0` to the console\n\nregexp->RegExp.exec(someStr)->ignore\n\nConsole.log(regexp->RegExp.lastIndex) // Logs `4` to the console\n```\n"}
}, {
"label": "RegExp.setLastIndex",
"kind": 12,
"tags": [],
"detail": "(t, int) => unit",
"documentation": {"kind": "markdown", "value": "\n`setLastIndex(regexp, index)` set the index the next match will start from.\n\nSee [`RegExp.lastIndex`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/lastIndex) on MDN.\n\n## Examples\n```rescript\n// Match the first word in a sentence\nlet regexp = RegExp.fromString(\"\\\\w+\")\nlet someStr = \"Many words here.\"\n\nregexp->RegExp.setLastIndex(4)\nregexp->RegExp.exec(someStr)->ignore\n\nConsole.log(regexp->RegExp.lastIndex) // Logs `10` to the console\n```\n"}
}, {
"label": "RegExp.sticky",
"kind": 12,
"tags": [],
"detail": "t => bool",
"documentation": {"kind": "markdown", "value": "\n`sticky(regexp)` returns whether the sticky (`y`) flag is set on this `RegExp`.\n\nSee [`RegExp.sticky`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/sticky) on MDN.\n\n## Examples\n```rescript\nlet regexp1 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"g\")\nConsole.log(regexp1->RegExp.unicode) // Logs `false`, since `y` is not set\n\nlet regexp2 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"my\")\nConsole.log(regexp2->RegExp.unicode) // Logs `true`, since `y` is set\n```\n"}
}, {
"label": "RegExp.ignore",
"kind": 12,
"tags": [],
"detail": "t => unit",
"documentation": {"kind": "markdown", "value": "\n `ignore(regExp)` ignores the provided regExp and returns unit.\n\n This helper is useful when you want to discard a value (for example, the result of an operation with side effects)\n without having to store or process it further.\n"}
}, {
"label": "RegExp.exec",
"kind": 12,
"tags": [],
"detail": "(t, string) => option<Result.t>",
"documentation": {"kind": "markdown", "value": "\n`exec(regexp, string)` executes the provided regexp on the provided string, optionally returning a `RegExp.Result.t` if the regexp matches on the string.\n\nSee [`RegExp.exec`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/exec) on MDN.\n\n## Examples\n```rescript\n// Match the first word in a sentence\nlet regexp = RegExp.fromString(\"\\\\w+\")\n\nswitch regexp->RegExp.exec(\"ReScript is pretty cool, right?\") {\n| None => Console.log(\"Nope, no match...\")\n| Some(result) => Console.log(result->RegExp.Result.fullMatch) // Prints \"ReScript\"\n}\n```\n"}
}, {
"label": "RegExp.ignoreCase",
"kind": 12,
"tags": [],
"detail": "t => bool",
"documentation": {"kind": "markdown", "value": "\n`ignoreCase(regexp)` returns whether the ignore case (`i`) flag is set on this `RegExp`.\n\nSee [`RegExp.ignoreCase`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/ignoreCase) on MDN.\n\n## Examples\n```rescript\nlet regexp1 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"g\")\nConsole.log(regexp1->RegExp.ignoreCase) // Logs `false`, since `i` is not set\n\nlet regexp2 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"i\")\nConsole.log(regexp2->RegExp.ignoreCase) // Logs `true`, since `i` is set\n```\n"}
}, {
"label": "RegExp.global",
"kind": 12,
"tags": [],
"detail": "t => bool",
"documentation": {"kind": "markdown", "value": "\n`global(regexp)` returns whether the global (`g`) flag is set on this `RegExp`.\n\nSee [`RegExp.global`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/global) on MDN.\n\n## Examples\n```rescript\nlet regexp1 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"g\")\nConsole.log(regexp1->RegExp.global) // Logs `true`, since `g` is set\n\nlet regexp2 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"i\")\nConsole.log(regexp2->RegExp.global) // Logs `false`, since `g` is not set\n```\n"}
}, {
"label": "RegExp.multiline",
"kind": 12,
"tags": [],
"detail": "t => bool",
"documentation": {"kind": "markdown", "value": "\n`multiline(regexp)` returns whether the multiline (`m`) flag is set on this `RegExp`.\n\nSee [`RegExp.multiline`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/multiline) on MDN.\n\n## Examples\n```rescript\nlet regexp1 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"g\")\nConsole.log(regexp1->RegExp.multiline) // Logs `false`, since `m` is not set\n\nlet regexp2 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"mi\")\nConsole.log(regexp2->RegExp.multiline) // Logs `true`, since `m` is set\n```\n"}
}, {
"label": "RegExp.test",
"kind": 12,
"tags": [],
"detail": "(t, string) => bool",
"documentation": {"kind": "markdown", "value": "\n`test(regexp, string)` tests whether the provided `regexp` matches on the provided string.\n\nSee [`RegExp.test`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/test) on MDN.\n\n## Examples\n```rescript\n// Match the first word in a sentence\nlet regexp = RegExp.fromString(\"\\\\w+\")\n\nif regexp->RegExp.test(\"ReScript is cool!\") {\n Console.log(\"Yay, there's a word in there.\")\n}\n```\n"}
}, {
"label": "RegExp.unicode",
"kind": 12,
"tags": [],
"detail": "t => bool",
"documentation": {"kind": "markdown", "value": "\n`unicode(regexp)` returns whether the unicode (`y`) flag is set on this `RegExp`.\n\nSee [`RegExp.unicode`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/unicode) on MDN.\n\n## Examples\n```rescript\nlet regexp1 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"g\")\nConsole.log(regexp1->RegExp.unicode) // Logs `false`, since `u` is not set\n\nlet regexp2 = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"mu\")\nConsole.log(regexp2->RegExp.unicode) // Logs `true`, since `u` is set\n```\n"}
}, {
"label": "RegExp.source",
"kind": 12,
"tags": [],
"detail": "t => string",
"documentation": {"kind": "markdown", "value": "\n`source(regexp)` returns the source text for this `RegExp`, without the two forward slashes (if present), and without any set flags.\n\nSee [`RegExp.source`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/RegExp/source) on MDN.\n\n## Examples\n```rescript\nlet regexp = RegExp.fromStringWithFlags(\"\\\\w+\", ~flags=\"g\")\nConsole.log(regexp->RegExp.source) // Logs `\\w+`, the source text of the `RegExp`\n```\n"}
}]