From 63b6d9829e609310e6aca7a7d8f848b34ea8116b Mon Sep 17 00:00:00 2001 From: Iwan Date: Wed, 16 Dec 2020 10:41:52 +0100 Subject: [PATCH 1/7] Implement draft new syntax for importing values from JavaScript modules --- .depend | 3 +- src/res_core.ml | 338 +++++++++++++----- src/res_grammar.ml | 17 +- src/res_js_ffi.ml | 116 ------ src/res_parsetree_viewer.ml | 60 ++-- src/res_parsetree_viewer.mli | 15 +- src/res_printer.ml | 225 +++++++++--- .../ffi/__snapshots__/parse.spec.js.snap | 9 +- .../__snapshots__/parse.spec.js.snap | 30 +- tests/parsing/grammar/structure/jsFfiSugar.js | 13 - .../react/__snapshots__/render.spec.js.snap | 8 +- .../ffi/__snapshots__/render.spec.js.snap | 6 +- tests/printer/ffi/import.res | 2 - .../other/__snapshots__/render.spec.js.snap | 20 +- .../__snapshots__/render.spec.js.snap | 3 +- 15 files changed, 509 insertions(+), 356 deletions(-) delete mode 100644 src/res_js_ffi.ml diff --git a/.depend b/.depend index c11eee4a..8fcb3f72 100644 --- a/.depend +++ b/.depend @@ -14,7 +14,7 @@ src/res_comment.cmi : src/res_comments_table.cmx : src/res_parsetree_viewer.cmx src/res_doc.cmx \ src/res_comment.cmx src/res_core.cmx : src/res_token.cmx src/res_scanner.cmx src/res_printer.cmx \ - src/res_parser.cmx src/res_js_ffi.cmx src/res_grammar.cmx src/res_doc.cmx \ + src/res_parser.cmx src/res_grammar.cmx src/res_doc.cmx \ src/res_diagnostics.cmx src/res_comments_table.cmx \ src/res_character_codes.cmx src/res_core.cmi src/res_core.cmi : src/res_parser.cmi @@ -41,7 +41,6 @@ src/res_driver_reason_binary.cmi : src/res_token.cmx src/res_driver.cmi src/res_grammar.cmx : src/res_token.cmx src/res_io.cmx : src/res_io.cmi src/res_io.cmi : -src/res_js_ffi.cmx : src/res_minibuffer.cmx : src/res_minibuffer.cmi src/res_minibuffer.cmi : src/res_multi_printer.cmx : src/res_printer.cmx src/res_io.cmx \ diff --git a/src/res_core.ml b/src/res_core.ml index 051f524f..50ecf801 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -5,7 +5,7 @@ module Diagnostics = Res_diagnostics module CommentTable = Res_comments_table module ResPrinter = Res_printer module Scanner = Res_scanner -module JsFfi = Res_js_ffi +(* module JsFfi = Res_js_ffi *) module Parser = Res_parser let mkLoc startLoc endLoc = Location.{ @@ -133,6 +133,18 @@ type recordPatternItem = | PatUnderscore | PatField of (Ast_helper.lid * Parsetree.pattern) +type jsImportClauseDescription = { + attrs: Parsetree.attributes; + name: string Location.loc; + alias: string; + typ: Parsetree.core_type; +} + +type jsImportClause = + | DefaultImport of jsImportClauseDescription + | NameSpaceImport of jsImportClauseDescription + | NamedImport of jsImportClauseDescription + type context = | OrdinaryExpr | TernaryTrueBranchExpr @@ -604,31 +616,31 @@ let parseModuleLongIdent ~lowercase p = moduleIdent (* `window.location` or `Math` or `Foo.Bar` *) -let parseIdentPath p = - let rec loop p acc = - match p.Parser.token with - | Uident ident | Lident ident -> - Parser.next p; - let lident = (Longident.Ldot (acc, ident)) in - begin match p.Parser.token with - | Dot -> - Parser.next p; - loop p lident - | _ -> lident - end - | _t -> acc - in - match p.Parser.token with - | Lident ident | Uident ident -> - Parser.next p; - begin match p.Parser.token with - | Dot -> - Parser.next p; - loop p (Longident.Lident ident) - | _ -> Longident.Lident ident - end - | _ -> - Longident.Lident "_" +(* let parseIdentPath p = *) + (* let rec loop p acc = *) + (* match p.Parser.token with *) + (* | Uident ident | Lident ident -> *) + (* Parser.next p; *) + (* let lident = (Longident.Ldot (acc, ident)) in *) + (* begin match p.Parser.token with *) + (* | Dot -> *) + (* Parser.next p; *) + (* loop p lident *) + (* | _ -> lident *) + (* end *) + (* | _t -> acc *) + (* in *) + (* match p.Parser.token with *) + (* | Lident ident | Uident ident -> *) + (* Parser.next p; *) + (* begin match p.Parser.token with *) + (* | Dot -> *) + (* Parser.next p; *) + (* loop p (Longident.Lident ident) *) + (* | _ -> Longident.Lident ident *) + (* end *) + (* | _ -> *) + (* Longident.Lident "_" *) let verifyJsxOpeningClosingName p nameExpr = let closing = match p.Parser.token with @@ -5145,10 +5157,9 @@ and parseStructureItemRegion p = let loc = mkLoc startPos p.prevEndPos in Some (Ast_helper.Str.primitive ~loc externalDef) | Import -> - let importDescr = parseJsImport ~startPos ~attrs p in + let structureItem = parseJsImportDeclaration ~startPos ~attrs p in parseNewlineOrSemicolonStructure p; let loc = mkLoc startPos p.prevEndPos in - let structureItem = JsFfi.toParsetree importDescr in Some {structureItem with pstr_loc = loc} | Exception -> let exceptionDef = parseExceptionDef ~attrs p in @@ -5202,20 +5213,181 @@ and parseStructureItemRegion p = None end -and parseJsImport ~startPos ~attrs p = +(* import ImportClause FromClause *) +and parseJsImportDeclaration ~startPos ~attrs p = Parser.expect Token.Import p; - let importSpec = match p.Parser.token with - | Token.Lident _ | Token.At -> - let decl = match parseJsFfiDeclaration p with - | Some decl -> decl - | None -> assert false - in - JsFfi.Default decl - | _ -> JsFfi.Spec(parseJsFfiDeclarations p) - in - let scope = parseJsFfiScope p in + let importJsClause = parseJsImportClause p in + let fromClause = parseFromClause p in let loc = mkLoc startPos p.prevEndPos in - JsFfi.importDescr ~attrs ~importSpec ~scope ~loc + let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in + importJsClause + |> List.map (fun jsImport -> + let valueDescription = match jsImport with + (* import schoolName: string from '/modules/school.js' *) + | DefaultImport {attrs; name; alias; typ} -> + (* module("/modules/school.js") external schoolName: string = "default" *) + Ast_helper.Val.mk ~loc:name.loc + ~attrs:(fromClause::attrs) ~prim:[alias] name typ + + (* import * as leftPad: (string, int) => string from "leftPad" *) + | NameSpaceImport {attrs; name; typ} -> + let ({Asttypes.txt = jsModuleName}, _ ) = fromClause in + let moduleAttribute = (Location.mknoloc "module", Parsetree.PStr []) in + (* @module external leftPad: (string, int) => string = "leftPad" *) + Ast_helper.Val.mk ~loc:name.loc + ~attrs:(moduleAttribute::attrs) ~prim:[jsModuleName] name typ + + (* import {dirname: string => string} from "path" *) + | NamedImport {attrs; name; alias; typ} -> + (* module("path") external dirname: (string, string) => string = "dirname" *) + Ast_helper.Val.mk ~loc:name.loc + ~attrs:(fromClause::attrs) ~prim:[alias] name typ + in + (* TODO: nicer loc on primitive? *) + Ast_helper.Str.primitive ~loc:valueDescription.pval_loc valueDescription + ) + |> Ast_helper.Mod.structure ~loc + |> Ast_helper.Incl.mk ~attrs ~loc + |> Ast_helper.Str.include_ ~loc + +(* + * ImportClause ::= + * ImportedDefaultBinding + * NameSpaceImport + * NamedImports + * ImportedDefaultBinding , NameSpaceImport + * ImportedDefaultBinding , NamedImports + *) +and parseJsImportClause p = + let startPos = p.Parser.startPos in + match p.token with + (* ImportedDefaultBinding *) + | Token.Lident ident | String ident -> + let identLoc = mkLoc startPos p.endPos in + Parser.next p; + Parser.expect Colon p; + let typ = parseTypExpr p in + let default = DefaultImport { + attrs = []; + name = Location.mkloc ident identLoc; + alias = "default"; + typ + } in + begin match p.token with + | Comma -> + Parser.next p; + begin match p.token with + | Lbrace -> + default::(parseJsNamedImports p) + | Asterisk -> + [default; parseNameSpaceImport p] + | _ -> [default] + end + | _ -> + [default] + end + | Asterisk -> + [parseNameSpaceImport p] + (* NamedImports *) + | Lbrace -> + parseJsNamedImports p + | _ -> + assert false + +(* + * NameSpaceImport : + * * as ImportedBinding + *) +and parseNameSpaceImport p = + let startPos = p.Parser.startPos in + Parser.expect Asterisk p; + Parser.expect As p; + match p.Parser.token with + | Token.Lident ident | String ident -> + let identLoc = mkLoc startPos p.endPos in + Parser.next p; + Parser.expect Colon p; + let typ = parseTypExpr p in + NameSpaceImport { + attrs = []; + name = Location.mkloc ident identLoc; + alias = ""; + typ + } + | _ -> + assert false + +(* + * NamedImports : + * { } + * { ImportsListItem } + * { ImportsListItems , } + *) +and parseJsNamedImports p = + Parser.expect Lbrace p; + let namedImports = parseCommaDelimitedRegion + ~grammar:Grammar.JsNamedImports + ~closing:Rbrace + ~f:parseJsImportsListItemRegion + p + in + Parser.expect Rbrace p; + namedImports + +(* + * ImportsListItem : + * ImportSpecifier + * ImportsList , ImportSpecifier + * + * ImportSpecifier : + * ImportedBinding : typexpr + * IdentifierName as ImportedBinding : typexpr + *) +and parseJsImportsListItemRegion p = + let startPos = p.Parser.startPos in + let attrs = parseAttributes p in + match p.token with + | Token.Lident ident | String ident -> + let identLoc = mkLoc startPos p.endPos in + Parser.next p; + let name = match p.Parser.token with + | As -> + Parser.next p; + let (ident, loc) = parseLident p in + Location.mkloc ident loc + | _ -> + Location.mkloc ident identLoc + in + Parser.expect Colon p; + let typ = parseTypExpr p in + (* TODO: decent type *) + Some (NamedImport {attrs; name; alias = ident; typ}) + | _ -> + None + +(* + * FromClause ::= + * from ModuleSpecifier + *) +and parseFromClause p = + let startPos = p.Parser.startPos in + Parser.expect (Lident "from") p; (* TODO: token *) + (* @module("/modules/school.js") *) + match p.token with + | String jsModuleSpecifier -> + let loc = mkLoc startPos p.endPos in + Parser.next p; + ( + Location.mkloc "module" loc, + Parsetree.PStr [ + Ast_helper.Str.eval ~loc ( + Ast_helper.Exp.constant ~loc (Parsetree.Pconst_string (jsModuleSpecifier, None)) + ) + ] + ) + | _ -> + (* TODO: error *) + (Location.mknoloc "module" , Parsetree.PStr []) and parseJsExport ~attrs p = let exportStart = p.Parser.startPos in @@ -5256,49 +5428,49 @@ and parseSignJsExport ~attrs p = let loc = mkLoc exportStart p.prevEndPos in Ast_helper.Sig.value valueDesc ~loc -and parseJsFfiScope p = - match p.Parser.token with - | Token.Lident "from" -> - Parser.next p; - begin match p.token with - | String s -> Parser.next p; JsFfi.Module s - | Uident _ | Lident _ -> - let value = parseIdentPath p in - JsFfi.Scope value - | _ -> JsFfi.Global - end - | _ -> JsFfi.Global - -and parseJsFfiDeclarations p = - Parser.expect Token.Lbrace p; - let decls = parseCommaDelimitedRegion - ~grammar:Grammar.JsFfiImport - ~closing:Rbrace - ~f:parseJsFfiDeclaration - p - in - Parser.expect Rbrace p; - decls - -and parseJsFfiDeclaration p = - let startPos = p.Parser.startPos in - let attrs = parseAttributes p in - match p.Parser.token with - | Lident _ -> - let (ident, _) = parseLident p in - let alias = match p.token with - | As -> - Parser.next p; - let (ident, _) = parseLident p in - ident - | _ -> - ident - in - Parser.expect Token.Colon p; - let typ = parseTypExpr p in - let loc = mkLoc startPos p.prevEndPos in - Some (JsFfi.decl ~loc ~alias ~attrs ~name:ident ~typ) - | _ -> None +(* and parseJsFfiScope p = *) + (* match p.Parser.token with *) + (* | Token.Lident "from" -> *) + (* Parser.next p; *) + (* begin match p.token with *) + (* | String s -> Parser.next p; JsFfi.Module s *) + (* | Uident _ | Lident _ -> *) + (* let value = parseIdentPath p in *) + (* JsFfi.Scope value *) + (* | _ -> JsFfi.Global *) + (* end *) + (* | _ -> JsFfi.Global *) + +(* and parseJsFfiDeclarations p = *) + (* Parser.expect Token.Lbrace p; *) + (* let decls = parseCommaDelimitedRegion *) + (* ~grammar:Grammar.JsFfiImport *) + (* ~closing:Rbrace *) + (* ~f:parseJsFfiDeclaration *) + (* p *) + (* in *) + (* Parser.expect Rbrace p; *) + (* decls *) + +(* and parseJsFfiDeclaration p = *) + (* let startPos = p.Parser.startPos in *) + (* let attrs = parseAttributes p in *) + (* match p.Parser.token with *) + (* | Lident _ -> *) + (* let (ident, _) = parseLident p in *) + (* let alias = match p.token with *) + (* | As -> *) + (* Parser.next p; *) + (* let (ident, _) = parseLident p in *) + (* ident *) + (* | _ -> *) + (* ident *) + (* in *) + (* Parser.expect Token.Colon p; *) + (* let typ = parseTypExpr p in *) + (* let loc = mkLoc startPos p.prevEndPos in *) + (* Some (JsFfi.decl ~loc ~alias ~attrs ~name:ident ~typ) *) + (* | _ -> None *) (* include-statement ::= include module-expr *) and parseIncludeStatement ~attrs p = diff --git a/src/res_grammar.ml b/src/res_grammar.ml index 11e2cbc0..1560fb8e 100644 --- a/src/res_grammar.ml +++ b/src/res_grammar.ml @@ -56,8 +56,8 @@ type t = | Primitive | AtomicTypExpr | ListExpr - | JsFfiImport | Pattern + | JsNamedImports let toString = function | OpenDescription -> "an open description" @@ -114,10 +114,10 @@ let toString = function | AtomicTypExpr -> "a type" | ListExpr -> "an ocaml list expr" | PackageConstraint -> "a package constraint" - | JsFfiImport -> "js ffi import" | JsxChild -> "jsx child" | Pattern -> "pattern" | ExprFor -> "a for expression" + | JsNamedImports -> "js named imports" let isSignatureItemStart = function | Token.At @@ -293,10 +293,6 @@ let isAttributeStart = function | Token.At -> true | _ -> false -let isJsFfiImportStart = function - | Token.Lident _ | At -> true - | _ -> false - let isJsxChildStart = isAtomicExprStart let isBlockExprStart = function @@ -307,6 +303,10 @@ let isBlockExprStart = function | LessThan | Backtick | Try | Underscore -> true | _ -> false +let isJsImportsListItemStart token = match token with + | Token.At | Lident _ | String _ -> true + | _ -> false + let isListElement grammar token = match grammar with | ExprList -> token = Token.DotDotDot || isExprStart token @@ -335,7 +335,7 @@ let isListElement grammar token = | ConstructorDeclaration -> token = Bar | Primitive -> begin match token with Token.String _ -> true | _ -> false end | JsxAttribute -> isJsxAttributeStart token - | JsFfiImport -> isJsFfiImportStart token + | JsNamedImports -> isJsImportsListItemStart token | _ -> false let isListTerminator grammar token = @@ -353,7 +353,6 @@ let isListTerminator grammar token = | TypeParams, Rparen | ParameterList, (EqualGreater | Lbrace) | JsxAttribute, (Forwardslash | GreaterThan) - | JsFfiImport, Rbrace | StringFieldDeclarations, Rbrace -> true | Attribute, token when token <> At -> true @@ -362,7 +361,7 @@ let isListTerminator grammar token = | ConstructorDeclaration, token when token <> Bar -> true | Primitive, Semicolon -> true | Primitive, token when isStructureItemStart token -> true - + | JsNamedImports, Rbrace -> true | _ -> false let isPartOfList grammar token = diff --git a/src/res_js_ffi.ml b/src/res_js_ffi.ml deleted file mode 100644 index a2716111..00000000 --- a/src/res_js_ffi.ml +++ /dev/null @@ -1,116 +0,0 @@ -(* AST for js externals *) -type scope = - | Global - | Module of string (* bs.module("path") *) - | Scope of Longident.t (* bs.scope(/"window", "location"/) *) - -type label_declaration = { - jld_attributes: Parsetree.attributes; [@live] - jld_name: string; - jld_alias: string; - jld_type: Parsetree.core_type; - jld_loc: Location.t -} - -type importSpec = - | Default of label_declaration - | Spec of label_declaration list - -type import_description = { - jid_loc: Location.t; - jid_spec: importSpec; - jid_scope: scope; - jid_attributes: Parsetree.attributes; -} - -let decl ~attrs ~loc ~name ~alias ~typ = { - jld_loc = loc; - jld_attributes = attrs; - jld_name = name; - jld_alias = alias; - jld_type = typ -} - -let importDescr ~attrs ~scope ~importSpec ~loc = { - jid_loc = loc; - jid_spec = importSpec; - jid_scope = scope; - jid_attributes = attrs; -} - -let toParsetree importDescr = - let bsVal = (Location.mknoloc "bs.val", Parsetree.PStr []) in - let attrs = match importDescr.jid_scope with - | Global -> [bsVal] - (* @genType.import("./MyMath"), - * @genType.import(/"./MyMath", "default"/) *) - | Module s -> - let structure = [ - Parsetree.Pconst_string (s, None) - |> Ast_helper.Exp.constant - |> Ast_helper.Str.eval - ] in - let genType = (Location.mknoloc "genType.import", Parsetree.PStr structure) in - [genType] - | Scope longident -> - let structureItem = - let expr = match Longident.flatten longident |> List.map (fun s -> - Ast_helper.Exp.constant (Parsetree.Pconst_string (s, None)) - ) with - | [expr] -> expr - | [] as exprs | (_ as exprs) -> exprs |> Ast_helper.Exp.tuple - in - Ast_helper.Str.eval expr - in - let bsScope = ( - Location.mknoloc "bs.scope", - Parsetree. PStr [structureItem] - ) in - [bsVal; bsScope] - in - let valueDescrs = match importDescr.jid_spec with - | Default decl -> - let prim = [decl.jld_name] in - let allAttrs = - List.concat [attrs; importDescr.jid_attributes] - |> List.map (fun attr -> match attr with - | ( - {Location.txt = "genType.import"} as id, - Parsetree.PStr [{pstr_desc = Parsetree.Pstr_eval (moduleName, _) }] - ) -> - let default = - Parsetree.Pconst_string ("default", None) |> Ast_helper.Exp.constant - in - let structureItem = - [moduleName; default] - |> Ast_helper.Exp.tuple - |> Ast_helper.Str.eval - in - (id, Parsetree.PStr [structureItem]) - | attr -> attr - ) - in - [Ast_helper.Val.mk - ~loc:importDescr.jid_loc - ~prim - ~attrs:allAttrs - (Location.mknoloc decl.jld_alias) - decl.jld_type - |> Ast_helper.Str.primitive] - | Spec decls -> - List.map (fun decl -> - let prim = [decl.jld_name] in - let allAttrs = List.concat [attrs; decl.jld_attributes] in - Ast_helper.Val.mk - ~loc:importDescr.jid_loc - ~prim - ~attrs:allAttrs - (Location.mknoloc decl.jld_alias) - decl.jld_type - |> Ast_helper.Str.primitive ~loc:decl.jld_loc - ) decls - in - let jsFfiAttr = (Location.mknoloc "ns.jsFfi", Parsetree.PStr []) in - Ast_helper.Mod.structure ~loc:importDescr.jid_loc valueDescrs - |> Ast_helper.Incl.mk ~attrs:[jsFfiAttr] ~loc:importDescr.jid_loc - |> Ast_helper.Str.include_ ~loc:importDescr.jid_loc \ No newline at end of file diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index 6b8b3e91..a80d61e3 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -559,32 +559,37 @@ let extractValueDescriptionFromModExpr modExpr = | Pmod_structure structure -> loop structure [] | _ -> [] -type jsImportScope = - | JsGlobalImport (* nothing *) - | JsModuleImport of string (* from "path" *) - | JsScopedImport of string list (* window.location *) - -let classifyJsImport valueDescription = - let rec loop attrs = - let open Parsetree in +type jsModuleFlavour = + (* import ceo: string from "company" *) + | JsDefaultImport of string + (* import {delimiter: string} from "path" *) + | JsNamedImport of string + (* import * as leftPad: (string, int) => string from "leftPad" *) + | JsNamespacedImport of string + +let classifyJsModuleFlavour valueDescription = + let rec findModuleAttribute (attrs : Parsetree.attributes) = match attrs with - | [] -> JsGlobalImport - | ({Location.txt = "bs.scope"}, PStr [{pstr_desc = Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (s, _))}, _)}])::_ -> - JsScopedImport [s] - | ({Location.txt = "genType.import"}, PStr [{pstr_desc = Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (s, _))}, _)}])::_ -> - JsModuleImport s - | ({Location.txt = "bs.scope"}, PStr [{pstr_desc = Pstr_eval ({pexp_desc = Pexp_tuple exprs}, _)}])::_ -> - let scopes = List.fold_left (fun acc curr -> - match curr.Parsetree.pexp_desc with - | Pexp_constant (Pconst_string (s, _)) -> s::acc - | _ -> acc - ) [] exprs - in - JsScopedImport (List.rev scopes) - | _::attrs -> - loop attrs + | [] -> JsNamedImport "" + | ({Asttypes.txt = "bs.module" | "module"}, PStr structure)::_ -> + begin match structure with + | [] -> + let namespace = match valueDescription.pval_prim with + | moduleName::_ -> moduleName + | _ -> "" + in + JsNamespacedImport namespace + | [{pstr_desc = Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (moduleName, _)) }, _)}] -> + begin match valueDescription.pval_prim with + | prim::_ when prim = "default" -> JsDefaultImport moduleName + | _ -> JsNamedImport moduleName + end + | _ -> JsNamedImport "" + end + | _::rest -> + findModuleAttribute rest in - loop valueDescription.pval_attributes + findModuleAttribute valueDescription.pval_attributes let isUnderscoreApplySugar expr = match expr.pexp_desc with @@ -595,3 +600,10 @@ let isUnderscoreApplySugar expr = {pexp_desc = Pexp_apply _} ) -> true | _ -> false + +let hasModuleExternalAttribute (attrs : Parsetree.attributes) = + List.exists (fun attr -> match attr with + | ({Asttypes.txt = "bs.module" | "module"}, _) -> true + | _ -> false + ) attrs + diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index 3dc62609..f3224722 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -125,12 +125,15 @@ val isSinglePipeExpr : Parsetree.expression -> bool val extractValueDescriptionFromModExpr: Parsetree.module_expr -> Parsetree.value_description list -type jsImportScope = - | JsGlobalImport (* nothing *) - | JsModuleImport of string (* from "path" *) - | JsScopedImport of string list (* window.location *) +type jsModuleFlavour = + (* import ceo: string from "company" *) + | JsDefaultImport of string + (* import {delimiter: string} from "path" *) + | JsNamedImport of string + (* import * as leftPad: (string, int) => string from "leftPad" *) + | JsNamespacedImport of string -val classifyJsImport: Parsetree.value_description -> jsImportScope +val classifyJsModuleFlavour: Parsetree.value_description -> jsModuleFlavour (* (__x) => f(a, __x, c) -----> f(a, _, c) *) val rewriteUnderscoreApply: Parsetree.expression -> Parsetree.expression @@ -139,3 +142,5 @@ val rewriteUnderscoreApply: Parsetree.expression -> Parsetree.expression val isUnderscoreApplySugar: Parsetree.expression -> bool val hasIfLetAttribute: Parsetree.attributes -> bool + +val hasModuleExternalAttribute: Parsetree.attributes -> bool diff --git a/src/res_printer.ml b/src/res_printer.ml index 9f5b62c0..fb62c568 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -365,7 +365,16 @@ let printIdentLike ?allowUident txt = | ExoticIdent -> Doc.concat [ Doc.text "\\\""; Doc.text txt; - Doc.text"\"" + Doc.doubleQuote; + ] + | NormalIdent -> Doc.text txt + +let printJsExportedIdentLike txt = + match classifyIdentContent txt with + | ExoticIdent -> Doc.concat [ + Doc.doubleQuote; + Doc.text txt; + Doc.doubleQuote; ] | NormalIdent -> Doc.text txt @@ -410,9 +419,9 @@ let printConstant c = match c with end | Pconst_string (txt, None) -> Doc.concat [ - Doc.text "\""; + Doc.doubleQuote; printStringContents txt; - Doc.text "\""; + Doc.doubleQuote; ] | Pconst_string (txt, Some prefix) -> Doc.concat [ @@ -931,46 +940,47 @@ and printIncludeDeclaration (includeDeclaration : Parsetree.include_declaration) and printJsFfiImport (valueDescription: Parsetree.value_description) cmtTbl = let attrs = List.filter (fun attr -> match attr with - | ({Location.txt = "bs.val" | "genType.import" | "bs.scope" }, _) -> false + | ({Location.txt = "bs.module" | "module" }, _) -> false | _ -> true ) valueDescription.pval_attributes in - let (ident, alias) = match valueDescription.pval_prim with - | primitive::_ -> - if primitive <> valueDescription.pval_name.txt then - ( - printIdentLike primitive, - Doc.concat [ - Doc.text " as "; - printIdentLike valueDescription.pval_name.txt; - ] - ) - else - (printIdentLike primitive, Doc.nil) - | _ -> - (printIdentLike valueDescription.pval_name.txt, Doc.nil) - in - Doc.concat [ - printAttributes ~loc:valueDescription.pval_name.loc attrs cmtTbl; - ident; - alias; - Doc.text ": "; - printTypExpr valueDescription.pval_type cmtTbl; - ] - -and printJsFfiImportScope (scope: ParsetreeViewer.jsImportScope) = - match scope with - | JsGlobalImport -> Doc.nil - | JsModuleImport modName -> + match ParsetreeViewer.classifyJsModuleFlavour valueDescription with + | JsDefaultImport _ -> Doc.concat [ - Doc.text " from "; - Doc.doubleQuote; - Doc.text modName; - Doc.doubleQuote; + printAttributes ~loc:valueDescription.pval_name.loc attrs cmtTbl; + printIdentLike valueDescription.pval_name.txt; + Doc.text ": "; + printTypExpr valueDescription.pval_type cmtTbl; ] - | JsScopedImport idents -> + | JsNamespacedImport _ -> Doc.concat [ - Doc.text " from "; - Doc.join ~sep:Doc.dot (List.map Doc.text idents) + printAttributes ~loc:valueDescription.pval_name.loc attrs cmtTbl; + Doc.text "* as "; + printIdentLike valueDescription.pval_name.txt; + Doc.text ": "; + printTypExpr valueDescription.pval_type cmtTbl; + ] + | JsNamedImport _ -> + let (ident, alias) = match valueDescription.pval_prim with + | primitive::_ -> + if primitive <> valueDescription.pval_name.txt then + ( + printJsExportedIdentLike primitive, + Doc.concat [ + Doc.text " as "; + printIdentLike valueDescription.pval_name.txt; + ] + ) + else + (printIdentLike primitive, Doc.nil) + | _ -> + (printIdentLike valueDescription.pval_name.txt, Doc.nil) + in + Doc.concat [ + printAttributes ~loc:valueDescription.pval_name.loc attrs cmtTbl; + ident; + alias; + Doc.text ": "; + printTypExpr valueDescription.pval_type cmtTbl; ] and printJsFfiImportDeclaration (includeDeclaration: Parsetree.include_declaration) cmtTbl = @@ -981,11 +991,22 @@ and printJsFfiImportDeclaration (includeDeclaration: Parsetree.include_declarati ) includeDeclaration.pincl_attributes in let imports = ParsetreeViewer.extractValueDescriptionFromModExpr includeDeclaration.pincl_mod in - let scope = match imports with - | vd::_ -> ParsetreeViewer.classifyJsImport vd - | [] -> ParsetreeViewer.JsGlobalImport + (* FromClause *) + let fromClauseDoc = match imports with + | vd::_ -> + (match ParsetreeViewer.classifyJsModuleFlavour vd with + | ParsetreeViewer.JsNamespacedImport moduleName + | JsDefaultImport moduleName + | JsNamedImport moduleName -> + Doc.concat [ + Doc.text " from "; + Doc.doubleQuote; + Doc.text moduleName; + Doc.doubleQuote; + ] + ) + | [] -> Doc.nil in - let scopeDoc = printJsFfiImportScope scope in Doc.group ( Doc.concat [ printAttributes attrs cmtTbl; @@ -1006,7 +1027,7 @@ and printJsFfiImportDeclaration (includeDeclaration: Parsetree.include_declarati Doc.rbrace; ] ); - scopeDoc; + fromClauseDoc ] ) @@ -1017,10 +1038,118 @@ and printValueBindings ~recFlag (vbs: Parsetree.value_binding list) cmtTbl = ~print:(printValueBinding ~recFlag) cmtTbl + +(* + * @module("path") + * external dirname: string => string = "dirname" + * --> + * import {dirname: string => string} from "path" + *) +and printNonSugarJsImportExternal (valueDescription: Parsetree.value_description) cmtTbl = + let module ModuleFlavour = struct + type t = + | NamespacedImport + | Module of string + end in + let rec findModuleAttribute (attrs : Parsetree.attributes) acc = + match attrs with + | [] -> assert false + | ({txt = "bs.module" | "module"}, PStr structure)::rest -> + let flavour = match structure with + | [] -> ModuleFlavour.NamespacedImport + | [{pstr_desc = Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (moduleName, _)) }, _)}] -> ModuleFlavour.Module moduleName + | _ -> assert false + in + (flavour, List.concat [List.rev acc; rest]) + | attr::rest -> + findModuleAttribute rest (attr::acc) + in + let (moduleFlavour, attrs) = + findModuleAttribute valueDescription.pval_attributes [] + in + match moduleFlavour with + | ModuleFlavour.Module moduleName -> + let importBinding = match (valueDescription.pval_name.txt, valueDescription.pval_prim) with + | name, ["default"] -> + (* @bs.module("/modules/school.js") external schoolName: string = "default" *) + Doc.indent ( + Doc.concat [ + Doc.softLine; + printAttributes attrs cmtTbl; + printIdentLike name; + Doc.text ": "; + printTypExpr valueDescription.pval_type cmtTbl; + ] + ) + | name, [aliasName] -> + if name = aliasName then + Doc.concat [ + Doc.lbrace; + Doc.indent ( + Doc.concat [ + Doc.softLine; + printAttributes attrs cmtTbl; + printIdentLike valueDescription.pval_name.txt; + Doc.text ": "; + printTypExpr valueDescription.pval_type cmtTbl; + ] + ); + Doc.softLine; + Doc.rbrace; + ] + else + Doc.concat [ + Doc.lbrace; + Doc.indent ( + Doc.concat [ + Doc.softLine; + printAttributes attrs cmtTbl; + printJsExportedIdentLike aliasName; + Doc.text " as "; + printIdentLike valueDescription.pval_name.txt; + Doc.text ": "; + printTypExpr valueDescription.pval_type cmtTbl; + ] + ); + Doc.softLine; + Doc.rbrace; + ] + | name, _ -> + Doc.text name + in + Doc.group ( + Doc.concat [ + Doc.text "import "; + importBinding; + Doc.text " from "; + Doc.doubleQuote; + Doc.text moduleName; + Doc.doubleQuote; + ] + ) + | ModuleFlavour.NamespacedImport -> + Doc.group ( + Doc.concat [ + Doc.text "import "; + Doc.text "* as "; + Doc.text valueDescription.pval_name.txt; + Doc.text ":"; + Doc.line; + printTypExpr valueDescription.pval_type cmtTbl; + Doc.text " from "; + Doc.doubleQuote; + Doc.text valueDescription.pval_name.txt; + Doc.doubleQuote; + ] + ) + and printValueDescription valueDescription cmtTbl = let isExternal = match valueDescription.pval_prim with | [] -> false | _ -> true in + if ParsetreeViewer.hasModuleExternalAttribute valueDescription.pval_attributes then + printNonSugarJsImportExternal valueDescription cmtTbl + else let (hasGenType, attrs) = ParsetreeViewer.splitGenTypeAttr valueDescription.pval_attributes in let attrs = printAttributes ~loc:valueDescription.pval_name.loc attrs cmtTbl in let header = @@ -1044,9 +1173,9 @@ and printValueDescription valueDescription cmtTbl = Doc.line; Doc.join ~sep:Doc.line ( List.map(fun s -> Doc.concat [ - Doc.text "\""; + Doc.doubleQuote; Doc.text s; - Doc.text "\""; + Doc.doubleQuote; ]) valueDescription.pval_prim ); @@ -3597,7 +3726,7 @@ and printPexpApply expr cmtTbl = printComments (printLongident lident.txt) cmtTbl memberExpr.pexp_loc | _ -> printExpressionWithComments memberExpr cmtTbl in - Doc.concat [Doc.text "\""; memberDoc; Doc.text "\""] + Doc.concat [Doc.doubleQuote; memberDoc; Doc.doubleQuote] in Doc.group (Doc.concat [ printAttributes expr.pexp_attributes cmtTbl; @@ -4695,9 +4824,9 @@ and printBsObjectRow (lbl, expr) cmtTbl = let cmtLoc = {lbl.loc with loc_end = expr.pexp_loc.loc_end} in let lblDoc = let doc = Doc.concat [ - Doc.text "\""; + Doc.doubleQuote; printLongident lbl.txt; - Doc.text "\""; + Doc.doubleQuote; ] in printComments doc cmtTbl lbl.loc in diff --git a/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap b/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap index 0fbaec0c..3e9165ed 100644 --- a/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap @@ -21,14 +21,13 @@ and y = 2[@@genType ]" exports[`import.js 1`] = ` "include struct - external realValue : complexNumber -> float = \\"realValue\\"[@@genType.import + external realValue : complexNumber -> float = \\"realValue\\"[@@module \\"./MyMath\\"] - external maxInt : unit -> int = \\"maxInt\\"[@@genType.import \\"./MyMath\\"] + external maxInt : unit -> int = \\"maxInt\\"[@@module \\"./MyMath\\"] end[@@ns.jsFfi ] include struct - external realValue : complexNumber -> float = \\"realValue\\"[@@genType.import - (\\"./MyMath\\", - \\"default\\")] + external realValue : complexNumber -> float = \\"default\\"[@@module + \\"./MyMath\\"] end[@@ns.jsFfi ]" `; diff --git a/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap b/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap index b64fb735..f05ece8b 100644 --- a/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap @@ -60,34 +60,8 @@ exports[`itemExtension.js 1`] = ` exports[`jsFfiSugar.js 1`] = ` "include - struct - external setTimeout : (unit -> unit) -> unit -> float = \\"setTimeout\\" - [@@bs.val ] - end[@@ns.jsFfi ] -include - struct - external timeout : (unit -> unit) -> unit -> float = \\"setTimeout\\" - [@@bs.val ] - end[@@ns.jsFfi ] -include - struct - external setTimeout : (unit -> unit) -> unit -> float = \\"setTimeout\\" - [@@bs.val ] - external clearTimeout : float -> unit = \\"clearTimeout\\"[@@bs.val ] - end[@@ns.jsFfi ] -include - struct - external random : unit -> float = \\"random\\"[@@bs.val ][@@bs.scope \\"Math\\"] - end[@@ns.jsFfi ] -include - struct - external href : string = \\"href\\"[@@bs.val ][@@bs.scope - (\\"window\\", \\"location\\")] - end[@@ns.jsFfi ] -include - struct - external dirname : string -> string = \\"dirname\\"[@@genType.import \\"path\\"] - end[@@ns.jsFfi ]" + struct external dirname : string -> string = \\"dirname\\"[@@module \\"path\\"] end +[@@ns.jsFfi ]" `; exports[`letBinding.js 1`] = ` diff --git a/tests/parsing/grammar/structure/jsFfiSugar.js b/tests/parsing/grammar/structure/jsFfiSugar.js index 688c3bf5..895c4baa 100644 --- a/tests/parsing/grammar/structure/jsFfiSugar.js +++ b/tests/parsing/grammar/structure/jsFfiSugar.js @@ -1,16 +1,3 @@ -import setTimeout: (unit => unit, unit) => float - -import setTimeout as timeout: (unit => unit, unit) => float - -import { - setTimeout: (unit => unit, unit) => float, - clearTimeout: float => unit -} - -import { random: unit => float } from Math - -import { href: string } from window.location - import { dirname: string => string } from "path" diff --git a/tests/ppx/react/__snapshots__/render.spec.js.snap b/tests/ppx/react/__snapshots__/render.spec.js.snap index 34daac1b..b3cee015 100644 --- a/tests/ppx/react/__snapshots__/render.spec.js.snap +++ b/tests/ppx/react/__snapshots__/render.spec.js.snap @@ -24,11 +24,9 @@ exports[`externalWithCustomName.res 1`] = ` ~key: string=?, unit, ) => {\\"a\\": int, \\"b\\": string} = \\"\\" - @bs.module(\\"Foo\\") - external component: React.componentLike< - {\\"a\\": int, \\"b\\": string}, - React.element, - > = \\"component\\" + import { + component: React.componentLike<{\\"a\\": int, \\"b\\": string}, React.element> + } from \\"Foo\\" } let t = React.createElement( diff --git a/tests/printer/ffi/__snapshots__/render.spec.js.snap b/tests/printer/ffi/__snapshots__/render.spec.js.snap index f535f7de..5209440b 100644 --- a/tests/printer/ffi/__snapshots__/render.spec.js.snap +++ b/tests/printer/ffi/__snapshots__/render.spec.js.snap @@ -25,10 +25,6 @@ exports[`import.res 1`] = ` toNamespacedPath as \\\\\\"ToNamespacedPath\\": string => string, } from \\"path\\" -import { - \\\\\\"*crazy_string*\\" as crazyString: float => timestamp, -} from \\"firebase/app\\" - -import {document: Dom.document} +import {\\"*crazy_string*\\" as crazyString: float => timestamp} from \\"firebase/app\\" " `; diff --git a/tests/printer/ffi/import.res b/tests/printer/ffi/import.res index 789ebf9c..cabc7b3e 100644 --- a/tests/printer/ffi/import.res +++ b/tests/printer/ffi/import.res @@ -7,5 +7,3 @@ import { } from "path" import {\"*crazy_string*" as crazyString : float => timestamp } from "firebase/app" - -import document: Dom.document diff --git a/tests/printer/other/__snapshots__/render.spec.js.snap b/tests/printer/other/__snapshots__/render.spec.js.snap index 17e1381e..c7bf2c5a 100644 --- a/tests/printer/other/__snapshots__/render.spec.js.snap +++ b/tests/printer/other/__snapshots__/render.spec.js.snap @@ -300,15 +300,17 @@ module Range = { children: React.element, } - @bs.module(\\"react-range\\") @react.component - external make: ( - ~min: int, - ~max: int, - ~values: array, - ~onChange: array => unit, - ~renderTrack: renderTrackParams => React.element, - ~renderThumb: renderTrackParams => React.element, - ) => React.element = \\"Range\\" + import { + @react.component + \\"Range\\" as make: ( + ~min: int, + ~max: int, + ~values: array, + ~onChange: array => unit, + ~renderTrack: renderTrackParams => React.element, + ~renderThumb: renderTrackParams => React.element, + ) => React.element + } from \\"react-range\\" } @react.component diff --git a/tests/printer/structure/__snapshots__/render.spec.js.snap b/tests/printer/structure/__snapshots__/render.spec.js.snap index 5f7de27f..293236cf 100644 --- a/tests/printer/structure/__snapshots__/render.spec.js.snap +++ b/tests/printer/structure/__snapshots__/render.spec.js.snap @@ -115,8 +115,7 @@ include WebGl include ( /* Use varargs to avoid the ReactJS warning for duplicate keys in children */ { - @bs.val @bs.module(\\"react\\") - external createElementInternalHack: 'a = \\"createElement\\" + import {@bs.val createElement as createElementInternalHack: 'a} from \\"react\\" @bs.send external apply: ( 'theFunction, From 8a8a7c91aa8ca1a498d0d3b1e4aeddb6087ac483 Mon Sep 17 00:00:00 2001 From: Iwan Date: Sun, 3 Jan 2021 15:41:01 +0100 Subject: [PATCH 2/7] Refactor printing of JS import declarations --- src/res_core.ml | 56 +++-- src/res_printer.ml | 214 +++++++----------- .../ffi/__snapshots__/parse.spec.js.snap | 6 +- .../__snapshots__/parse.spec.js.snap | 6 +- .../react/__snapshots__/render.spec.js.snap | 2 +- .../other/__snapshots__/render.spec.js.snap | 170 +++++++++++++- tests/printer/other/import.res | 173 ++++++++++++++ .../__snapshots__/render.spec.js.snap | 5 +- 8 files changed, 465 insertions(+), 167 deletions(-) create mode 100644 tests/printer/other/import.res diff --git a/src/res_core.ml b/src/res_core.ml index 50ecf801..297a46c2 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -5,7 +5,6 @@ module Diagnostics = Res_diagnostics module CommentTable = Res_comments_table module ResPrinter = Res_printer module Scanner = Res_scanner -(* module JsFfi = Res_js_ffi *) module Parser = Res_parser let mkLoc startLoc endLoc = Location.{ @@ -5219,9 +5218,9 @@ and parseJsImportDeclaration ~startPos ~attrs p = let importJsClause = parseJsImportClause p in let fromClause = parseFromClause p in let loc = mkLoc startPos p.prevEndPos in - let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in - importJsClause - |> List.map (fun jsImport -> + (* let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in *) + let clause = + List.map (fun jsImport -> let valueDescription = match jsImport with (* import schoolName: string from '/modules/school.js' *) | DefaultImport {attrs; name; alias; typ} -> @@ -5229,11 +5228,21 @@ and parseJsImportDeclaration ~startPos ~attrs p = Ast_helper.Val.mk ~loc:name.loc ~attrs:(fromClause::attrs) ~prim:[alias] name typ - (* import * as leftPad: (string, int) => string from "leftPad" *) + (* import * as leftPad: (string, int) => string from "left-pad" *) | NameSpaceImport {attrs; name; typ} -> - let ({Asttypes.txt = jsModuleName}, _ ) = fromClause in + let (_, moduleSpecifier) = fromClause in + let jsModuleName = match (moduleSpecifier: Parsetree.payload) with + (* extract "left-pad"*) + | PStr [ + {Parsetree.pstr_desc = Pstr_eval ( + {pexp_desc = Pexp_constant (Pconst_string (jsModuleName, None))}, + _ + )} + ] -> jsModuleName + | _ -> "" + in let moduleAttribute = (Location.mknoloc "module", Parsetree.PStr []) in - (* @module external leftPad: (string, int) => string = "leftPad" *) + (* @module external leftPad: (string, int) => string = "left-pad" *) Ast_helper.Val.mk ~loc:name.loc ~attrs:(moduleAttribute::attrs) ~prim:[jsModuleName] name typ @@ -5245,21 +5254,27 @@ and parseJsImportDeclaration ~startPos ~attrs p = in (* TODO: nicer loc on primitive? *) Ast_helper.Str.primitive ~loc:valueDescription.pval_loc valueDescription - ) - |> Ast_helper.Mod.structure ~loc - |> Ast_helper.Incl.mk ~attrs ~loc - |> Ast_helper.Str.include_ ~loc + ) importJsClause + in match clause with + | [oneExternal] -> oneExternal + | clause -> + let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in + clause + |> Ast_helper.Mod.structure ~loc + |> Ast_helper.Incl.mk ~attrs ~loc + |> Ast_helper.Str.include_ ~loc (* * ImportClause ::= - * ImportedDefaultBinding - * NameSpaceImport + * attributes? ImportedDefaultBinding + * attributes? NameSpaceImport * NamedImports - * ImportedDefaultBinding , NameSpaceImport - * ImportedDefaultBinding , NamedImports + * attributes? ImportedDefaultBinding , attributes? NameSpaceImport + * attributes? ImportedDefaultBinding , NamedImports *) and parseJsImportClause p = let startPos = p.Parser.startPos in + let attrs = parseAttributes p in match p.token with (* ImportedDefaultBinding *) | Token.Lident ident | String ident -> @@ -5268,7 +5283,7 @@ and parseJsImportClause p = Parser.expect Colon p; let typ = parseTypExpr p in let default = DefaultImport { - attrs = []; + attrs = attrs; name = Location.mkloc ident identLoc; alias = "default"; typ @@ -5276,18 +5291,19 @@ and parseJsImportClause p = begin match p.token with | Comma -> Parser.next p; + let attrs = parseAttributes p in begin match p.token with | Lbrace -> default::(parseJsNamedImports p) | Asterisk -> - [default; parseNameSpaceImport p] + [default; parseNameSpaceImport ~attrs p] | _ -> [default] end | _ -> [default] end | Asterisk -> - [parseNameSpaceImport p] + [parseNameSpaceImport ~attrs p] (* NamedImports *) | Lbrace -> parseJsNamedImports p @@ -5298,7 +5314,7 @@ and parseJsImportClause p = * NameSpaceImport : * * as ImportedBinding *) -and parseNameSpaceImport p = +and parseNameSpaceImport ~attrs p = let startPos = p.Parser.startPos in Parser.expect Asterisk p; Parser.expect As p; @@ -5309,7 +5325,7 @@ and parseNameSpaceImport p = Parser.expect Colon p; let typ = parseTypExpr p in NameSpaceImport { - attrs = []; + attrs = attrs; name = Location.mkloc ident identLoc; alias = ""; typ diff --git a/src/res_printer.ml b/src/res_printer.ml index fb62c568..a1037955 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -924,7 +924,14 @@ and printIncludeDeclaration (includeDeclaration : Parsetree.include_declaration) ) includeDeclaration.pincl_attributes in if isJsFfiImport then - printJsFfiImportDeclaration includeDeclaration cmtTbl + let attrs = List.filter (fun attr -> + match attr with + | ({Location.txt = "ns.jsFfi"}, _) -> false + | _ -> true + ) includeDeclaration.pincl_attributes + in + let imports = ParsetreeViewer.extractValueDescriptionFromModExpr includeDeclaration.pincl_mod in + printJsFfiImportDeclaration ~attrs ~imports cmtTbl else Doc.concat [ printAttributes includeDeclaration.pincl_attributes cmtTbl; @@ -983,50 +990,94 @@ and printJsFfiImport (valueDescription: Parsetree.value_description) cmtTbl = printTypExpr valueDescription.pval_type cmtTbl; ] -and printJsFfiImportDeclaration (includeDeclaration: Parsetree.include_declaration) cmtTbl = - let attrs = List.filter (fun attr -> - match attr with - | ({Location.txt = "ns.jsFfi"}, _) -> false - | _ -> true - ) includeDeclaration.pincl_attributes +and printJsImportClause vds cmtTbl = + let isNamedImport vd = match ParsetreeViewer.classifyJsModuleFlavour vd with + | JsNamedImport _ -> true + | _ -> false in - let imports = ParsetreeViewer.extractValueDescriptionFromModExpr includeDeclaration.pincl_mod in + match vds with + | (first::_ as imports) when isNamedImport first -> + (* import {x: int, y: int} from "math" *) + Doc.concat [ + Doc.space; + Doc.lbrace; + Doc.indent ( + Doc.concat [ + Doc.softLine; + Doc.join ~sep:(Doc.concat [Doc.comma; Doc.line]) ( + List.map (fun vd -> printJsFfiImport vd cmtTbl) imports + ) + ] + ); + Doc.trailingComma; + Doc.softLine; + Doc.rbrace; + Doc.space; + ] + | first::(snd::_ as imports) when isNamedImport snd -> + (* import defaultExport: int, {x: int, y: int} from "math" *) + Doc.concat [ + Doc.indent ( + Doc.concat [ + Doc.line; + printJsFfiImport first cmtTbl; + Doc.comma; + Doc.line; + Doc.lbrace; + Doc.indent ( + Doc.concat [ + Doc.softLine; + Doc.join ~sep:(Doc.concat [Doc.comma; Doc.line]) ( + List.map (fun vd -> printJsFfiImport vd cmtTbl) imports + ) + ] + ); + Doc.softLine; + Doc.rbrace; + ] + ); + Doc.line; + ] + | imports -> + (* import defaultExport: int from "math" *) + (* import * as moduleObject: 'a from "math" *) + (* import defaultExport: int, * as moduleObject: 'a from "math" *) + Doc.concat [ + Doc.indent ( + Doc.concat [ + Doc.line; + Doc.join ~sep:(Doc.concat [Doc.comma; Doc.line]) ( + List.map (fun vd -> printJsFfiImport vd cmtTbl) imports + ) + ] + ); + Doc.line; + ] + +and printJsFfiImportDeclaration ~attrs ~imports cmtTbl = + +(* (includeDeclaration: Parsetree.include_declaration) cmtTbl = *) (* FromClause *) let fromClauseDoc = match imports with | vd::_ -> - (match ParsetreeViewer.classifyJsModuleFlavour vd with + begin match ParsetreeViewer.classifyJsModuleFlavour vd with | ParsetreeViewer.JsNamespacedImport moduleName | JsDefaultImport moduleName | JsNamedImport moduleName -> Doc.concat [ - Doc.text " from "; + Doc.text "from "; Doc.doubleQuote; Doc.text moduleName; Doc.doubleQuote; ] - ) + end | [] -> Doc.nil in Doc.group ( Doc.concat [ printAttributes attrs cmtTbl; - Doc.text "import "; - Doc.group ( - Doc.concat [ - Doc.lbrace; - Doc.indent ( - Doc.concat [ - Doc.softLine; - Doc.join ~sep:(Doc.concat [Doc.comma; Doc.line]) ( - List.map (fun vd -> printJsFfiImport vd cmtTbl) imports - ) - ] - ); - Doc.trailingComma; - Doc.softLine; - Doc.rbrace; - ] - ); + Doc.text "import"; + printJsImportClause imports cmtTbl; fromClauseDoc ] ) @@ -1038,117 +1089,12 @@ and printValueBindings ~recFlag (vbs: Parsetree.value_binding list) cmtTbl = ~print:(printValueBinding ~recFlag) cmtTbl - -(* - * @module("path") - * external dirname: string => string = "dirname" - * --> - * import {dirname: string => string} from "path" - *) -and printNonSugarJsImportExternal (valueDescription: Parsetree.value_description) cmtTbl = - let module ModuleFlavour = struct - type t = - | NamespacedImport - | Module of string - end in - let rec findModuleAttribute (attrs : Parsetree.attributes) acc = - match attrs with - | [] -> assert false - | ({txt = "bs.module" | "module"}, PStr structure)::rest -> - let flavour = match structure with - | [] -> ModuleFlavour.NamespacedImport - | [{pstr_desc = Pstr_eval ({pexp_desc = Pexp_constant (Pconst_string (moduleName, _)) }, _)}] -> ModuleFlavour.Module moduleName - | _ -> assert false - in - (flavour, List.concat [List.rev acc; rest]) - | attr::rest -> - findModuleAttribute rest (attr::acc) - in - let (moduleFlavour, attrs) = - findModuleAttribute valueDescription.pval_attributes [] - in - match moduleFlavour with - | ModuleFlavour.Module moduleName -> - let importBinding = match (valueDescription.pval_name.txt, valueDescription.pval_prim) with - | name, ["default"] -> - (* @bs.module("/modules/school.js") external schoolName: string = "default" *) - Doc.indent ( - Doc.concat [ - Doc.softLine; - printAttributes attrs cmtTbl; - printIdentLike name; - Doc.text ": "; - printTypExpr valueDescription.pval_type cmtTbl; - ] - ) - | name, [aliasName] -> - if name = aliasName then - Doc.concat [ - Doc.lbrace; - Doc.indent ( - Doc.concat [ - Doc.softLine; - printAttributes attrs cmtTbl; - printIdentLike valueDescription.pval_name.txt; - Doc.text ": "; - printTypExpr valueDescription.pval_type cmtTbl; - ] - ); - Doc.softLine; - Doc.rbrace; - ] - else - Doc.concat [ - Doc.lbrace; - Doc.indent ( - Doc.concat [ - Doc.softLine; - printAttributes attrs cmtTbl; - printJsExportedIdentLike aliasName; - Doc.text " as "; - printIdentLike valueDescription.pval_name.txt; - Doc.text ": "; - printTypExpr valueDescription.pval_type cmtTbl; - ] - ); - Doc.softLine; - Doc.rbrace; - ] - | name, _ -> - Doc.text name - in - Doc.group ( - Doc.concat [ - Doc.text "import "; - importBinding; - Doc.text " from "; - Doc.doubleQuote; - Doc.text moduleName; - Doc.doubleQuote; - ] - ) - | ModuleFlavour.NamespacedImport -> - Doc.group ( - Doc.concat [ - Doc.text "import "; - Doc.text "* as "; - Doc.text valueDescription.pval_name.txt; - Doc.text ":"; - Doc.line; - printTypExpr valueDescription.pval_type cmtTbl; - Doc.text " from "; - Doc.doubleQuote; - Doc.text valueDescription.pval_name.txt; - Doc.doubleQuote; - ] - ) - and printValueDescription valueDescription cmtTbl = let isExternal = match valueDescription.pval_prim with | [] -> false | _ -> true in if ParsetreeViewer.hasModuleExternalAttribute valueDescription.pval_attributes then - printNonSugarJsImportExternal valueDescription cmtTbl + printJsFfiImportDeclaration ~attrs:[] ~imports:[valueDescription] cmtTbl else let (hasGenType, attrs) = ParsetreeViewer.splitGenTypeAttr valueDescription.pval_attributes in let attrs = printAttributes ~loc:valueDescription.pval_name.loc attrs cmtTbl in diff --git a/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap b/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap index 3e9165ed..f251fbcc 100644 --- a/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap @@ -25,9 +25,5 @@ exports[`import.js 1`] = ` \\"./MyMath\\"] external maxInt : unit -> int = \\"maxInt\\"[@@module \\"./MyMath\\"] end[@@ns.jsFfi ] -include - struct - external realValue : complexNumber -> float = \\"default\\"[@@module - \\"./MyMath\\"] - end[@@ns.jsFfi ]" +external realValue : complexNumber -> float = \\"default\\"[@@module \\"./MyMath\\"]" `; diff --git a/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap b/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap index f05ece8b..78f9e7e0 100644 --- a/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap @@ -58,11 +58,7 @@ exports[`itemExtension.js 1`] = ` [%%itemExtension ][@@attrOnExtension ]" `; -exports[`jsFfiSugar.js 1`] = ` -"include - struct external dirname : string -> string = \\"dirname\\"[@@module \\"path\\"] end -[@@ns.jsFfi ]" -`; +exports[`jsFfiSugar.js 1`] = `"external dirname : string -> string = \\"dirname\\"[@@module \\"path\\"]"`; exports[`letBinding.js 1`] = ` "let a = 1 diff --git a/tests/ppx/react/__snapshots__/render.spec.js.snap b/tests/ppx/react/__snapshots__/render.spec.js.snap index b3cee015..f0577162 100644 --- a/tests/ppx/react/__snapshots__/render.spec.js.snap +++ b/tests/ppx/react/__snapshots__/render.spec.js.snap @@ -25,7 +25,7 @@ exports[`externalWithCustomName.res 1`] = ` unit, ) => {\\"a\\": int, \\"b\\": string} = \\"\\" import { - component: React.componentLike<{\\"a\\": int, \\"b\\": string}, React.element> + component: React.componentLike<{\\"a\\": int, \\"b\\": string}, React.element>, } from \\"Foo\\" } diff --git a/tests/printer/other/__snapshots__/render.spec.js.snap b/tests/printer/other/__snapshots__/render.spec.js.snap index c7bf2c5a..fdd357b4 100644 --- a/tests/printer/other/__snapshots__/render.spec.js.snap +++ b/tests/printer/other/__snapshots__/render.spec.js.snap @@ -309,7 +309,7 @@ module Range = { ~onChange: array => unit, ~renderTrack: renderTrackParams => React.element, ~renderThumb: renderTrackParams => React.element, - ) => React.element + ) => React.element, } from \\"react-range\\" } @@ -378,6 +378,174 @@ let make = () => { " `; +exports[`import.res 1`] = ` +"// Import a single export from a module +import {dirname: string => string} from \\"path\\" + +// old external +import {dirname: string => string} from \\"path\\" + +// Import multiple exports from a module +import { + delimiter: string, + dirname: string => string, + relative: (~from: string, ~\\\\\\"to\\": string) => string, +} from \\"path\\" + +// old external +import {delimiter: string} from \\"path\\" +import {dirname: (string, string) => string} from \\"path\\" +import {relative: (~from: string, ~\\\\\\"to\\": string) => string} from \\"path\\" + +// Import an export with a more convenient alias +import {renameSync as rename: (string, string) => string} from \\"fs\\" + +import {renameSync as rename: (string, string) => string} from \\"fs\\" + +// import an export with an illegal ReScript identifier +import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"react\\" + +// old external +import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"react\\" + +// import an export with additional attributes +import {@splice join: array => string} from \\"path\\" + +// old external +import { + @splice + join: array => string, +} from \\"path\\" + +// Rename multiple exports during import +import { + renameSync as rename: (string, string) => string, + unlinkSync as unlink: string => unit, +} from \\"fs\\" + +// old external +import {renameSync as rename: (string, string) => string} from \\"path\\" +import {unlinkSync as unlink: string => unit} from \\"path\\" + +// import an entire module's contents (aka NameSpacedImport) +import * as leftPad: (string, int) => string from \\"leftPad\\" + +// old external +import * as leftPad: (string, int) => string from \\"leftPad\\" + +// Importing default export +import schoolName: string from \\"/modules/school.js\\" +// syntactic sugar for +import schoolName: string from \\"/modules/school.js\\" + +// old external +import schoolName: string from \\"/modules/school.js\\" + +// importing default export with named imports +import schoolName: string, {getGrade: student => int} from \\"/modules/school.js\\" + +// old external +import schoolName: string from \\"/modules/school.js\\" +import {getGrade: student => int} from \\"/modules/school.js\\" + +// importing default export with multiple named imports +import + schoolName: string, + { + getGrade: student => int, + getTeachers: unit => array + } +from \\"/modules/school.js\\" + +// old external +import schoolName: string from \\"/modules/school.js\\" +import {getGrade: student => int} from \\"/modules/school.js\\" +import {getTeachers: unit => array} from \\"/modules/school.js\\" + +// import values from JavaScript modules with @as and polyvariants +import { + openSync: ( + path, + @bs.string + [ + | @bs.as(\\"r\\") #Read + | @bs.as(\\"r+\\") #Read_write + | @bs.as(\\"rs+\\") #Read_write_sync + | @bs.as(\\"w\\") #Write + | @bs.as(\\"wx\\") #Write_fail_if_exists + | @bs.as(\\"w+\\") #Write_read + | @bs.as(\\"wx+\\") #Write_read_fail_if_exists + | @bs.as(\\"a\\") #Append + | @bs.as(\\"ax\\") #Append_fail_if_exists + | @bs.as(\\"a+\\") #Append_read + | @bs.as(\\"ax+\\") #Append_read_fail_if_exists + ], + ) => unit, +} from \\"fs\\" + +// old external +import { + openSync: ( + path, + @bs.string + [ + | @bs.as(\\"r\\") #Read + | @bs.as(\\"r+\\") #Read_write + | @bs.as(\\"rs+\\") #Read_write_sync + | @bs.as(\\"w\\") #Write + | @bs.as(\\"wx\\") #Write_fail_if_exists + | @bs.as(\\"w+\\") #Write_read + | @bs.as(\\"wx+\\") #Write_read_fail_if_exists + | @bs.as(\\"a\\") #Append + | @bs.as(\\"ax\\") #Append_fail_if_exists + | @bs.as(\\"a+\\") #Append_read + | @bs.as(\\"ax+\\") #Append_read_fail_if_exists + ], + ) => unit, +} from \\"fs\\" + +// import a react component from a module +import { + @react.component + \\"Circle\\" as make: ( + ~center: Leaflet.point, + ~radius: int, + ~opacity: float, + ~stroke: bool, + ~color: string, + ~children: React.element=?, + ) => React.element, +} from \\"react-leaflet\\" + +module JsComponent = { + import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element + from \\"./DatePicker\\" +} + +import @attr * as leftPad: (string, int) => string from \\"leftPad\\" +import @attr * as leftPad: (string, int) => string from \\"leftPad\\" + +import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element, + @attr + * as dateLib: 'a +from \\"./DatePicker\\" + +import * as copyToClipboard: string => unit from \\"copy-to-clipboard\\" +" +`; + exports[`lor.js 1`] = ` "let lower = ch => lor(32, ch) " diff --git a/tests/printer/other/import.res b/tests/printer/other/import.res new file mode 100644 index 00000000..f1b2b8c9 --- /dev/null +++ b/tests/printer/other/import.res @@ -0,0 +1,173 @@ +// Import a single export from a module +import { dirname: string => string } from "path" + +// old external +@module("path") +external dirname: string => string = "dirname" + +// Import multiple exports from a module +import { + delimiter: string, + dirname: string => string, + relative: (~from: string, ~\"to": string) => string +} from "path" + +// old external +@module("path") +external delimiter: string = "delimiter" +@module("path") +external dirname: (string, string) => string = "dirname" +@module("path") +external relative: (~from: string, ~\"to": string) => string = "relative" + +// Import an export with a more convenient alias +import {renameSync as rename: (string, string) => string} from "fs" + +@module("fs") +external rename: (string, string) => string = "renameSync" + +// import an export with an illegal ReScript identifier +import {"Fragment" as make: component<{"children": element}>} from "react" + +// old external +@module("react") +external make: component<{"children": element}> = "Fragment" + +// import an export with additional attributes +import { + @splice + join: array => string +} from "path" + +// old external +@module("path") @splice +external join: array => string = "join" + +// Rename multiple exports during import +import { + renameSync as rename: (string, string) => string, + unlinkSync as unlink: string => unit, +} from "fs" + +// old external +@module("path") +external rename: (string, string) => string = "renameSync" +@module("path") +external unlink: string => unit = "unlinkSync" + +// import an entire module's contents (aka NameSpacedImport) +import * as leftPad: (string, int) => string from "leftPad" + +// old external +@module +external leftPad: (string, int) => string = "leftPad" + +// Importing default export +import schoolName: string from "/modules/school.js"; +// syntactic sugar for +import {default as schoolName: string} from "/modules/school.js"; + +// old external +@module("/modules/school.js") external schoolName: string = "default" + +// importing default export with named imports +import schoolName: string, {getGrade: student => int} from "/modules/school.js"; + +// old external +@module("/modules/school.js") external schoolName: string = "default" +@module("/modules/school.js") external getGrade: student => int = "getGrade" + +// importing default export with multiple named imports +import + schoolName: string, + { + getGrade: student => int, + getTeachers: unit => array + } +from "/modules/school.js"; + +// old external +@module("/modules/school.js") external schoolName: string = "default" +@module("/modules/school.js") external getGrade: student => int = "getGrade" +@module("/modules/school.js") external getTeachers: unit => array = "getTeachers" + + +// import values from JavaScript modules with @as and polyvariants +import { + openSync: ( + path, + @bs.string [ + | @bs.as("r") #Read + | @bs.as("r+") #Read_write + | @bs.as("rs+") #Read_write_sync + | @bs.as("w") #Write + | @bs.as("wx") #Write_fail_if_exists + | @bs.as("w+") #Write_read + | @bs.as("wx+") #Write_read_fail_if_exists + | @bs.as("a") #Append + | @bs.as("ax") #Append_fail_if_exists + | @bs.as("a+") #Append_read + | @bs.as("ax+") #Append_read_fail_if_exists + ], + ) => unit +} from "fs" + +// old external +@module("fs") +external openSync: ( + path, + @bs.string + [ + | @bs.as("r") #Read + | @bs.as("r+") #Read_write + | @bs.as("rs+") #Read_write_sync + | @bs.as("w") #Write + | @bs.as("wx") #Write_fail_if_exists + | @bs.as("w+") #Write_read + | @bs.as("wx+") #Write_read_fail_if_exists + | @bs.as("a") #Append + | @bs.as("ax") #Append_fail_if_exists + | @bs.as("a+") #Append_read + | @bs.as("ax+") #Append_read_fail_if_exists + ], +) => unit = "openSync" + +// import a react component from a module +import { + @react.component + "Circle" as make: ( + ~center: Leaflet.point, + ~radius: int, + ~opacity: float, + ~stroke: bool, + ~color: string, + ~children: React.element=? + ) => React.element +} from "react-leaflet" + +module JsComponent = { + import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element from "./DatePicker" +} + + + +import @attr * as leftPad: (string, int) => string from "leftPad" +import @attr * as leftPad: (string, int) => string from "leftPad" + +import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element, + @attr * as dateLib: 'a +from "./DatePicker" + +import * as copyToClipboard: string => unit from "copy-to-clipboard" diff --git a/tests/printer/structure/__snapshots__/render.spec.js.snap b/tests/printer/structure/__snapshots__/render.spec.js.snap index 293236cf..75090a2e 100644 --- a/tests/printer/structure/__snapshots__/render.spec.js.snap +++ b/tests/printer/structure/__snapshots__/render.spec.js.snap @@ -115,7 +115,10 @@ include WebGl include ( /* Use varargs to avoid the ReactJS warning for duplicate keys in children */ { - import {@bs.val createElement as createElementInternalHack: 'a} from \\"react\\" + import { + @bs.val + createElement as createElementInternalHack: 'a, + } from \\"react\\" @bs.send external apply: ( 'theFunction, From cadca0405a170c0b2af1d0108c38ff1d7a09b90d Mon Sep 17 00:00:00 2001 From: Iwan Date: Sun, 3 Jan 2021 16:04:03 +0100 Subject: [PATCH 3/7] Add support for JS import declarations in interface files. --- src/res_core.ml | 120 +++++++----- src/res_parsetree_viewer.ml | 14 ++ src/res_parsetree_viewer.mli | 1 + src/res_printer.ml | 25 ++- .../other/__snapshots__/render.spec.js.snap | 168 +++++++++++++++++ tests/printer/other/import.resi | 173 ++++++++++++++++++ 6 files changed, 447 insertions(+), 54 deletions(-) create mode 100644 tests/printer/other/import.resi diff --git a/src/res_core.ml b/src/res_core.ml index 297a46c2..24d21417 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -5156,9 +5156,24 @@ and parseStructureItemRegion p = let loc = mkLoc startPos p.prevEndPos in Some (Ast_helper.Str.primitive ~loc externalDef) | Import -> - let structureItem = parseJsImportDeclaration ~startPos ~attrs p in + let valueDescriptions = parseJsImportDeclaration ~startPos ~attrs p in parseNewlineOrSemicolonStructure p; let loc = mkLoc startPos p.prevEndPos in + let structureItem = + let primitives = + List.map (fun valueDescription -> + Ast_helper.Str.primitive ~loc:valueDescription.Parsetree.pval_loc valueDescription + ) valueDescriptions + in + match primitives with + | [oneExternal] -> oneExternal + | clause -> + let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in + clause + |> Ast_helper.Mod.structure ~loc + |> Ast_helper.Incl.mk ~attrs ~loc + |> Ast_helper.Str.include_ ~loc + in Some {structureItem with pstr_loc = loc} | Exception -> let exceptionDef = parseExceptionDef ~attrs p in @@ -5213,56 +5228,44 @@ and parseStructureItemRegion p = end (* import ImportClause FromClause *) -and parseJsImportDeclaration ~startPos ~attrs p = +and parseJsImportDeclaration ~startPos:_startPos ~attrs:_ p = Parser.expect Token.Import p; let importJsClause = parseJsImportClause p in let fromClause = parseFromClause p in - let loc = mkLoc startPos p.prevEndPos in - (* let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in *) - let clause = - List.map (fun jsImport -> - let valueDescription = match jsImport with - (* import schoolName: string from '/modules/school.js' *) - | DefaultImport {attrs; name; alias; typ} -> - (* module("/modules/school.js") external schoolName: string = "default" *) - Ast_helper.Val.mk ~loc:name.loc - ~attrs:(fromClause::attrs) ~prim:[alias] name typ - - (* import * as leftPad: (string, int) => string from "left-pad" *) - | NameSpaceImport {attrs; name; typ} -> - let (_, moduleSpecifier) = fromClause in - let jsModuleName = match (moduleSpecifier: Parsetree.payload) with - (* extract "left-pad"*) - | PStr [ - {Parsetree.pstr_desc = Pstr_eval ( - {pexp_desc = Pexp_constant (Pconst_string (jsModuleName, None))}, - _ - )} - ] -> jsModuleName - | _ -> "" - in - let moduleAttribute = (Location.mknoloc "module", Parsetree.PStr []) in - (* @module external leftPad: (string, int) => string = "left-pad" *) - Ast_helper.Val.mk ~loc:name.loc - ~attrs:(moduleAttribute::attrs) ~prim:[jsModuleName] name typ - - (* import {dirname: string => string} from "path" *) - | NamedImport {attrs; name; alias; typ} -> - (* module("path") external dirname: (string, string) => string = "dirname" *) - Ast_helper.Val.mk ~loc:name.loc - ~attrs:(fromClause::attrs) ~prim:[alias] name typ + List.map (fun jsImport -> + let valueDescription = match jsImport with + (* import schoolName: string from '/modules/school.js' *) + | DefaultImport {attrs; name; alias; typ} -> + (* module("/modules/school.js") external schoolName: string = "default" *) + Ast_helper.Val.mk ~loc:name.loc + ~attrs:(fromClause::attrs) ~prim:[alias] name typ + + (* import * as leftPad: (string, int) => string from "left-pad" *) + | NameSpaceImport {attrs; name; typ} -> + let (_, moduleSpecifier) = fromClause in + let jsModuleName = match (moduleSpecifier: Parsetree.payload) with + (* extract "left-pad"*) + | PStr [ + {Parsetree.pstr_desc = Pstr_eval ( + {pexp_desc = Pexp_constant (Pconst_string (jsModuleName, None))}, + _ + )} + ] -> jsModuleName + | _ -> "" in - (* TODO: nicer loc on primitive? *) - Ast_helper.Str.primitive ~loc:valueDescription.pval_loc valueDescription - ) importJsClause - in match clause with - | [oneExternal] -> oneExternal - | clause -> - let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in - clause - |> Ast_helper.Mod.structure ~loc - |> Ast_helper.Incl.mk ~attrs ~loc - |> Ast_helper.Str.include_ ~loc + let moduleAttribute = (Location.mknoloc "module", Parsetree.PStr []) in + (* @module external leftPad: (string, int) => string = "left-pad" *) + Ast_helper.Val.mk ~loc:name.loc + ~attrs:(moduleAttribute::attrs) ~prim:[jsModuleName] name typ + + (* import {dirname: string => string} from "path" *) + | NamedImport {attrs; name; alias; typ} -> + (* module("path") external dirname: (string, string) => string = "dirname" *) + Ast_helper.Val.mk ~loc:name.loc + ~attrs:(fromClause::attrs) ~prim:[alias] name typ + in + valueDescription + ) importJsClause (* * ImportClause ::= @@ -6131,8 +6134,27 @@ and parseSignatureItemRegion p = let loc = mkLoc startPos p.prevEndPos in Some (Ast_helper.Sig.extension ~attrs ~loc extension) | Import -> - Parser.next p; - parseSignatureItemRegion p + let valueDescriptions = parseJsImportDeclaration ~startPos ~attrs p in + parseNewlineOrSemicolonStructure p; + let loc = mkLoc startPos p.prevEndPos in + let signatureItem = + let primitives = + List.map (fun valueDescription -> + Ast_helper.Sig.value ~loc:valueDescription.Parsetree.pval_loc valueDescription + ) valueDescriptions + in + match primitives with + | [oneExternal] -> oneExternal + | clause -> + let attrs = (Location.mknoloc "ns.jsFfi", Parsetree.PStr [])::attrs in + clause + |> Ast_helper.Mty.signature ~loc + |> Ast_helper.Incl.mk ~attrs ~loc + |> Ast_helper.Sig.include_ ~loc + in + Some {signatureItem with psig_loc = loc} + (* Parser.next p; *) + (* parseSignatureItemRegion p *) | _ -> begin match attrs with | (({Asttypes.loc = attrLoc}, _) as attr)::_ -> diff --git a/src/res_parsetree_viewer.ml b/src/res_parsetree_viewer.ml index a80d61e3..a2a5fadc 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -559,6 +559,20 @@ let extractValueDescriptionFromModExpr modExpr = | Pmod_structure structure -> loop structure [] | _ -> [] +let extractValueDescriptionFromModType modType = + let rec loop signature acc = + match signature with + | [] -> List.rev acc + | signatureItem::signature -> + begin match signatureItem.Parsetree.psig_desc with + | Psig_value vd -> loop signature (vd::acc) + | _ -> loop signature acc + end + in + match modType.pmty_desc with + | Pmty_signature signature -> loop signature [] + | _ -> [] + type jsModuleFlavour = (* import ceo: string from "company" *) | JsDefaultImport of string diff --git a/src/res_parsetree_viewer.mli b/src/res_parsetree_viewer.mli index f3224722..25fd7d14 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -124,6 +124,7 @@ val isBracedExpr : Parsetree.expression -> bool val isSinglePipeExpr : Parsetree.expression -> bool val extractValueDescriptionFromModExpr: Parsetree.module_expr -> Parsetree.value_description list +val extractValueDescriptionFromModType: Parsetree.module_type -> Parsetree.value_description list type jsModuleFlavour = (* import ceo: string from "company" *) diff --git a/src/res_printer.ml b/src/res_printer.ml index a1037955..7a5dfcf2 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -910,11 +910,26 @@ and printOpenDescription (openDescription : Parsetree.open_description) cmtTbl = ] and printIncludeDescription (includeDescription: Parsetree.include_description) cmtTbl = - Doc.concat [ - printAttributes includeDescription.pincl_attributes cmtTbl; - Doc.text "include "; - printModType includeDescription.pincl_mod cmtTbl; - ] + let isJsFfiImport = List.exists (fun attr -> match attr with + | ({Location.txt = "ns.jsFfi"}, _) -> true + | _ -> false + ) includeDescription.pincl_attributes + in + if isJsFfiImport then + let attrs = List.filter (fun attr -> + match attr with + | ({Location.txt = "ns.jsFfi"}, _) -> false + | _ -> true + ) includeDescription.pincl_attributes + in + let imports = ParsetreeViewer.extractValueDescriptionFromModType includeDescription.pincl_mod in + printJsFfiImportDeclaration ~attrs ~imports cmtTbl + else + Doc.concat [ + printAttributes includeDescription.pincl_attributes cmtTbl; + Doc.text "include "; + printModType includeDescription.pincl_mod cmtTbl; + ] and printIncludeDeclaration (includeDeclaration : Parsetree.include_declaration) cmtTbl = let isJsFfiImport = List.exists (fun attr -> diff --git a/tests/printer/other/__snapshots__/render.spec.js.snap b/tests/printer/other/__snapshots__/render.spec.js.snap index fdd357b4..dc1818d8 100644 --- a/tests/printer/other/__snapshots__/render.spec.js.snap +++ b/tests/printer/other/__snapshots__/render.spec.js.snap @@ -546,6 +546,174 @@ import * as copyToClipboard: string => unit from \\"copy-to-clipboard\\" " `; +exports[`import.resi 1`] = ` +"// Import a single export from a module +import {dirname: string => string} from \\"path\\" + +// old external +import {dirname: string => string} from \\"path\\" + +// Import multiple exports from a module +import { + delimiter: string, + dirname: string => string, + relative: (~from: string, ~\\\\\\"to\\": string) => string, +} from \\"path\\" + +// old external +import {delimiter: string} from \\"path\\" +import {dirname: (string, string) => string} from \\"path\\" +import {relative: (~from: string, ~\\\\\\"to\\": string) => string} from \\"path\\" + +// Import an export with a more convenient alias +import {renameSync as rename: (string, string) => string} from \\"fs\\" + +import {renameSync as rename: (string, string) => string} from \\"fs\\" + +// import an export with an illegal ReScript identifier +import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"react\\" + +// old external +import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"react\\" + +// import an export with additional attributes +import {@splice join: array => string} from \\"path\\" + +// old external +import { + @splice + join: array => string, +} from \\"path\\" + +// Rename multiple exports during import +import { + renameSync as rename: (string, string) => string, + unlinkSync as unlink: string => unit, +} from \\"fs\\" + +// old external +import {renameSync as rename: (string, string) => string} from \\"path\\" +import {unlinkSync as unlink: string => unit} from \\"path\\" + +// import an entire module's contents (aka NameSpacedImport) +import * as leftPad: (string, int) => string from \\"leftPad\\" + +// old external +import * as leftPad: (string, int) => string from \\"leftPad\\" + +// Importing default export +import schoolName: string from \\"/modules/school.js\\" +// syntactic sugar for +import schoolName: string from \\"/modules/school.js\\" + +// old external +import schoolName: string from \\"/modules/school.js\\" + +// importing default export with named imports +import schoolName: string, {getGrade: student => int} from \\"/modules/school.js\\" + +// old external +import schoolName: string from \\"/modules/school.js\\" +import {getGrade: student => int} from \\"/modules/school.js\\" + +// importing default export with multiple named imports +import + schoolName: string, + { + getGrade: student => int, + getTeachers: unit => array + } +from \\"/modules/school.js\\" + +// old external +import schoolName: string from \\"/modules/school.js\\" +import {getGrade: student => int} from \\"/modules/school.js\\" +import {getTeachers: unit => array} from \\"/modules/school.js\\" + +// import values from JavaScript modules with @as and polyvariants +import { + openSync: ( + path, + @bs.string + [ + | @bs.as(\\"r\\") #Read + | @bs.as(\\"r+\\") #Read_write + | @bs.as(\\"rs+\\") #Read_write_sync + | @bs.as(\\"w\\") #Write + | @bs.as(\\"wx\\") #Write_fail_if_exists + | @bs.as(\\"w+\\") #Write_read + | @bs.as(\\"wx+\\") #Write_read_fail_if_exists + | @bs.as(\\"a\\") #Append + | @bs.as(\\"ax\\") #Append_fail_if_exists + | @bs.as(\\"a+\\") #Append_read + | @bs.as(\\"ax+\\") #Append_read_fail_if_exists + ], + ) => unit, +} from \\"fs\\" + +// old external +import { + openSync: ( + path, + @bs.string + [ + | @bs.as(\\"r\\") #Read + | @bs.as(\\"r+\\") #Read_write + | @bs.as(\\"rs+\\") #Read_write_sync + | @bs.as(\\"w\\") #Write + | @bs.as(\\"wx\\") #Write_fail_if_exists + | @bs.as(\\"w+\\") #Write_read + | @bs.as(\\"wx+\\") #Write_read_fail_if_exists + | @bs.as(\\"a\\") #Append + | @bs.as(\\"ax\\") #Append_fail_if_exists + | @bs.as(\\"a+\\") #Append_read + | @bs.as(\\"ax+\\") #Append_read_fail_if_exists + ], + ) => unit, +} from \\"fs\\" + +// import a react component from a module +import { + @react.component + \\"Circle\\" as make: ( + ~center: Leaflet.point, + ~radius: int, + ~opacity: float, + ~stroke: bool, + ~color: string, + ~children: React.element=?, + ) => React.element, +} from \\"react-leaflet\\" + +module type JsComponent = { + import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element + from \\"./DatePicker\\" +} + +import @attr * as leftPad: (string, int) => string from \\"leftPad\\" +import @attr * as leftPad: (string, int) => string from \\"leftPad\\" + +import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element, + @attr + * as dateLib: 'a +from \\"./DatePicker\\" + +import * as copyToClipboard: string => unit from \\"copy-to-clipboard\\" +" +`; + exports[`lor.js 1`] = ` "let lower = ch => lor(32, ch) " diff --git a/tests/printer/other/import.resi b/tests/printer/other/import.resi new file mode 100644 index 00000000..9a9b5e75 --- /dev/null +++ b/tests/printer/other/import.resi @@ -0,0 +1,173 @@ +// Import a single export from a module +import { dirname: string => string } from "path" + +// old external +@module("path") +external dirname: string => string = "dirname" + +// Import multiple exports from a module +import { + delimiter: string, + dirname: string => string, + relative: (~from: string, ~\"to": string) => string +} from "path" + +// old external +@module("path") +external delimiter: string = "delimiter" +@module("path") +external dirname: (string, string) => string = "dirname" +@module("path") +external relative: (~from: string, ~\"to": string) => string = "relative" + +// Import an export with a more convenient alias +import {renameSync as rename: (string, string) => string} from "fs" + +@module("fs") +external rename: (string, string) => string = "renameSync" + +// import an export with an illegal ReScript identifier +import {"Fragment" as make: component<{"children": element}>} from "react" + +// old external +@module("react") +external make: component<{"children": element}> = "Fragment" + +// import an export with additional attributes +import { + @splice + join: array => string +} from "path" + +// old external +@module("path") @splice +external join: array => string = "join" + +// Rename multiple exports during import +import { + renameSync as rename: (string, string) => string, + unlinkSync as unlink: string => unit, +} from "fs" + +// old external +@module("path") +external rename: (string, string) => string = "renameSync" +@module("path") +external unlink: string => unit = "unlinkSync" + +// import an entire module's contents (aka NameSpacedImport) +import * as leftPad: (string, int) => string from "leftPad" + +// old external +@module +external leftPad: (string, int) => string = "leftPad" + +// Importing default export +import schoolName: string from "/modules/school.js"; +// syntactic sugar for +import {default as schoolName: string} from "/modules/school.js"; + +// old external +@module("/modules/school.js") external schoolName: string = "default" + +// importing default export with named imports +import schoolName: string, {getGrade: student => int} from "/modules/school.js"; + +// old external +@module("/modules/school.js") external schoolName: string = "default" +@module("/modules/school.js") external getGrade: student => int = "getGrade" + +// importing default export with multiple named imports +import + schoolName: string, + { + getGrade: student => int, + getTeachers: unit => array + } +from "/modules/school.js"; + +// old external +@module("/modules/school.js") external schoolName: string = "default" +@module("/modules/school.js") external getGrade: student => int = "getGrade" +@module("/modules/school.js") external getTeachers: unit => array = "getTeachers" + + +// import values from JavaScript modules with @as and polyvariants +import { + openSync: ( + path, + @bs.string [ + | @bs.as("r") #Read + | @bs.as("r+") #Read_write + | @bs.as("rs+") #Read_write_sync + | @bs.as("w") #Write + | @bs.as("wx") #Write_fail_if_exists + | @bs.as("w+") #Write_read + | @bs.as("wx+") #Write_read_fail_if_exists + | @bs.as("a") #Append + | @bs.as("ax") #Append_fail_if_exists + | @bs.as("a+") #Append_read + | @bs.as("ax+") #Append_read_fail_if_exists + ], + ) => unit +} from "fs" + +// old external +@module("fs") +external openSync: ( + path, + @bs.string + [ + | @bs.as("r") #Read + | @bs.as("r+") #Read_write + | @bs.as("rs+") #Read_write_sync + | @bs.as("w") #Write + | @bs.as("wx") #Write_fail_if_exists + | @bs.as("w+") #Write_read + | @bs.as("wx+") #Write_read_fail_if_exists + | @bs.as("a") #Append + | @bs.as("ax") #Append_fail_if_exists + | @bs.as("a+") #Append_read + | @bs.as("ax+") #Append_read_fail_if_exists + ], +) => unit = "openSync" + +// import a react component from a module +import { + @react.component + "Circle" as make: ( + ~center: Leaflet.point, + ~radius: int, + ~opacity: float, + ~stroke: bool, + ~color: string, + ~children: React.element=? + ) => React.element +} from "react-leaflet" + +module type JsComponent = { + import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element from "./DatePicker" +} + + + +import @attr * as leftPad: (string, int) => string from "leftPad" +import @attr * as leftPad: (string, int) => string from "leftPad" + +import + @react.component + make: ( + ~id: string=?, + ~onChange: Js.Nullable.t => unit, + ~selected: Js.Date.t=?, + ) => React.element, + @attr * as dateLib: 'a +from "./DatePicker" + +import * as copyToClipboard: string => unit from "copy-to-clipboard" From 7a900e7ee33235a5d34aa727b8d79688eef19ec5 Mon Sep 17 00:00:00 2001 From: Iwan Date: Sun, 3 Jan 2021 16:19:04 +0100 Subject: [PATCH 4/7] Handle empty primitives in externals. @module("mat4") external create: unit => t = "" --- src/res_printer.ml | 8 +++----- tests/printer/other/__snapshots__/render.spec.js.snap | 3 +++ tests/printer/other/import.res | 3 +++ 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/res_printer.ml b/src/res_printer.ml index 7a5dfcf2..6f1ec0ed 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -984,7 +984,7 @@ and printJsFfiImport (valueDescription: Parsetree.value_description) cmtTbl = | JsNamedImport _ -> let (ident, alias) = match valueDescription.pval_prim with | primitive::_ -> - if primitive <> valueDescription.pval_name.txt then + if primitive <> "" && primitive <> valueDescription.pval_name.txt then ( printJsExportedIdentLike primitive, Doc.concat [ @@ -993,7 +993,8 @@ and printJsFfiImport (valueDescription: Parsetree.value_description) cmtTbl = ] ) else - (printIdentLike primitive, Doc.nil) + (* handle: @module("mat4") external create: unit => t = "" *) + (printIdentLike valueDescription.pval_name.txt, Doc.nil) | _ -> (printIdentLike valueDescription.pval_name.txt, Doc.nil) in @@ -1070,9 +1071,6 @@ and printJsImportClause vds cmtTbl = ] and printJsFfiImportDeclaration ~attrs ~imports cmtTbl = - -(* (includeDeclaration: Parsetree.include_declaration) cmtTbl = *) - (* FromClause *) let fromClauseDoc = match imports with | vd::_ -> begin match ParsetreeViewer.classifyJsModuleFlavour vd with diff --git a/tests/printer/other/__snapshots__/render.spec.js.snap b/tests/printer/other/__snapshots__/render.spec.js.snap index dc1818d8..a26b218c 100644 --- a/tests/printer/other/__snapshots__/render.spec.js.snap +++ b/tests/printer/other/__snapshots__/render.spec.js.snap @@ -543,6 +543,9 @@ import from \\"./DatePicker\\" import * as copyToClipboard: string => unit from \\"copy-to-clipboard\\" + +// handle \\"\\" +import {create: unit => t} from \\"mat4\\" " `; diff --git a/tests/printer/other/import.res b/tests/printer/other/import.res index f1b2b8c9..2607e524 100644 --- a/tests/printer/other/import.res +++ b/tests/printer/other/import.res @@ -171,3 +171,6 @@ import from "./DatePicker" import * as copyToClipboard: string => unit from "copy-to-clipboard" + +// handle "" +@module("mat4") external create: unit => t = "" From 00a4232b646de2063c2124d959493ccc3b164cbb Mon Sep 17 00:00:00 2001 From: Iwan Date: Sun, 3 Jan 2021 21:52:38 +0100 Subject: [PATCH 5/7] Improve printing of attributes + improve conversion from ml/reason 1) attributes above import clauses, will be formatted above. 2) conversion: @bs.module now becomes automatically @module All @module annotations also are parsed in first position. (like you would write them in front) Updated the test data to have all @module annotations in first position. Otherwise roundtrip tests wouldn't succeed. --- src/res_ast_conversion.ml | 5 ++++ src/res_core.ml | 4 +-- src/res_printer.ml | 3 +- .../idempotency/pupilfirst/shared/DateTime.re | 4 +-- tests/idempotency/reason-react/src/React.re | 2 +- .../reason-react/src/ReactDOMRe.re | 19 ++++++------ .../reason-react/src/ReactDOMServerRe.re | 4 +-- .../reason-react/src/ReasonReact.rei | 4 +-- tests/idempotency/reasongl/reasongl_web.ml | 24 +++++++-------- .../other/__snapshots__/render.spec.js.snap | 29 ++++++++++++++++--- tests/printer/other/fatSlider.res | 2 +- tests/printer/other/import.res | 19 ++++++++++-- tests/printer/structure/include.js | 2 +- 13 files changed, 78 insertions(+), 43 deletions(-) diff --git a/src/res_ast_conversion.ml b/src/res_ast_conversion.ml index 1761d73b..e2eb0c1e 100644 --- a/src/res_ast_conversion.ml +++ b/src/res_ast_conversion.ml @@ -332,6 +332,11 @@ let normalize = )}, _) -> false | _ ->true ) + |> List.map (fun attr -> match attr with + | ({Location.txt = "bs.module"} as loc, payload) -> + ({loc with txt = "module"}, payload) + | attr -> attr + ) |> default_mapper.attributes mapper ); pat = begin fun mapper p -> diff --git a/src/res_core.ml b/src/res_core.ml index 24d21417..7a6f2df4 100644 --- a/src/res_core.ml +++ b/src/res_core.ml @@ -5276,8 +5276,8 @@ and parseJsImportDeclaration ~startPos:_startPos ~attrs:_ p = * attributes? ImportedDefaultBinding , NamedImports *) and parseJsImportClause p = - let startPos = p.Parser.startPos in let attrs = parseAttributes p in + let startPos = p.Parser.startPos in match p.token with (* ImportedDefaultBinding *) | Token.Lident ident | String ident -> @@ -5363,8 +5363,8 @@ and parseJsNamedImports p = * IdentifierName as ImportedBinding : typexpr *) and parseJsImportsListItemRegion p = - let startPos = p.Parser.startPos in let attrs = parseAttributes p in + let startPos = p.Parser.startPos in match p.token with | Token.Lident ident | String ident -> let identLoc = mkLoc startPos p.endPos in diff --git a/src/res_printer.ml b/src/res_printer.ml index 6f1ec0ed..bdbc381e 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -4803,6 +4803,7 @@ and printBsObjectRow (lbl, expr) cmtTbl = * type t = string` -> attr is on prev line, print the attributes * with a line break between, we respect the users' original layout *) and printAttributes ?loc ?(inline=false) (attrs: Parsetree.attributes) cmtTbl = + match ParsetreeViewer.filterParsingAttrs attrs with | [] -> Doc.nil | attrs -> @@ -4810,7 +4811,7 @@ and printAttributes ?loc ?(inline=false) (attrs: Parsetree.attributes) cmtTbl = | None -> Doc.line | Some loc -> begin match List.rev attrs with | ({loc = firstLoc}, _)::_ when loc.loc_start.pos_lnum > firstLoc.loc_end.pos_lnum -> - Doc.hardLine; + Doc.hardLine | _ -> Doc.line end in diff --git a/tests/idempotency/pupilfirst/shared/DateTime.re b/tests/idempotency/pupilfirst/shared/DateTime.re index e2e8d587..b9dfaf5a 100644 --- a/tests/idempotency/pupilfirst/shared/DateTime.re +++ b/tests/idempotency/pupilfirst/shared/DateTime.re @@ -1,9 +1,9 @@ type t = Js.Date.t; -[@bs.val] [@bs.module "date-fns"] +[@bs.module "date-fns"] [@bs.val] external dateFormat: (t, string) => string = "format"; -[@bs.val] [@bs.module "date-fns"] external dateParse: string => t = "parse"; +[@bs.module "date-fns"] [@bs.val] external dateParse: string => t = "parse"; let parse = s => s |> dateParse; diff --git a/tests/idempotency/reason-react/src/React.re b/tests/idempotency/reason-react/src/React.re index 68347890..9c694f1a 100644 --- a/tests/idempotency/reason-react/src/React.re +++ b/tests/idempotency/reason-react/src/React.re @@ -16,7 +16,7 @@ external createElement: (component('props), 'props) => element = "createElement" [@bs.module "react"] external cloneElement: (component('props), 'props) => element = "cloneElement"; -[@bs.splice] [@bs.module "react"] +[@bs.module "react"] [@bs.splice] external createElementVariadic: (component('props), 'props, array(element)) => element = "createElement"; diff --git a/tests/idempotency/reason-react/src/ReactDOMRe.re b/tests/idempotency/reason-react/src/ReactDOMRe.re index d61b3e91..06f5a29e 100644 --- a/tests/idempotency/reason-react/src/ReactDOMRe.re +++ b/tests/idempotency/reason-react/src/ReactDOMRe.re @@ -4,7 +4,7 @@ that takes in a reactElement, a dom element, and returns unit (nothing) */ /* It's like `let`, except you're pointing the implementation to the JS side. The compiler will inline these calls and add the appropriate `require("react-dom")` in the file calling this `render` */ -[@bs.val] [@bs.module "react-dom"] +[@bs.module "react-dom"] [@bs.val] external render: (React.element, Dom.element) => unit = "render"; [@bs.val] @@ -41,7 +41,7 @@ let renderToElementWithId = (reactElement, id) => | Some(element) => render(reactElement, element) }; -[@bs.val] [@bs.module "react-dom"] +[@bs.module "react-dom"] [@bs.val] external hydrate: (React.element, Dom.element) => unit = "hydrate"; let hydrateToElementWithClassName = (reactElement, className) => @@ -70,16 +70,16 @@ let hydrateToElementWithId = (reactElement, id) => | Some(element) => hydrate(reactElement, element) }; -[@bs.val] [@bs.module "react-dom"] +[@bs.module "react-dom"] [@bs.val] external createPortal: (React.element, Dom.element) => React.element = "createPortal"; -[@bs.val] [@bs.module "react-dom"] +[@bs.module "react-dom"] [@bs.val] external unmountComponentAtNode: Dom.element => unit = "unmountComponentAtNode"; -[@bs.val] [@bs.module "react-dom"] +[@bs.module "react-dom"] [@bs.val] external findDOMNode: ReasonReact.reactRef => Dom.element = "findDOMNode"; external domElementToObj: Dom.element => Js.t({..}) = "%identity"; @@ -1094,7 +1094,7 @@ type domProps = { suppressContentEditableWarning: bool, }; -[@bs.splice] [@bs.module "react"] +[@bs.module "react"] [@bs.splice] external createDOMElementVariadic: (string, ~props: domProps=?, array(React.element)) => React.element = @@ -2102,9 +2102,8 @@ external objToDOMProps: Js.t({..}) => props = "%identity"; [@deprecated "Please use ReactDOMRe.props instead"] type reactDOMProps = props; -[@bs.splice] [@bs.val] [@bs.module "react"] -external createElement: - (string, ~props: props=?, array(React.element)) => +[@bs.module "react"] [@bs.splice] [@bs.val]external createElement: + (string, ~props: props=?, array(React.element)) => React.element = "createElement"; @@ -2112,7 +2111,7 @@ external createElement: include ( /* Use varargs to avoid the ReactJS warning for duplicate keys in children */ { - [@bs.val] [@bs.module "react"] + [@bs.module "react"] [@bs.val] external createElementInternalHack: 'a = "createElement"; [@bs.send] external apply: diff --git a/tests/idempotency/reason-react/src/ReactDOMServerRe.re b/tests/idempotency/reason-react/src/ReactDOMServerRe.re index 0ed46bad..04254c72 100644 --- a/tests/idempotency/reason-react/src/ReactDOMServerRe.re +++ b/tests/idempotency/reason-react/src/ReactDOMServerRe.re @@ -1,7 +1,7 @@ -[@bs.val] [@bs.module "react-dom/server"] +[@bs.module "react-dom/server"] [@bs.val] external renderToString : React.element => string = "renderToString"; -[@bs.val] [@bs.module "react-dom/server"] +[@bs.module "react-dom/server"] [@bs.val] external renderToStaticMarkup : React.element => string = "renderToStaticMarkup"; diff --git a/tests/idempotency/reason-react/src/ReasonReact.rei b/tests/idempotency/reason-react/src/ReasonReact.rei index d91f3dba..d7d3c359 100644 --- a/tests/idempotency/reason-react/src/ReasonReact.rei +++ b/tests/idempotency/reason-react/src/ReasonReact.rei @@ -35,12 +35,12 @@ external refToJsObj: reactRef => Js.t({..}) = "%identity"; In every other case, you should be using the JSX */ -[@bs.splice] [@bs.val] [@bs.module "react"] +[@bs.module "react"] [@bs.splice] [@bs.val] external createElement: (reactClass, ~props: Js.t({..})=?, array(reactElement)) => reactElement = "createElement"; -[@bs.splice] [@bs.module "react"] +[@bs.module "react"] [@bs.splice] external cloneElement: (reactElement, ~props: Js.t({..})=?, array(reactElement)) => reactElement = "cloneElement"; diff --git a/tests/idempotency/reasongl/reasongl_web.ml b/tests/idempotency/reasongl/reasongl_web.ml index 85dc2be5..698c78a5 100644 --- a/tests/idempotency/reasongl/reasongl_web.ml +++ b/tests/idempotency/reasongl/reasongl_web.ml @@ -708,33 +708,29 @@ module Gl : RGLInterface.t = struct type t = float array let to_array a = a - external create : unit -> t = ""[@@bs.scope "mat4"][@@bs.module - "gl-matrix"] - external identity : out:t -> unit = ""[@@bs.scope "mat4"][@@bs.module - "gl-matrix"] + external create : unit -> t = "create"[@@bs.module "gl-matrix"][@@bs.scope "mat4"] + external identity : out:t -> unit = "identity"[@@bs.module "gl-matrix"][@@bs.scope "mat4"] external translate : - out:t -> matrix:t -> vec:float array -> unit = ""[@@bs.scope - "mat4"][@@bs.module - "gl-matrix"] - external scale : out:t -> matrix:t -> vec:float array -> unit = "" - [@@bs.scope "mat4"][@@bs.module "gl-matrix"] + out:t -> matrix:t -> vec:float array -> unit = "translate"[@@bs.module "gl-matrix"][@@bs.scope "mat4"] + external scale : out:t -> matrix:t -> vec:float array -> unit = "scale" + [@@bs.module "gl-matrix"] [@@bs.scope "mat4"] external rotate : - out:t -> matrix:t -> rad:float -> vec:float array -> unit = "" - [@@bs.scope "mat4"][@@bs.module "gl-matrix"] + out:t -> matrix:t -> rad:float -> vec:float array -> unit = "rotate" + [@@bs.module "gl-matrix"] [@@bs.scope "mat4"] external ortho : out:t -> left:float -> right:float -> bottom:float -> top:float -> near:float -> far:float -> unit - = ""[@@bs.scope "mat4"][@@bs.module "gl-matrix"] + = "ortho"[@@bs.module "gl-matrix"][@@bs.scope "mat4"] external perspective : out:t -> fovy:float -> aspect:float -> near:float -> far:float -> unit = - ""[@@bs.scope "mat4"][@@bs.module "gl-matrix"] + "perspective"[@@bs.module "gl-matrix"] [@@bs.scope "mat4"] external lookAt : out:t -> eye:float array -> center:float array -> up:float array -> unit = - ""[@@bs.scope "mat4"][@@bs.module "gl-matrix"] + "lookA"[@@bs.module "gl-matrix"][@@bs.scope "mat4"] end external uniform1i : context:contextT -> location:uniformT -> value:int -> unit = diff --git a/tests/printer/other/__snapshots__/render.spec.js.snap b/tests/printer/other/__snapshots__/render.spec.js.snap index a26b218c..7bcf629e 100644 --- a/tests/printer/other/__snapshots__/render.spec.js.snap +++ b/tests/printer/other/__snapshots__/render.spec.js.snap @@ -409,7 +409,10 @@ import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"re import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"react\\" // import an export with additional attributes -import {@splice join: array => string} from \\"path\\" +import { + @splice + join: array => string, +} from \\"path\\" // old external import { @@ -528,9 +531,24 @@ module JsComponent = { from \\"./DatePicker\\" } -import @attr * as leftPad: (string, int) => string from \\"leftPad\\" +// attribute on same line, print attribute on same line import @attr * as leftPad: (string, int) => string from \\"leftPad\\" +// attribute above, print attribute above +import + @attr + * as leftPad: (string, int) => string +from \\"leftPad\\" + +// attribute on same line, print attribute on same line +import @runtime collect: unit => unit from \\"gc\\" + +// attribute above, print attribute above +import + @runtime + collect: unit => unit +from \\"gc\\" + import @react.component make: ( @@ -545,7 +563,7 @@ from \\"./DatePicker\\" import * as copyToClipboard: string => unit from \\"copy-to-clipboard\\" // handle \\"\\" -import {create: unit => t} from \\"mat4\\" +/* @module(\\"mat4\\") external create: unit => t = \\"\\" */ " `; @@ -580,7 +598,10 @@ import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"re import {\\"Fragment\\" as make: component<{\\"children\\": element}>} from \\"react\\" // import an export with additional attributes -import {@splice join: array => string} from \\"path\\" +import { + @splice + join: array => string, +} from \\"path\\" // old external import { diff --git a/tests/printer/other/fatSlider.res b/tests/printer/other/fatSlider.res index ef463ad0..8e7fb24d 100644 --- a/tests/printer/other/fatSlider.res +++ b/tests/printer/other/fatSlider.res @@ -11,7 +11,7 @@ module Range = { children: React.element, } - @bs.module("react-range") @react.component + @module("react-range") @react.component external make: ( ~min: int, ~max: int, diff --git a/tests/printer/other/import.res b/tests/printer/other/import.res index 2607e524..8fe84ef3 100644 --- a/tests/printer/other/import.res +++ b/tests/printer/other/import.res @@ -155,10 +155,23 @@ module JsComponent = { ) => React.element from "./DatePicker" } +// attribute on same line, print attribute on same line +import @attr * as leftPad: (string, int) => string from "leftPad" +// attribute above, print attribute above +import + @attr + * as leftPad: (string, int) => string +from "leftPad" -import @attr * as leftPad: (string, int) => string from "leftPad" -import @attr * as leftPad: (string, int) => string from "leftPad" +// attribute on same line, print attribute on same line +import @runtime collect: unit => unit from "gc" + +// attribute above, print attribute above +import + @runtime + collect: unit => unit +from "gc" import @react.component @@ -173,4 +186,4 @@ from "./DatePicker" import * as copyToClipboard: string => unit from "copy-to-clipboard" // handle "" -@module("mat4") external create: unit => t = "" +/* @module("mat4") external create: unit => t = "" */ diff --git a/tests/printer/structure/include.js b/tests/printer/structure/include.js index 16ca0263..812c34d2 100644 --- a/tests/printer/structure/include.js +++ b/tests/printer/structure/include.js @@ -6,7 +6,7 @@ include WebGl include ( /* Use varargs to avoid the ReactJS warning for duplicate keys in children */ { - @bs.val @bs.module("react") + @module("react") @bs.val external createElementInternalHack: 'a = "createElement" @bs.send external apply: ( From 913c6861f7c537172f855d96fec3cb83daa70133 Mon Sep 17 00:00:00 2001 From: Iwan Date: Mon, 4 Jan 2021 08:53:31 +0100 Subject: [PATCH 6/7] Update Makefile; remove references to deleted res_js_ffi --- Makefile | 1 - 1 file changed, 1 deletion(-) diff --git a/Makefile b/Makefile index 83e21b28..d3c2843a 100644 --- a/Makefile +++ b/Makefile @@ -27,7 +27,6 @@ API_FILES = \ src/res_comments_table.cmx\ src/res_printer.cmx\ src/res_scanner.cmx\ - src/res_js_ffi.cmx\ src/res_parser.cmx\ src/res_core.cmx\ src/res_driver.cmx \ From 83aef29d9a9ab11cac933bfc4312bb62cd86888d Mon Sep 17 00:00:00 2001 From: Iwan Date: Mon, 4 Jan 2021 19:32:56 +0100 Subject: [PATCH 7/7] Format @genType attribute as normal attribute. We can continue iterating on this later. --- src/res_printer.ml | 13 +++++-------- .../reason/__snapshots__/render.spec.js.snap | 11 ++++++----- 2 files changed, 11 insertions(+), 13 deletions(-) diff --git a/src/res_printer.ml b/src/res_printer.ml index bdbc381e..970e6148 100644 --- a/src/res_printer.ml +++ b/src/res_printer.ml @@ -1109,10 +1109,10 @@ and printValueDescription valueDescription cmtTbl = if ParsetreeViewer.hasModuleExternalAttribute valueDescription.pval_attributes then printJsFfiImportDeclaration ~attrs:[] ~imports:[valueDescription] cmtTbl else - let (hasGenType, attrs) = ParsetreeViewer.splitGenTypeAttr valueDescription.pval_attributes in + let attrs = valueDescription.pval_attributes in let attrs = printAttributes ~loc:valueDescription.pval_name.loc attrs cmtTbl in let header = - if isExternal then "external " else (if hasGenType then "export " else "let ") in + if isExternal then "external " else "let " in Doc.group ( Doc.concat [ attrs; @@ -1186,16 +1186,13 @@ and printTypeDeclarations ~recFlag typeDeclarations cmtTbl = * | Ptype_open *) and printTypeDeclaration ~name ~equalSign ~recFlag i (td: Parsetree.type_declaration) cmtTbl = - let (hasGenType, attrs) = ParsetreeViewer.splitGenTypeAttr td.ptype_attributes in + let attrs = td.ptype_attributes in let attrs = printAttributes ~loc:td.ptype_loc attrs cmtTbl in let prefix = if i > 0 then - Doc.concat [ - Doc.text "and "; - if hasGenType then Doc.text "export " else Doc.nil - ] + Doc.text "and " else Doc.concat [ - Doc.text (if hasGenType then "export type " else "type "); + Doc.text "type "; recFlag ] in diff --git a/tests/conversion/reason/__snapshots__/render.spec.js.snap b/tests/conversion/reason/__snapshots__/render.spec.js.snap index 05fe37ab..7eee09f2 100644 --- a/tests/conversion/reason/__snapshots__/render.spec.js.snap +++ b/tests/conversion/reason/__snapshots__/render.spec.js.snap @@ -1049,8 +1049,8 @@ exports[`gentype.re 1`] = ` @after export type t - @after - export x: int + @genType @after + let x: int @foo type e = .. @@ -1064,8 +1064,8 @@ module type MT = { @after export type t - @after - export x: int + @genType @after + let x: int @foo type e = .. @@ -1077,7 +1077,8 @@ let x = 42 `; exports[`gentype.rei 1`] = ` -"export x: int +"@genType +let x: int " `;