From 75a77bede6e13748092c62f1d73b53b89cacf572 Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 5 May 2025 11:04:57 +0200 Subject: [PATCH 1/2] change completions of regex literals --- compiler/frontend/ast_literal.ml | 2 +- .../tests/src/CompletionRegexp.res | 3 + .../src/expected/CompletionPipeChain.res.txt | 8 +- .../src/expected/CompletionRegexp.res.txt | 78 +++++++++++++++++++ 4 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 tests/analysis_tests/tests/src/CompletionRegexp.res create mode 100644 tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt diff --git a/compiler/frontend/ast_literal.ml b/compiler/frontend/ast_literal.ml index 367f7f44ec..b4cb5d2c5e 100644 --- a/compiler/frontend/ast_literal.ml +++ b/compiler/frontend/ast_literal.ml @@ -71,7 +71,7 @@ module Lid = struct 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 js_re_id : t = Ldot (Lident "RegExp", "t") end module No_loc = struct diff --git a/tests/analysis_tests/tests/src/CompletionRegexp.res b/tests/analysis_tests/tests/src/CompletionRegexp.res new file mode 100644 index 0000000000..ebb124363b --- /dev/null +++ b/tests/analysis_tests/tests/src/CompletionRegexp.res @@ -0,0 +1,3 @@ +let emailPattern = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ +// emailPattern-> +// ^com diff --git a/tests/analysis_tests/tests/src/expected/CompletionPipeChain.res.txt b/tests/analysis_tests/tests/src/expected/CompletionPipeChain.res.txt index d35eb940f1..5c0a09690e 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionPipeChain.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionPipeChain.res.txt @@ -519,14 +519,14 @@ Resolved opens 1 Stdlib ContextPath Value[r]->la ContextPath Value[r] Path r -CPPipe pathFromEnv:Js.Re found:false -Path Js.Re.la +CPPipe pathFromEnv:Stdlib.RegExp found:false +Path Stdlib.RegExp.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 diff --git a/tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt b/tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt new file mode 100644 index 0000000000..b8effb211a --- /dev/null +++ b/tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt @@ -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 +CPPipe pathFromEnv:Stdlib.RegExp found:false +Path Stdlib.RegExp. +[{ + "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", + "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"} + }] + From 3f96e5568d2e7b7e0d3c62ca9b4cce94cc9e027c Mon Sep 17 00:00:00 2001 From: Gabriel Nordeborn Date: Mon, 5 May 2025 16:52:19 +0200 Subject: [PATCH 2/2] more sustainable fix, that also covers completions for all stdlib modules --- analysis/src/TypeUtils.ml | 7 ++++++- compiler/frontend/ast_literal.ml | 2 +- .../tests/src/expected/CompletionRegexp.res.txt | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/analysis/src/TypeUtils.ml b/analysis/src/TypeUtils.ml index c9b1acd011..4c89a2c117 100644 --- a/analysis/src/TypeUtils.ml +++ b/analysis/src/TypeUtils.ml @@ -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) diff --git a/compiler/frontend/ast_literal.ml b/compiler/frontend/ast_literal.ml index b4cb5d2c5e..c92865338f 100644 --- a/compiler/frontend/ast_literal.ml +++ b/compiler/frontend/ast_literal.ml @@ -71,7 +71,7 @@ module Lid = struct let js_null_undefined : t = Ldot (Lident "Js", "null_undefined") (* FIXME: Use primitive module *) - let js_re_id : t = Ldot (Lident "RegExp", "t") + let js_re_id : t = Ldot (Lident "Stdlib_RegExp", "t") end module No_loc = struct diff --git a/tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt b/tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt index b8effb211a..1eb863198c 100644 --- a/tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt +++ b/tests/analysis_tests/tests/src/expected/CompletionRegexp.res.txt @@ -6,7 +6,6 @@ Resolved opens 1 Stdlib ContextPath Value[emailPattern]-> ContextPath Value[emailPattern] Path emailPattern -CPPipe pathFromEnv:Stdlib.RegExp found:false Path Stdlib.RegExp. [{ "label": "RegExp.lastIndex",