feat: add rescript parser and queries (#6671)

This commit is contained in:
Riley Bruins 2024-07-27 08:34:30 -07:00 committed by GitHub
parent 7cec6219c4
commit f97e0de005
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 748 additions and 0 deletions

View file

@ -380,6 +380,7 @@ We are looking for maintainers to add more parsers and to write query files for
- [x] [regex](https://github.com/tree-sitter/tree-sitter-regex) (maintained by @theHamsta)
- [x] [rego](https://github.com/FallenAngel97/tree-sitter-rego) (maintained by @FallenAngel97)
- [x] [pip requirements](https://github.com/ObserverOfTime/tree-sitter-requirements) (maintained by @ObserverOfTime)
- [x] [rescript](https://github.com/rescript-lang/tree-sitter-rescript) (maintained by @ribru17)
- [x] [rnoweb](https://github.com/bamonroe/tree-sitter-rnoweb) (maintained by @bamonroe)
- [x] [robot](https://github.com/Hubro/tree-sitter-robot) (maintained by @Hubro)
- [x] [robots](https://github.com/opa-oz/tree-sitter-robots-txt) (maintained by @opa-oz)

View file

@ -620,6 +620,9 @@
"requirements": {
"revision": "5ad9b7581b3334f6ad492847d007f2fac6e6e5f2"
},
"rescript": {
"revision": "444c127686714b2358622427c3bdba7eb09021c6"
},
"rnoweb": {
"revision": "1a74dc0ed731ad07db39f063e2c5a6fe528cae7f"
},

View file

@ -1804,6 +1804,14 @@ list.requirements = {
readme_name = "pip requirements",
}
list.rescript = {
install_info = {
url = "https://github.com/rescript-lang/tree-sitter-rescript",
files = { "src/parser.c", "src/scanner.c" },
},
maintainers = { "@ribru17" },
}
list.rnoweb = {
install_info = {
url = "https://github.com/bamonroe/tree-sitter-rnoweb",

View file

@ -0,0 +1,12 @@
[
(block)
(function)
(module_declaration)
(type_declaration)
(external_declaration)
(call_expression)
(switch_expression)
(parenthesized_expression)
(record)
(include_statement)+
] @fold

View file

@ -0,0 +1,335 @@
(comment) @comment @spell
; Identifiers
;------------
; Escaped identifiers like \"+."
((value_identifier) @constant.macro
(#lua-match? @constant.macro "^%.*$"))
(value_identifier) @variable
[
(type_identifier)
(unit_type)
(list)
(list_pattern)
] @type
((type_identifier) @type.builtin
(#any-of? @type.builtin "int" "char" "string" "float" "bool" "unit"))
[
(variant_identifier)
(polyvar_identifier)
] @constructor
(record_type_field
(property_identifier) @property)
(record_field
(property_identifier) @property)
(object
(field
(property_identifier) @property))
(object_type
(field
(property_identifier) @property))
(module_identifier) @module
(member_expression
(property_identifier) @variable.member)
(value_identifier_path
(module_identifier)
(value_identifier) @variable)
(record_pattern
(value_identifier_path
(value_identifier) @variable.member))
(record_pattern
(value_identifier) @variable)
(labeled_argument
label: (value_identifier) @variable.parameter)
; Parameters
;----------------
(list_pattern
(value_identifier) @variable.parameter)
(spread_pattern
(value_identifier) @variable.parameter)
; String literals
;----------------
[
(string)
(template_string)
] @string
(character) @character
(escape_sequence) @string.escape
; Other literals
;---------------
[
(true)
(false)
] @boolean
(number) @number
(polyvar) @constructor
(polyvar_string) @constructor
; Functions
;----------
; parameter(s) in parens
(parameter
(value_identifier) @variable.parameter)
(labeled_parameter
(value_identifier) @variable.parameter)
; single parameter with no parens
(function
parameter: (value_identifier) @variable.parameter)
(parameter
(tuple_pattern
(tuple_item_pattern
(value_identifier) @variable.parameter)))
(parameter
(array_pattern
(value_identifier) @variable.parameter))
(parameter
(record_pattern
(value_identifier) @variable.parameter))
; function identifier in let binding
(let_binding
pattern: (value_identifier) @function
body: (function))
; function calls
(call_expression
function: (value_identifier_path
(value_identifier) @function.method.call .))
(call_expression
function: (value_identifier) @function.call)
; highlight the right-hand side of a pipe operator as a function call
(pipe_expression
(value_identifier) @function.call .)
(pipe_expression
(value_identifier_path
(value_identifier) @function.method.call .) .)
; Meta
;-----
(decorator_identifier) @attribute
(extension_identifier) @keyword
"%" @keyword
; Misc
;-----
(polyvar_type_pattern
"#" @constructor)
[
"include"
"open"
] @keyword.import
[
"private"
"mutable"
"rec"
] @keyword.modifier
"type" @keyword.type
[
"and"
"with"
"as"
] @keyword.operator
[
"export"
"external"
"let"
"module"
"assert"
"await"
"lazy"
"constraint"
] @keyword
"await" @keyword.coroutine
(function
"async" @keyword.coroutine)
(module_unpack
"unpack" @keyword)
[
"if"
"else"
"switch"
"when"
] @keyword.conditional
[
"exception"
"try"
"catch"
] @keyword.exception
(call_expression
function: (value_identifier) @keyword.exception
(#eq? @keyword.exception "raise"))
[
"for"
"in"
"to"
"downto"
"while"
] @keyword.repeat
[
"."
","
"|"
":"
] @punctuation.delimiter
[
"++"
"+"
"+."
"-"
"-."
"*"
"**"
"*."
"/."
"<="
"=="
"==="
"!"
"!="
"!=="
">="
"&&"
"||"
"="
":="
"->"
"|>"
":>"
"+="
"=>"
(uncurry)
] @operator
; Explicitly enclose these operators with binary_expression
; to avoid confusion with JSX tag delimiters
(binary_expression
[
"<"
">"
"/"
] @operator)
[
"("
")"
"{"
"}"
"["
"]"
"<"
">"
] @punctuation.bracket
(unit
[
"("
")"
] @constant.builtin)
(template_substitution
"${" @punctuation.special
"}" @punctuation.special) @none
(polyvar_type
[
"["
"[>"
"[<"
"]"
] @punctuation.bracket)
[
"~"
"?"
".."
"..."
] @punctuation.special
(ternary_expression
[
"?"
":"
] @keyword.conditional.ternary)
; JSX
;----------
(jsx_identifier) @tag
(jsx_element
open_tag: (jsx_opening_element
[
"<"
">"
] @tag.delimiter))
(jsx_element
close_tag: (jsx_closing_element
[
"<"
"/"
">"
] @tag.delimiter))
(jsx_self_closing_element
[
"/"
">"
"<"
] @tag.delimiter)
(jsx_fragment
[
">"
"<"
"/"
] @tag.delimiter)
(jsx_attribute
(property_identifier) @tag.attribute)

View file

@ -0,0 +1,36 @@
[
(block)
(record_type)
(record)
(parenthesized_expression)
(call_expression)
(function_type_parameters)
(function)
(switch_match)
(let_declaration)
(jsx_element)
(jsx_fragment)
(jsx_self_closing_element)
(object_type)
] @indent.begin
[
"}"
")"
(jsx_closing_element)
] @indent.branch @indent.end
(jsx_self_closing_element
"/" @indent.branch
">"? @indent.end)
; </> is captured as 3 different anonymous nodes
(jsx_fragment
"<"
"<" @indent.branch)
(jsx_fragment
">"
">" @indent.end)
(comment) @indent.auto

View file

@ -0,0 +1,33 @@
((comment) @injection.content
(#set! injection.language "comment"))
(extension_expression
(extension_identifier) @_name
(#eq? @_name "re")
(expression_statement
(_) @injection.content
(#set! injection.language "regex")))
(extension_expression
(extension_identifier) @_name
(#eq? @_name "raw")
(expression_statement
(_
(_) @injection.content
(#set! injection.language "javascript"))))
(extension_expression
(extension_identifier) @_name
(#eq? @_name "graphql")
(expression_statement
(_
(_) @injection.content
(#set! injection.language "graphql"))))
(extension_expression
(extension_identifier) @_name
(#eq? @_name "relay")
(expression_statement
(_
(_) @injection.content
(#set! injection.language "graphql"))))

View file

@ -0,0 +1,9 @@
(switch_expression) @local.scope
; Definitions
;------------
(type_declaration) @local.definition.type
(let_binding) @local.definition.var
(module_declaration) @local.definition.namespace

View file

@ -0,0 +1,23 @@
@genType
type person = {
name: string,
age: int,
}
@genType
type renderMe<'a> = React.component<{
"randomString": string,
"poly": 'a,
}>
@genType.import("./hookExample") @react.component
external make: (
~person: person,
~children: React.element,
~renderMe: renderMe<'a>,
) => React.element = "makeRenamed"
@genType.import("./hookExample")
external foo: (~person: person) => string = "foo"
let hi = 'a'

View file

@ -0,0 +1,151 @@
let hit = ({hit, children}: DocSearch.hitComponent) => {
let toTitle = str =>
str->Js.String2.charAt(0)->Js.String2.toUpperCase ++ Js.String2.sliceToEnd(str, ~from=1)
let description = switch hit.url
->Js.String2.split("/")
->Js.Array2.sliceFrom(1)
->Belt.List.fromArray {
| list{"blog" as r | "community" as r, ..._} => r->toTitle
| list{"docs", doc, version, ...rest} =>
let path = rest->Belt.List.toArray
let info =
path
->Js.Array2.slice(~start=0, ~end_=Js.Array2.length(path) - 1)
->Js.Array2.map(path =>
switch path {
| "api" => "API"
| other => toTitle(other)
}
)
[doc->toTitle, version->toTitle]->Js.Array2.concat(info)->Js.Array2.joinWith(" / ")
| _ => ""
}
<Next.Link href={hit.url} className="flex flex-col w-full">
<span className="text-gray-60 captions px-4 pt-3 pb-1 block">
{description->React.string}
</span>
children
</Next.Link>
}
let transformItems = (items: DocSearch.transformItems) => {
items->Belt.Array.keepMap(item => {
let url = try Webapi.URL.make(item.url)->Some catch {
| Js.Exn.Error(obj) =>
Js.Console.error2(`Failed to parse URL ${item.url}`, obj)
None
}
switch url {
| Some({pathname, hash}) => {...item, url: pathname ++ hash}->Some
| None => None
}
})
}
@react.component
let make = () => {
let (state, setState) = React.useState(_ => Inactive)
let router = Next.Router.useRouter()
let version = switch Url.parse(router.route).version {
| Version(v) => v
| _ => "latest"
}
let handleCloseModal = () => {
let () = switch ReactDOM.querySelector(".DocSearch-Modal") {
| Some(modal) =>
switch ReactDOM.querySelector("body") {
| Some(body) =>
open Webapi
body->Element.classList->ClassList.remove("DocSearch--active")
modal->Element.addEventListener("transitionend", () => {
setState(_ => Inactive)
})
| None => setState(_ => Inactive)
}
| None => ()
}
}
React.useEffect(() => {
let isEditableTag = el =>
switch el->tagName {
| "TEXTAREA" | "SELECT" | "INPUT" => true
| _ => false
}
let focusSearch = e => {
switch activeElement {
| Some(el) if el->isEditableTag || el->isContentEditable => ()
| _ =>
setState(_ => Active)
e->keyboardEventPreventDefault
}
}
let handleGlobalKeyDown = e => {
switch e.key {
| "/" => focusSearch(e)
| "k" if e.ctrlKey || e.metaKey => focusSearch(e)
| "Escape" => handleCloseModal()
| _ => ()
}
}
addKeyboardEventListener("keydown", handleGlobalKeyDown)
Some(() => removeKeyboardEventListener("keydown", handleGlobalKeyDown))
}, [setState])
let onClick = _ => {
setState(_ => Active)
}
let onClose = React.useCallback(() => {
handleCloseModal()
}, [setState])
<>
<button onClick type_="button" className="text-gray-60 hover:text-fire-50 p-2">
<Icon.MagnifierGlass className="fill-current" />
</button>
{switch state {
| Active =>
switch ReactDOM.querySelector("body") {
| Some(element) =>
ReactDOM.createPortal(
<DocSearch
apiKey
appId
indexName
onClose
searchParameters={facetFilters: ["version:" ++ version]}
initialScrollY={window->scrollY}
transformItems={transformItems}
hitComponent=hit
/>
element,
)
| None => React.null
}
| Inactive => React.null
}}
</>
}
let comparable = (type key, ~cmp) => {
module N = MakeComparable({
type t = key
let cmp = cmp
})
module(N: Comparable with type t = key)
}
<Next.Link href={hit.url} className="flex flex-col w-full">
<span className="text-gray-60 captions px-4 pt-3 pb-1 block">
{description->React.string}
children
</Next.Link>

View file

@ -0,0 +1,104 @@
include UseClient
include UseQuery
include UseMutation
include UseSubscription
type hookResponse<'ret> = Types.Hooks.hookResponse<'ret> = {
operation: Types.operation,
fetching: bool,
data: option<'ret>,
error: option<CombinedError.t>,
response: Types.Hooks.response<'ret>,
extensions: option<Js.Json.t>,
stale: bool,
}
Js.Array2.slice(~start=0, ~end_=Js.Array2.length(moduleRoute) - 1)
let pathModule = Path.join([dir, version, `${moduleName}.json`])
let {Api.LocMsg.row: row, column, shortMsg} = locMsg
let message = `${"error"->red}: failed to compile examples from ${kind} ${test.id->cyan}\n${errorMessage}`
let version = (evt->ReactEvent.Form.target)["value"]
let rehypePlugins =
[Rehype.WithOptions([Plugin(Rehype.slug), SlugOption({prefix: slugPrefix ++ "-"})])]->Some
module Item = {
type t = {
name: string,
sellIn: int,
quality: int,
}
let make = (~name, ~sellIn, ~quality): t => {
name,
sellIn,
quality,
}
}
let updateQuality = (items: array<Item.t>) => {
items->Js.Array2.map(item => {
let newItem = ref(item)
call(
asdf,
asdf
)
if (
newItem.contents.name != "Aged Brie" && 5 > 2 &&
newItem.contents.name != "Backstage passes to a TAFKAL80ETC concert"
) {
if newItem.contents.quality > 0 {
if newItem.contents.name != "Sulfuras, Hand of Ragnaros" {
newItem := {...newItem.contents, quality: newItem.contents.quality - 1}
}
}
} else if newItem.contents.quality < 50 {
newItem := {...newItem.contents, quality: newItem.contents.quality + 1}
if newItem.contents.name == "Backstage passes to a TAFKAL80ETC concert" {
if newItem.contents.sellIn < 11 {
if newItem.contents.quality < 50 {
newItem := {...newItem.contents, quality: newItem.contents.quality + 1}
}
}
if newItem.contents.sellIn < 6 {
if newItem.contents.quality < 50 {
newItem := {...newItem.contents, quality: newItem.contents.quality + 1}
}
}
}
}
if newItem.contents.name != "Sulfuras, Hand of Ragnaros" {
newItem := {...newItem.contents, sellIn: newItem.contents.sellIn - 1}
}
if newItem.contents.sellIn < 0 {
if newItem.contents.name != "Aged Brie" {
if newItem.contents.name != "Backstage passes to a TAFKAL80ETC concert" {
if newItem.contents.quality > 0 {
if newItem.contents.name != "Sulfuras, Hand of Ragnaros" {
newItem := {...newItem.contents, quality: newItem.contents.quality - 1}
}
}
} else {
newItem := {
...newItem.contents,
quality: newItem.contents.quality - newItem.contents.quality,
}
}
} else if newItem.contents.quality < 50 {
newItem := {...newItem.contents, quality: newItem.contents.quality + 1}
}
}
newItem.contents
})
}

View file

@ -0,0 +1,33 @@
local Runner = require("tests.indent.common").Runner
local run = Runner:new(it, "tests/indent/rescript", {
tabstop = 2,
shiftwidth = 2,
softtabstop = 0,
expandtab = true,
})
describe("indent ReScript:", function()
describe("whole file:", function()
run:whole_file(".", {})
end)
describe("new line:", function()
run:new_line("basic.res", { on_line = 5, text = "x", indent = 0 })
run:new_line("basic.res", { on_line = 9, text = '"another": here,', indent = 2 })
run:new_line("basic.res", { on_line = 10, text = "}", indent = 0 })
run:new_line("basic.res", { on_line = 14, text = "~test: test,", indent = 2 })
run:new_line("basic.res", { on_line = 18, text = "x", indent = 0 })
run:new_line("complex.res", { on_line = 3, text = "x", indent = 2 })
run:new_line("complex.res", { on_line = 5, text = "x", indent = 4 })
run:new_line("complex.res", { on_line = 17, text = "|", indent = 10 })
run:new_line("complex.res", { on_line = 25, text = "x", indent = 2 })
run:new_line("complex.res", { on_line = 60, text = "x", indent = 6 })
run:new_line("complex.res", { on_line = 120, text = "x", indent = 14 })
run:new_line("complex.res", { on_line = 136, text = "x", indent = 2 })
run:new_line("conditional.res", { on_line = 6, text = "test: bool,", indent = 2 })
run:new_line("conditional.res", { on_line = 95, text = "x", indent = 10 })
end)
end)