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/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 \ 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 051f524f..7a6f2df4 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.{ @@ -133,6 +132,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 +615,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 +5156,24 @@ 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 valueDescriptions = parseJsImportDeclaration ~startPos ~attrs p in parseNewlineOrSemicolonStructure p; let loc = mkLoc startPos p.prevEndPos in - let structureItem = JsFfi.toParsetree importDescr 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 @@ -5202,20 +5227,186 @@ and parseStructureItemRegion p = None end -and parseJsImport ~startPos ~attrs p = +(* import ImportClause FromClause *) +and parseJsImportDeclaration ~startPos:_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 + let importJsClause = parseJsImportClause p in + let fromClause = parseFromClause p in + 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 in - JsFfi.Default decl - | _ -> JsFfi.Spec(parseJsFfiDeclarations p) + valueDescription + ) importJsClause + +(* + * ImportClause ::= + * attributes? ImportedDefaultBinding + * attributes? NameSpaceImport + * NamedImports + * attributes? ImportedDefaultBinding , attributes? NameSpaceImport + * attributes? ImportedDefaultBinding , NamedImports + *) +and parseJsImportClause p = + let attrs = parseAttributes p in + 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 = attrs; + name = Location.mkloc ident identLoc; + alias = "default"; + typ + } in + 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 ~attrs p] + | _ -> [default] + end + | _ -> + [default] + end + | Asterisk -> + [parseNameSpaceImport ~attrs p] + (* NamedImports *) + | Lbrace -> + parseJsNamedImports p + | _ -> + assert false + +(* + * NameSpaceImport : + * * as ImportedBinding + *) +and parseNameSpaceImport ~attrs 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 = 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 - let scope = parseJsFfiScope p in - let loc = mkLoc startPos p.prevEndPos in - JsFfi.importDescr ~attrs ~importSpec ~scope ~loc + Parser.expect Rbrace p; + namedImports + +(* + * ImportsListItem : + * ImportSpecifier + * ImportsList , ImportSpecifier + * + * ImportSpecifier : + * ImportedBinding : typexpr + * IdentifierName as ImportedBinding : typexpr + *) +and parseJsImportsListItemRegion p = + 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 + 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 +5447,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 = @@ -5943,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_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..a2a5fadc 100644 --- a/src/res_parsetree_viewer.ml +++ b/src/res_parsetree_viewer.ml @@ -559,32 +559,51 @@ let extractValueDescriptionFromModExpr modExpr = | Pmod_structure structure -> loop structure [] | _ -> [] -type jsImportScope = - | JsGlobalImport (* nothing *) - | JsModuleImport of string (* from "path" *) - | JsScopedImport of string list (* window.location *) +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 [] + | _ -> [] -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 +614,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..25fd7d14 100644 --- a/src/res_parsetree_viewer.mli +++ b/src/res_parsetree_viewer.mli @@ -124,13 +124,17 @@ 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 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 +143,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..970e6148 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 [ @@ -901,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 -> @@ -915,7 +939,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; @@ -931,67 +962,83 @@ 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 <> "" && primitive <> valueDescription.pval_name.txt then + ( + printJsExportedIdentLike primitive, + Doc.concat [ + Doc.text " as "; + printIdentLike valueDescription.pval_name.txt; + ] + ) + else + (* handle: @module("mat4") external create: unit => t = "" *) + (printIdentLike valueDescription.pval_name.txt, 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 = - 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 - let scope = match imports with - | vd::_ -> ParsetreeViewer.classifyJsImport vd - | [] -> ParsetreeViewer.JsGlobalImport +and printJsImportClause vds cmtTbl = + let isNamedImport vd = match ParsetreeViewer.classifyJsModuleFlavour vd with + | JsNamedImport _ -> true + | _ -> false in - let scopeDoc = printJsFfiImportScope scope in - Doc.group ( + match vds with + | (first::_ as imports) when isNamedImport first -> + (* import {x: int, y: int} from "math" *) Doc.concat [ - printAttributes attrs cmtTbl; - Doc.text "import "; - Doc.group ( + 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 [ @@ -1001,12 +1048,50 @@ and printJsFfiImportDeclaration (includeDeclaration: Parsetree.include_declarati ) ] ); - Doc.trailingComma; Doc.softLine; Doc.rbrace; ] ); - scopeDoc; + 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 = + let fromClauseDoc = match imports with + | vd::_ -> + begin match ParsetreeViewer.classifyJsModuleFlavour vd with + | ParsetreeViewer.JsNamespacedImport moduleName + | JsDefaultImport moduleName + | JsNamedImport moduleName -> + Doc.concat [ + Doc.text "from "; + Doc.doubleQuote; + Doc.text moduleName; + Doc.doubleQuote; + ] + end + | [] -> Doc.nil + in + Doc.group ( + Doc.concat [ + printAttributes attrs cmtTbl; + Doc.text "import"; + printJsImportClause imports cmtTbl; + fromClauseDoc ] ) @@ -1021,10 +1106,13 @@ and printValueDescription valueDescription cmtTbl = let isExternal = match valueDescription.pval_prim with | [] -> false | _ -> true in - let (hasGenType, attrs) = ParsetreeViewer.splitGenTypeAttr valueDescription.pval_attributes in + if ParsetreeViewer.hasModuleExternalAttribute valueDescription.pval_attributes then + printJsFfiImportDeclaration ~attrs:[] ~imports:[valueDescription] cmtTbl + else + 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; @@ -1044,9 +1132,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 ); @@ -1098,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 @@ -3597,7 +3682,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 +4780,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 @@ -4715,6 +4800,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 -> @@ -4722,7 +4808,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/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 " `; 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/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap b/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap index 0fbaec0c..f251fbcc 100644 --- a/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/grammar/ffi/__snapshots__/parse.spec.js.snap @@ -21,14 +21,9 @@ 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\\")] - 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 b64fb735..78f9e7e0 100644 --- a/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap +++ b/tests/parsing/grammar/structure/__snapshots__/parse.spec.js.snap @@ -58,37 +58,7 @@ exports[`itemExtension.js 1`] = ` [%%itemExtension ][@@attrOnExtension ]" `; -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 ]" -`; +exports[`jsFfiSugar.js 1`] = `"external dirname : string -> string = \\"dirname\\"[@@module \\"path\\"]"`; exports[`letBinding.js 1`] = ` "let a = 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..f0577162 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..7bcf629e 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 @@ -376,6 +378,366 @@ 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\\" +} + +// 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: ( + ~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\\" + +// handle \\"\\" +/* @module(\\"mat4\\") external create: unit => t = \\"\\" */ +" +`; + +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/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 new file mode 100644 index 00000000..8fe84ef3 --- /dev/null +++ b/tests/printer/other/import.res @@ -0,0 +1,189 @@ +// 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" +} + +// 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: ( + ~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" + +// handle "" +/* @module("mat4") external create: unit => t = "" */ 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" diff --git a/tests/printer/structure/__snapshots__/render.spec.js.snap b/tests/printer/structure/__snapshots__/render.spec.js.snap index 5f7de27f..75090a2e 100644 --- a/tests/printer/structure/__snapshots__/render.spec.js.snap +++ b/tests/printer/structure/__snapshots__/render.spec.js.snap @@ -115,8 +115,10 @@ 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, 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: (