Skip to content

Complete lowercase JSX elements #719

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
Feb 1, 2023
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 @@ -29,6 +29,7 @@
- Add way to autocomplete an exhaustive switch statement for identifiers. Example: an identifier that's a variant can have a switch autoinserted matching all variant cases. https://github.com/rescript-lang/rescript-vscode/pull/699
- Support typed expression completion for lowercase (builtin) JSX tags. https://github.com/rescript-lang/rescript-vscode/pull/702
- Support typed expression completion driven by type annotations. https://github.com/rescript-lang/rescript-vscode/pull/711
- Completion for lowercase JSX elements, treating them like HTML elements. https://github.com/rescript-lang/rescript-vscode/pull/719

#### :nail_care: Polish

Expand Down
23 changes: 21 additions & 2 deletions analysis/src/CompletionBackEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,7 @@ let getEnvWithOpens ~scope ~(env : QueryEnv.t) ~package
| None -> None
| Some env -> ResolvePath.resolvePath ~env ~package ~path))

let detail name (kind : Completion.kind) =
let kindToDetail name (kind : Completion.kind) =
match kind with
| Type {decl} -> decl |> Shared.declToString name
| Value typ -> typ |> Shared.typeToString
Expand Down Expand Up @@ -568,11 +568,17 @@ let completionToItem
insertText;
insertTextFormat;
filterText;
detail;
} =
let item =
mkItem ~name
~kind:(Completion.kindToInt kind)
~deprecated ~detail:(detail name kind) ~docstring
~deprecated
~detail:
(match detail with
| None -> kindToDetail name kind
| Some detail -> detail)
~docstring
in
if !Cfg.supportsSnippets then
{item with sortText; insertText; insertTextFormat; filterText}
Expand Down Expand Up @@ -1526,3 +1532,16 @@ let rec processCompletable ~debug ~full ~scope ~env ~pos ~forHover
| _ -> [c])
| _ -> [c])
|> List.flatten
| ChtmlElement {prefix} ->
CompletionJsx.htmlElements
|> List.filter_map (fun (elementName, description, deprecated) ->
if Utils.startsWith elementName prefix then
let name = "<" ^ elementName ^ ">" in
Some
(Completion.create name ~kind:(Label name) ~detail:description ~env
~docstring:[description] ~insertText:elementName
?deprecated:
(match deprecated with
| true -> Some "true"
| false -> None))
else None)
6 changes: 5 additions & 1 deletion analysis/src/CompletionFrontEnd.ml
Original file line number Diff line number Diff line change
Expand Up @@ -735,7 +735,11 @@ let completionWithParser1 ~currentFile ~debug ~offset ~path ~posCursor ~text =
in
if jsxCompletable <> None then setResultOpt jsxCompletable
else if compName.loc |> Loc.hasPos ~pos:posBeforeCursor then
setResult (Cpath (CPId (compNamePath, Module)))
setResult
(match compNamePath with
| [prefix] when Char.lowercase_ascii prefix.[0] = prefix.[0] ->
ChtmlElement {prefix}
| _ -> Cpath (CPId (compNamePath, Module)))
| Pexp_apply
( {pexp_desc = Pexp_ident {txt = Lident "|."}},
[
Expand Down
207 changes: 207 additions & 0 deletions analysis/src/CompletionJsx.ml
Original file line number Diff line number Diff line change
Expand Up @@ -487,6 +487,213 @@ let domLabels =
("suppressContentEditableWarning", bool);
]

(* List and explanations taken from
https://www.tutorialrepublic.com/html-reference/html5-tags.php. *)
let htmlElements =
[
("a", "Defines a hyperlink.", false);
("abbr", "Defines an abbreviated form of a longer word or phrase.", false);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this compare with the complete list currently supported? A subset? (E.g. aria).

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmmm, not sure I follow what you mean..?

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I guess this is a selection of all the possible ones accepted by the type checker.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I think the type checker would accept anything, I think the JSX tag name is just turned into a string by the PPX. So this is strictly about providing completion/information for something that we don't really have (or care) about type information for right now. So it's strictly an addon/extra help.

("acronym", "Defines an acronym. Use <abbr> instead.", true);
("address", "Specifies the author's contact information.", false);
( "applet",
"Embeds a Java applet (mini Java applications) on the page. Use <object> \
instead.",
true );
("area", "Defines a specific area within an image map.", false);
("article", "Defines an article.", false);
("aside", "Defines some content loosely related to the page content.", false);
("audio", "Embeds a sound, or an audio stream in an HTML document.", false);
("b", "Displays text in a bold style.", false);
("base", "Defines the base URL for all relative URLs in a document.", false);
("basefont", "Specifies the base font for a page. Use CSS instead.", true);
( "bdi",
"Represents text that is isolated from its surrounding for the purposes \
of bidirectional text formatting.",
false );
("bdo", "Overrides the current text direction.", false);
("big", "Displays text in a large size. Use CSS instead.", true);
( "blockquote",
"Represents a section that is quoted from another source.",
false );
("body", "Defines the document's body.", false);
("br", "Produces a single line break.", false);
("button", "Creates a clickable button.", false);
( "canvas",
"Defines a region in the document, which can be used to draw graphics on \
the fly via scripting (usually JavaScript).",
false );
("caption", "Defines the caption or title of the table.", false);
("center", "Align contents in the center. Use CSS instead.", true);
("cite", "Indicates a citation or reference to another source.", false);
("code", "Specifies text as computer code.", false);
( "col",
"Defines attribute values for one or more columns in a table.",
false );
("colgroup", "Specifies attributes for multiple columns in a table.", false);
( "data",
"Links a piece of content with a machine-readable translation.",
false );
( "datalist",
"Represents a set of pre-defined options for an <input> element.",
false );
( "dd",
"Specifies a description, or value for the term (<dt>) in a description \
list (<dl>).",
false );
("del", "Represents text that has been deleted from the document.", false);
( "details",
"Represents a widget from which the user can obtain additional \
information or controls on-demand.",
false );
("dfn", "Specifies a definition.", false);
("dialog", "Defines a dialog box or subwindow.", false);
("dir", "Defines a directory list. Use <ul> instead.", true);
("div", "Specifies a division or a section in a document.", false);
("dl", "Defines a description list.", false);
("dt", "Defines a term (an item) in a description list.", false);
("em", "Defines emphasized text.", false);
( "embed",
"Embeds external application, typically multimedia content like audio or \
video into an HTML document.",
false );
("fieldset", "Specifies a set of related form fields.", false);
("figcaption", "Defines a caption or legend for a figure.", false);
("figure", "Represents a figure illustrated as part of the document.", false);
("font", "Defines font, color, and size for text. Use CSS instead.", true);
("footer", "Represents the footer of a document or a section.", false);
("form", "Defines an HTML form for user input.", false);
("frame", "Defines a single frame within a frameset.", true);
("frameset", "Defines a collection of frames or other frameset.", true);
( "head",
"Defines the head portion of the document that contains information \
about the document such as title.",
false );
("header", "Represents the header of a document or a section.", false);
("hgroup", "Defines a group of headings.", false);
("h1", "Defines HTML headings.", false);
("h2", "Defines HTML headings.", false);
("h3", "Defines HTML headings.", false);
("h4", "Defines HTML headings.", false);
("h5", "Defines HTML headings.", false);
("h6", "Defines HTML headings.", false);
("hr", "Produce a horizontal line.", false);
("html", "Defines the root of an HTML document.", false);
("i", "Displays text in an italic style.", false);
("iframe", "Displays a URL in an inline frame.", false);
("img", "Represents an image.", false);
("input", "Defines an input control.", false);
( "ins",
"Defines a block of text that has been inserted into a document.",
false );
("kbd", "Specifies text as keyboard input.", false);
( "keygen",
"Represents a control for generating a public-private key pair.",
false );
("label", "Defines a label for an <input> control.", false);
("legend", "Defines a caption for a <fieldset> element.", false);
("li", "Defines a list item.", false);
( "link",
"Defines the relationship between the current document and an external \
resource.",
false );
("main", "Represents the main or dominant content of the document.", false);
("map", "Defines a client-side image-map.", false);
("mark", "Represents text highlighted for reference purposes.", false);
("menu", "Represents a list of commands.", false);
( "menuitem",
"Defines a list (or menuitem) of commands that a user can perform.",
false );
("meta", "Provides structured metadata about the document content.", false);
("meter", "Represents a scalar measurement within a known range.", false);
("nav", "Defines a section of navigation links.", false);
( "noframes",
"Defines an alternate content that displays in browsers that do not \
support frames.",
true );
( "noscript",
"Defines alternative content to display when the browser doesn't support \
scripting.",
false );
("object", "Defines an embedded object.", false);
("ol", "Defines an ordered list.", false);
( "optgroup",
"Defines a group of related options in a selection list.",
false );
("option", "Defines an option in a selection list.", false);
("output", "Represents the result of a calculation.", false);
("p", "Defines a paragraph.", false);
("param", "Defines a parameter for an object or applet element.", false);
("picture", "Defines a container for multiple image sources.", false);
("pre", "Defines a block of preformatted text.", false);
("progress", "Represents the completion progress of a task.", false);
("q", "Defines a short inline quotation.", false);
( "rp",
"Provides fall-back parenthesis for browsers that that don't support \
ruby annotations.",
false );
( "rt",
"Defines the pronunciation of character presented in a ruby annotations.",
false );
("ruby", "Represents a ruby annotation.", false);
( "s",
"Represents contents that are no longer accurate or no longer relevant.",
false );
("samp", "Specifies text as sample output from a computer program.", false);
( "script",
"Places script in the document for client-side processing.",
false );
( "section",
"Defines a section of a document, such as header, footer etc.",
false );
("select", "Defines a selection list within a form.", false);
("small", "Displays text in a smaller size.", false);
( "source",
"Defines alternative media resources for the media elements like <audio> \
or <video>.",
false );
("span", "Defines an inline styleless section in a document.", false);
("strike", "Displays text in strikethrough style.", true);
("strong", "Indicate strongly emphasized text.", false);
( "style",
"Inserts style information (commonly CSS) into the head of a document.",
false );
("sub", "Defines subscripted text.", false);
("summary", "Defines a summary for the <details> element.", false);
("sup", "Defines superscripted text.", false);
( "svg",
"Embed SVG (Scalable Vector Graphics) content in an HTML document.",
false );
("table", "Defines a data table.", false);
( "tbody",
"Groups a set of rows defining the main body of the table data.",
false );
("td", "Defines a cell in a table.", false);
( "template",
"Defines the fragments of HTML that should be hidden when the page is \
loaded, but can be cloned and inserted in the document by JavaScript.",
false );
("textarea", "Defines a multi-line text input control (text area).", false);
( "tfoot",
"Groups a set of rows summarizing the columns of the table.",
false );
("th", "Defines a header cell in a table.", false);
( "thead",
"Groups a set of rows that describes the column labels of a table.",
false );
("time", "Represents a time and/or date.", false);
("title", "Defines a title for the document.", false);
("tr", "Defines a row of cells in a table.", false);
( "track",
"Defines text tracks for the media elements like <audio> or <video>.",
false );
("tt", "Displays text in a teletype style.", true);
("u", "Displays text with an underline.", false);
("ul", "Defines an unordered list.", false);
("var", "Defines a variable.", false);
("video", "Embeds video content in an HTML document.", false);
("wbr", "Represents a line break opportunity.", false);
]

let getJsxLabels ~componentPath ~findTypeOfValue ~package =
match componentPath @ ["make"] |> findTypeOfValue with
| Some (typ, make_env) ->
Expand Down
12 changes: 9 additions & 3 deletions analysis/src/SharedTypes.ml
Original file line number Diff line number Diff line change
Expand Up @@ -348,19 +348,22 @@ module Completion = struct
deprecated: string option;
docstring: string list;
kind: kind;
detail: string option;
}

let create ~kind ~env ?(docstring = []) ?filterText name =
let create ~kind ~env ?(docstring = []) ?filterText ?insertText ?deprecated
?detail name =
{
name;
env;
deprecated = None;
deprecated;
docstring;
kind;
sortText = None;
insertText = None;
insertText;
insertTextFormat = None;
filterText;
detail;
}

let createWithSnippet ~name ?insertText ~kind ~env ?sortText ?filterText
Expand All @@ -375,6 +378,7 @@ module Completion = struct
insertText;
insertTextFormat = Some Protocol.Snippet;
filterText;
detail = None;
}

(* https://microsoft.github.io/language-server-protocol/specifications/specification-current/#textDocument_completion *)
Expand Down Expand Up @@ -654,6 +658,7 @@ module Completable = struct
fallback: t option;
}
| CexhaustiveSwitch of {contextPath: contextPath; exprLoc: Location.t}
| ChtmlElement of {prefix: string}

let toString =
let completionContextToString = function
Expand Down Expand Up @@ -740,6 +745,7 @@ module Completable = struct
|> String.concat ", "))
| CexhaustiveSwitch {contextPath} ->
"CexhaustiveSwitch " ^ contextPathToString contextPath
| ChtmlElement {prefix} -> "ChtmlElement <" ^ prefix
end

module CursorPosition = struct
Expand Down
2 changes: 2 additions & 0 deletions analysis/tests/src/CompletionJsx.res
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ module SomeComponent = {
// ^com
// {someArr->a}
// ^com
// <di
// ^com
</div>
}
}
Expand Down
Loading