src/parser_utils/file_sig.ml (1,440 lines of code) (raw):
(*
* Copyright (c) Meta Platforms, Inc. and affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*)
module Ast_utils = Flow_ast_utils
module Ast = Flow_ast
module Result = Base.Result
open Flow_ast_visitor
module Make
(L : Loc_sig.S)
(Scope_api : Scope_api_sig.S with module L = L)
(Scope_builder : Scope_builder_sig.S with module L = L and module Api = Scope_api) =
struct
module L = L
type t = {
module_sig: module_sig;
declare_modules: (L.t * module_sig) SMap.t;
}
and options = {
module_ref_prefix: string option;
enable_enums: bool;
enable_relay_integration: bool;
relay_integration_module_prefix: string option;
}
and module_sig = {
requires: require list;
module_kind: module_kind;
type_exports_named: (string * (L.t * type_export)) list;
type_exports_star: (L.t * export_star) list;
}
and require =
| Require of {
source: L.t Ast_utils.source;
require_loc: L.t;
bindings: require_bindings option;
}
| ImportDynamic of {
source: L.t Ast_utils.source;
import_loc: L.t;
}
| Import0 of { source: L.t Ast_utils.source }
| Import of {
import_loc: L.t;
source: L.t Ast_utils.source;
named: imported_locs Nel.t SMap.t SMap.t;
ns: L.t Ast_utils.ident option;
types: imported_locs Nel.t SMap.t SMap.t;
typesof: imported_locs Nel.t SMap.t SMap.t;
typesof_ns: L.t Ast_utils.ident option;
}
and imported_locs = {
remote_loc: L.t;
local_loc: L.t;
}
and require_bindings =
| BindIdent of L.t Ast_utils.ident
| BindNamed of (L.t Ast_utils.ident * require_bindings) list
and module_kind =
| CommonJS of { mod_exp_loc: L.t option }
| ES of {
named: (string * (L.t * export)) list;
star: (L.t * export_star) list;
}
and export =
| ExportDefault of {
default_loc: L.t;
local: L.t Ast_utils.ident option;
}
| ExportNamed of {
loc: L.t;
kind: named_export_kind;
}
| ExportNs of {
loc: L.t;
star_loc: L.t;
source: L.t Ast_utils.source;
}
and named_export_kind =
| NamedDeclaration
| NamedSpecifier of {
local: L.t Ast_utils.ident;
source: L.t Ast_utils.source option;
}
and export_star =
| ExportStar of {
star_loc: L.t;
source: L.t Ast_utils.source;
}
and type_export =
| TypeExportNamed of {
loc: L.t;
kind: named_export_kind;
}
[@@deriving show]
type tolerable_error =
(* e.g. `module.exports.foo = 4` when not at the top level *)
| BadExportPosition of L.t
(* e.g. `foo(module)`, dangerous because `module` is aliased *)
| BadExportContext of string (* offending identifier *) * L.t
| SignatureVerificationError of L.t Signature_error.t
[@@deriving show]
type error = IndeterminateModuleType of L.t
type tolerable_t = t * tolerable_error list
let empty_module_sig =
{
requires = [];
module_kind = CommonJS { mod_exp_loc = None };
type_exports_named = [];
type_exports_star = [];
}
let empty = { module_sig = empty_module_sig; declare_modules = SMap.empty }
let default_opts =
{
module_ref_prefix = None;
enable_relay_integration = false;
enable_enums = false;
relay_integration_module_prefix = None;
}
module PP = struct
let string_of_option f = function
| None -> "None"
| Some x -> Printf.sprintf "Some (%s)" (f x)
let items_to_collection_string indent open_ close items =
let indent_str = String.make (indent * 2) ' ' in
let items_str =
items |> Base.List.map ~f:(Printf.sprintf "%s%s;\n" (indent_str ^ " ")) |> String.concat ""
in
Printf.sprintf "%s\n%s%s%s" open_ items_str indent_str close
let items_to_list_string indent items = items_to_collection_string indent "[" "]" items
let items_to_record_string indent items =
let items =
items |> Base.List.map ~f:(fun (label, value) -> Printf.sprintf "%s: %s" label value)
in
items_to_collection_string indent "{" "}" items
end
let to_string t =
let string_of_module_sig module_sig =
let string_of_require_list require_list =
let string_of_require_bindings = function
| BindIdent (_, name) -> Printf.sprintf "BindIdent: %s" name
| BindNamed named ->
Printf.sprintf
"BindNamed: %s"
(String.concat ", " @@ Base.List.map ~f:(fun ((_, name), _) -> name) named)
in
let string_of_require = function
| Require { source = (_, name); bindings; _ } ->
Printf.sprintf
"Require (%s, %s)"
name
(PP.string_of_option string_of_require_bindings bindings)
| ImportDynamic _ -> "ImportDynamic"
| Import0 _ -> "Import0"
| Import _ -> "Import"
in
PP.items_to_list_string 2 (Base.List.map ~f:string_of_require require_list)
in
let string_of_named_export_kind = function
| NamedDeclaration -> "NamedDeclaration"
| NamedSpecifier { local; _ } ->
let (_, x) = local in
Printf.sprintf "NamedSpecifier(%s)" x
in
let string_of_export (n, export) =
( n,
match export with
| (_, ExportDefault { local; _ }) ->
Printf.sprintf "ExportDefault (%s)" @@ PP.string_of_option (fun (_, x) -> x) local
| (_, ExportNamed { kind; _ }) ->
Printf.sprintf "ExportNamed (%s)" @@ string_of_named_export_kind kind
| (_, ExportNs _) -> "ExportNs"
)
in
let string_of_type_export (n, type_export) =
( n,
match type_export with
| (_, TypeExportNamed { kind; _ }) ->
Printf.sprintf "TypeExportNamed (%s)" @@ string_of_named_export_kind kind
)
in
let string_of_export_star = function
| (_, ExportStar _) -> "ExportStar"
in
let string_of_module_kind = function
| CommonJS _ -> "CommonJS"
| ES { named; star } ->
PP.items_to_record_string
2
[
("named", PP.items_to_record_string 3 @@ Base.List.map ~f:string_of_export named);
("star", PP.items_to_list_string 3 @@ Base.List.map ~f:string_of_export_star star);
]
in
PP.items_to_record_string
1
[
("requires", string_of_require_list module_sig.requires);
("module_kind", string_of_module_kind module_sig.module_kind);
( "type_exports_named",
PP.items_to_record_string 2
@@ Base.List.map ~f:string_of_type_export module_sig.type_exports_named
);
( "type_exports_star",
PP.items_to_list_string 2
@@ Base.List.map ~f:string_of_export_star module_sig.type_exports_star
);
]
in
PP.items_to_record_string 0 [("module_sig", string_of_module_sig t.module_sig)]
let combine_nel _ a b = Some (Nel.concat (a, [b]))
let require_loc_map msig =
let acc = SMap.empty in
(* requires *)
let acc =
List.fold_left
(fun acc require ->
match require with
| Require { source = (loc, mref); _ }
| ImportDynamic { source = (loc, mref); _ }
| Import0 { source = (loc, mref) }
| Import { source = (loc, mref); _ } ->
SMap.add mref (Nel.one loc) acc ~combine:Nel.rev_append)
acc
msig.requires
in
(* export type {...} from 'foo' *)
let acc =
List.fold_left
(fun acc (_, type_export) ->
match type_export with
| (_, TypeExportNamed { kind = NamedSpecifier { source = Some (loc, mref); _ }; _ }) ->
SMap.add mref (Nel.one loc) acc ~combine:Nel.rev_append
| _ -> acc)
acc
msig.type_exports_named
in
(* export type * from 'foo' *)
let acc =
List.fold_left
(fun acc export_star ->
match export_star with
| (_, ExportStar { source = (source_loc, mref); _ }) ->
SMap.add mref (Nel.one source_loc) acc ~combine:Nel.rev_append)
acc
msig.type_exports_star
in
let acc =
match msig.module_kind with
| CommonJS _ -> acc
| ES { named; star } ->
(* export {...} from 'foo' *)
let acc =
List.fold_left
(fun acc (_, export) ->
match export with
| (_, ExportNamed { kind = NamedSpecifier { source = Some (loc, mref); _ }; _ })
| (_, ExportNs { source = (loc, mref); _ }) ->
SMap.add mref (Nel.one loc) acc ~combine:Nel.rev_append
| _ -> acc)
acc
named
in
(* export * from 'foo' *)
let acc =
List.fold_left
(fun acc export_star ->
match export_star with
| (_, ExportStar { source = (source_loc, mref); _ }) ->
SMap.add mref (Nel.one source_loc) acc ~combine:Nel.rev_append)
acc
star
in
acc
in
acc
let require_set msig =
let map = require_loc_map msig in
SMap.fold (fun key _ acc -> SSet.add key acc) map SSet.empty
let add_declare_module name m loc fsig =
{ fsig with declare_modules = SMap.add name (loc, m) fsig.declare_modules }
let add_require require msig =
let requires = require :: msig.requires in
Ok { msig with requires }
let add_type_exports named star msig =
let named =
Base.List.map
~f:(fun (name, export) ->
let type_export =
match export with
| (export_loc, ExportNamed { loc; kind }) -> (export_loc, TypeExportNamed { loc; kind })
| (_, ExportDefault _) -> failwith "export default type"
| (_, ExportNs _) -> failwith "export type * as X"
in
(name, type_export))
named
in
let type_exports_named = List.rev_append named msig.type_exports_named in
let type_exports_star =
Base.Option.fold
~f:(fun acc export_star -> export_star :: acc)
~init:msig.type_exports_star
star
in
Ok { msig with type_exports_named; type_exports_star }
let add_es_exports loc named star msig =
let result =
match msig.module_kind with
| CommonJS { mod_exp_loc = Some _ } -> Error (IndeterminateModuleType loc)
| CommonJS { mod_exp_loc = None } -> Ok ([], [])
| ES { named; star } -> Ok (named, star)
in
match result with
| Error e -> Error e
| Ok (named0, star0) ->
let named = List.rev_append named named0 in
let star = Base.Option.fold ~f:(fun acc export_star -> export_star :: acc) ~init:star0 star in
let module_kind = ES { named; star } in
Ok { msig with module_kind }
let set_cjs_exports mod_exp_loc msig =
match msig.module_kind with
| CommonJS { mod_exp_loc = original_mod_exp_loc } ->
let mod_exp_loc = Base.Option.first_some original_mod_exp_loc (Some mod_exp_loc) in
let module_kind = CommonJS { mod_exp_loc } in
Ok { msig with module_kind }
| ES _ -> Error (IndeterminateModuleType mod_exp_loc)
(* Subclass of the AST visitor class that calculates requires and exports. Initializes with the
scope builder class.
*)
class requires_exports_calculator ~ast ~opts =
object (this)
inherit
[(t * tolerable_error list, error) result, L.t] visitor ~init:(Ok (empty, [])) as super
val scope_info : Scope_api.info =
Scope_builder.program ~with_types:true ~enable_enums:opts.enable_enums ast
val mutable curr_declare_module : module_sig option = None
(* This ensures that we do not add a `require` with no bindings to `module_sig.requires` (when
* processing a `call`) when we have already added that `require` with bindings (when processing
* a `variable_declarator`). *)
val mutable visited_requires_with_bindings : L.LSet.t = L.LSet.empty
method private visited_requires_with_bindings loc bindings =
bindings = None && L.LSet.mem loc visited_requires_with_bindings
method private visit_requires_with_bindings loc bindings =
if bindings <> None then
visited_requires_with_bindings <- L.LSet.add loc visited_requires_with_bindings
method private update_module_sig f =
match curr_declare_module with
| Some m ->
(match f m with
| Error e -> this#set_acc (Error e)
| Ok msig -> curr_declare_module <- Some msig)
| None ->
this#update_acc (function
| Error _ as acc -> acc
| Ok (fsig, errs) ->
(match f fsig.module_sig with
| Error e -> Error e
| Ok module_sig -> Ok ({ fsig with module_sig }, errs))
)
method private add_require require = this#update_module_sig (add_require require)
method private add_exports loc kind named batch =
let add =
let open Ast.Statement in
match kind with
| ExportType -> add_type_exports
| ExportValue -> add_es_exports loc
in
this#update_module_sig (add named batch)
method private set_cjs_exports mod_exp_loc =
this#update_module_sig (set_cjs_exports mod_exp_loc)
method private add_cjs_export mod_exp_loc =
this#update_module_sig (set_cjs_exports mod_exp_loc)
method private add_tolerable_error (err : tolerable_error) =
this#update_acc (Result.map ~f:(fun (fsig, errs) -> (fsig, err :: errs)))
method! expression (expr : (L.t, L.t) Ast.Expression.t) =
let open Ast.Expression in
begin
match expr with
(* Disallow expressions consisting of `module` or `exports`. These are dangerous because they
* can allow aliasing and mutation. *)
| ( _,
Identifier
(loc, { Ast.Identifier.name = ("module" | "exports") as name; comments = _ })
)
when not (Scope_api.is_local_use scope_info loc) ->
this#add_tolerable_error (BadExportContext (name, loc))
| _ -> ()
end;
super#expression expr
method! binary loc (expr : (L.t, L.t) Ast.Expression.Binary.t) =
let open Ast.Expression in
let open Ast.Expression.Binary in
let is_module_or_exports = function
| (_, Identifier (_, { Ast.Identifier.name = "module" | "exports"; comments = _ })) ->
true
| _ -> false
in
let is_legal_operator = function
| StrictEqual
| StrictNotEqual ->
true
| _ -> false
in
let identify_or_recurse subexpr =
if not (is_module_or_exports subexpr) then ignore (this#expression subexpr)
in
let { operator; left; right; comments = _ } = expr in
(* Allow e.g. `require.main === module` by avoiding the recursive calls (where the errors
* are generated) if the AST matches specific patterns. *)
if is_legal_operator operator then (
identify_or_recurse left;
identify_or_recurse right;
expr
) else
super#binary loc expr
method! member loc (expr : (L.t, L.t) Ast.Expression.Member.t) =
let open Ast.Expression in
let open Ast.Expression.Member in
let { _object; property; comments = _ } = expr in
(* Strip the loc to simplify the patterns *)
let (_, _object) = _object in
(* This gets called when patterns like `module.id` appear on the LHS of an
* assignment, in addition to when they appear in ordinary expression
* locations. Therefore we have to prevent anything that would be dangerous
* if it appeared on the LHS side of an assignment. Ordinary export
* statements are handled by handle_assignment, which stops recursion so we
* don't arrive here in those cases. *)
begin
match (_object, property) with
(* Allow `module.anythingButExports` *)
| ( Identifier (_, { Ast.Identifier.name = "module"; comments = _ }),
PropertyIdentifier (_, { Ast.Identifier.name = prop; comments = _ })
)
when prop <> "exports" ->
()
(* Allow `module.exports.whatever` -- this is safe because handle_assignment has already
* looked for assignments to it before recursing down here. *)
| ( Member
{
_object = (_, Identifier (_, { Ast.Identifier.name = "module"; comments = _ }));
property =
PropertyIdentifier (_, { Ast.Identifier.name = "exports"; comments = _ });
_;
},
PropertyIdentifier _
)
(* Allow `exports.whatever`, for the same reason as above *)
| (Identifier (_, { Ast.Identifier.name = "exports"; comments = _ }), PropertyIdentifier _)
->
(* In these cases we don't know much about the property so we should recurse *)
ignore (this#member_property property)
| _ -> ignore (super#member loc expr)
end;
expr
method! call call_loc (expr : (L.t, L.t) Ast.Expression.Call.t) =
let open Ast.Expression in
let { Call.callee; targs = _; arguments; comments = _ } = expr in
this#handle_call call_loc callee arguments None;
super#call call_loc expr
method! literal loc (expr : L.t Ast.Literal.t) =
let open Ast.Literal in
this#handle_literal loc expr.value;
super#literal loc expr
method! tagged_template loc (expr : ('loc, 'loc) Ast.Expression.TaggedTemplate.t) =
let open Ast.Expression.TaggedTemplate in
let { tag; quasi; comments = _ } = expr in
match tag with
| (_, Ast.Expression.Identifier (_, { Ast.Identifier.name = "graphql"; _ }))
when opts.enable_relay_integration ->
(match
Graphql.extract_module_name ~module_prefix:opts.relay_integration_module_prefix quasi
with
| Ok module_name ->
this#add_require
(Require { source = (loc, module_name); require_loc = loc; bindings = None });
expr
| Error _ -> expr)
| _ -> super#tagged_template loc expr
method! import import_loc (expr : (L.t, L.t) Ast.Expression.Import.t) =
let open Ast.Expression in
let { Import.argument; comments = _ } = expr in
begin
match argument with
| ( loc,
( Literal { Ast.Literal.value = Ast.Literal.String name; _ }
| TemplateLiteral
{
TemplateLiteral.quasis =
[
( _,
{
TemplateLiteral.Element.value =
{ TemplateLiteral.Element.cooked = name; _ };
_;
}
);
];
_;
} )
) ->
this#add_require (ImportDynamic { source = (loc, name); import_loc })
| _ -> ()
end;
super#import import_loc expr
method! import_declaration import_loc (decl : (L.t, L.t) Ast.Statement.ImportDeclaration.t) =
let open Ast.Statement.ImportDeclaration in
let { import_kind; source; specifiers; default; comments = _ } = decl in
let source =
match source with
| (loc, { Ast.StringLiteral.value = name; _ }) -> (loc, name)
in
let import =
match (default, specifiers) with
| (None, None) -> Import0 { source }
| _ ->
let named = ref SMap.empty in
let ns = ref None in
let types = ref SMap.empty in
let typesof = ref SMap.empty in
let typesof_ns = ref None in
let ref_of_kind = function
| ImportType -> types
| ImportTypeof -> typesof
| ImportValue -> named
in
let add_named remote local loc ref =
let locals = SMap.singleton local (Nel.one loc) in
let combine_nel_smap a b = SMap.union a b ~combine:combine_nel in
ref := SMap.add remote locals !ref ~combine:combine_nel_smap
in
let set_ns local loc ref =
if !ref = None then
ref := Some (loc, local)
else
failwith "unreachable"
in
Base.Option.iter
~f:(fun (loc, { Ast.Identifier.name = local; comments = _ }) ->
add_named
"default"
local
{ remote_loc = loc; local_loc = loc }
(ref_of_kind import_kind))
default;
Base.Option.iter
~f:(function
| ImportNamespaceSpecifier (_, (loc, { Ast.Identifier.name = local; comments = _ }))
->
(match import_kind with
| ImportType -> failwith "import type * is a parse error"
| ImportTypeof -> set_ns local loc typesof_ns
| ImportValue -> set_ns local loc ns)
| ImportNamedSpecifiers named_specifiers ->
List.iter
(function
| { local; remote; kind } ->
let import_kind =
match kind with
| Some k -> k
| None -> import_kind
in
let (local_loc, { Ast.Identifier.name = local_name; comments = _ }) =
match local with
| Some x -> x
| None -> remote
in
let (remote_loc, { Ast.Identifier.name = remote_name; comments = _ }) =
remote
in
add_named
remote_name
local_name
{ remote_loc; local_loc }
(ref_of_kind import_kind))
named_specifiers)
specifiers;
Import
{
import_loc;
source;
named = !named;
ns = !ns;
types = !types;
typesof = !typesof;
typesof_ns = !typesof_ns;
}
in
this#add_require import;
super#import_declaration import_loc decl
method! export_default_declaration
stmt_loc (decl : (L.t, L.t) Ast.Statement.ExportDefaultDeclaration.t) =
let open Ast.Statement.ExportDefaultDeclaration in
let { default = default_loc; declaration; comments = _ } = decl in
let local =
match declaration with
| Declaration (_, Ast.Statement.FunctionDeclaration { Ast.Function.id; _ }) -> id
| Declaration (_, Ast.Statement.ClassDeclaration { Ast.Class.id; _ }) -> id
| Declaration (_, Ast.Statement.EnumDeclaration { Ast.Statement.EnumDeclaration.id; _ })
->
Some id
| Expression (_, Ast.Expression.Function { Ast.Function.id; _ }) -> id
| _ -> None
in
let local = Base.Option.map ~f:Flow_ast_utils.source_of_ident local in
let export = (stmt_loc, ExportDefault { default_loc; local }) in
this#add_exports stmt_loc Ast.Statement.ExportValue [("default", export)] None;
super#export_default_declaration stmt_loc decl
method! export_named_declaration
stmt_loc (decl : (L.t, L.t) Ast.Statement.ExportNamedDeclaration.t) =
let open Ast.Statement.ExportNamedDeclaration in
let { export_kind; source; specifiers; declaration; comments = _ } = decl in
let source =
match source with
| Some (loc, { Ast.StringLiteral.value = mref; raw = _; comments = _ }) -> Some (loc, mref)
| None -> None
in
begin
match declaration with
| None -> () (* assert specifiers <> None *)
| Some (loc, stmt) ->
let open Ast.Statement in
assert (source = None);
let kind = NamedDeclaration in
(match stmt with
| FunctionDeclaration
{ Ast.Function.id = Some (loc, { Ast.Identifier.name; comments = _ }); _ }
| ClassDeclaration
{ Ast.Class.id = Some (loc, { Ast.Identifier.name; comments = _ }); _ }
| EnumDeclaration
{ Ast.Statement.EnumDeclaration.id = (loc, { Ast.Identifier.name; _ }); _ } ->
let export = (stmt_loc, ExportNamed { loc; kind }) in
this#add_exports stmt_loc ExportValue [(name, export)] None
| VariableDeclaration { VariableDeclaration.declarations = decls; _ } ->
let rev_named =
Ast_utils.fold_bindings_of_variable_declarations
(fun _ named (loc, { Ast.Identifier.name; comments = _ }) ->
let export = (stmt_loc, ExportNamed { loc; kind }) in
(name, export) :: named)
[]
decls
in
this#add_exports stmt_loc ExportValue (List.rev rev_named) None
| TypeAlias { TypeAlias.id; _ }
| OpaqueType { OpaqueType.id; _ }
| InterfaceDeclaration { Interface.id; _ } ->
let export = (stmt_loc, ExportNamed { loc; kind }) in
this#add_exports stmt_loc ExportType [(Flow_ast_utils.name_of_ident id, export)] None
| _ -> failwith "unsupported declaration")
end;
begin
match specifiers with
| None -> () (* assert declaration <> None *)
| Some specifiers -> this#export_specifiers stmt_loc export_kind source specifiers
end;
super#export_named_declaration stmt_loc decl
method! declare_module_exports loc (exports : (L.t, L.t) Ast.Statement.DeclareModuleExports.t)
=
this#set_cjs_exports loc;
super#declare_module_exports loc exports
method! declare_export_declaration
stmt_loc (decl : (L.t, L.t) Ast.Statement.DeclareExportDeclaration.t) =
let open Ast.Statement.DeclareExportDeclaration in
let { default; source; specifiers; declaration; comments = _ } = decl in
let source =
match source with
| Some (loc, { Ast.StringLiteral.value = mref; raw = _; comments = _ }) ->
assert (Base.Option.is_none default);
(* declare export default from not supported *)
Some (loc, mref)
| _ -> None
in
begin
match declaration with
| None -> () (* assert specifiers <> None *)
| Some declaration ->
let open Ast.Statement in
assert (source = None);
let kind = NamedDeclaration in
(match declaration with
| Variable (_, { DeclareVariable.id; _ })
| Function (_, { DeclareFunction.id; _ })
| Class (_, { DeclareClass.id; _ }) ->
let (name, export) =
match default with
| Some default_loc ->
( "default",
( stmt_loc,
ExportDefault
{ default_loc; local = Some (Flow_ast_utils.source_of_ident id) }
)
)
| None ->
(Flow_ast_utils.name_of_ident id, (stmt_loc, ExportNamed { loc = fst id; kind }))
in
this#add_exports stmt_loc ExportValue [(name, export)] None
| DefaultType _ ->
let default_loc =
match default with
| Some loc -> loc
| None -> failwith "declare export default must have a default loc"
in
let export = (stmt_loc, ExportDefault { default_loc; local = None }) in
this#add_exports stmt_loc ExportValue [("default", export)] None
| NamedType (_, { TypeAlias.id; _ })
| NamedOpaqueType (_, { OpaqueType.id; _ })
| Interface (_, { Interface.id; _ }) ->
assert (Base.Option.is_none default);
let export = (stmt_loc, ExportNamed { loc = fst id; kind }) in
this#add_exports stmt_loc ExportType [(Flow_ast_utils.name_of_ident id, export)] None)
end;
begin
match specifiers with
| None -> () (* assert declaration <> None *)
| Some specifiers ->
assert (Base.Option.is_none default);
(* declare export type unsupported *)
let export_kind = Ast.Statement.ExportValue in
this#export_specifiers stmt_loc export_kind source specifiers
end;
super#declare_export_declaration stmt_loc decl
method! assignment loc (expr : (L.t, L.t) Ast.Expression.Assignment.t) =
this#handle_assignment ~is_toplevel:false loc expr;
expr
method handle_assignment
~(is_toplevel : bool) loc (expr : (L.t, L.t) Ast.Expression.Assignment.t) =
let open Ast.Expression in
let open Ast.Expression.Assignment in
let { operator; left; right; comments = _ } = expr in
(* Handle exports *)
match (operator, left) with
(* module.exports = ... *)
| ( None,
( mod_exp_loc,
Ast.Pattern.Expression
( _,
Member
{
Member._object =
( module_loc,
Identifier (_, { Ast.Identifier.name = "module"; comments = _ })
);
property =
Member.PropertyIdentifier
(_, { Ast.Identifier.name = "exports"; comments = _ });
_;
}
)
)
)
when not (Scope_api.is_local_use scope_info module_loc) ->
this#handle_cjs_default_export module_loc mod_exp_loc;
ignore (this#expression right);
if not is_toplevel then this#add_tolerable_error (BadExportPosition mod_exp_loc)
(* exports.foo = ... *)
| ( None,
( _,
Ast.Pattern.Expression
( _,
Member
{
Member._object =
( (mod_exp_loc as module_loc),
Identifier (_, { Ast.Identifier.name = "exports"; comments = _ })
);
property = Member.PropertyIdentifier _;
_;
}
)
)
)
(* module.exports.foo = ... *)
| ( None,
( _,
Ast.Pattern.Expression
( _,
Member
{
Member._object =
( mod_exp_loc,
Member
{
Member._object =
( module_loc,
Identifier (_, { Ast.Identifier.name = "module"; comments = _ })
);
property =
Member.PropertyIdentifier
(_, { Ast.Identifier.name = "exports"; comments = _ });
_;
}
);
property = Member.PropertyIdentifier _;
_;
}
)
)
)
when not (Scope_api.is_local_use scope_info module_loc) ->
(* expressions not allowed in declare module body *)
assert (curr_declare_module = None);
this#add_cjs_export mod_exp_loc;
ignore (this#expression right);
if not is_toplevel then this#add_tolerable_error (BadExportPosition mod_exp_loc)
(* module = ... *)
| ( None,
( _,
Ast.Pattern.Identifier
{
Ast.Pattern.Identifier.name =
(loc, { Ast.Identifier.name = ("exports" | "module") as id; comments = _ });
_;
}
)
)
when not (Scope_api.is_local_use scope_info loc) ->
ignore (this#expression right);
this#add_tolerable_error (BadExportContext (id, loc))
| _ -> ignore (super#assignment loc expr)
method private handle_cjs_default_export module_loc mod_exp_loc =
(* expressions not allowed in declare module body *)
assert (curr_declare_module = None);
if not (Scope_api.is_local_use scope_info module_loc) then this#set_cjs_exports mod_exp_loc
method! variable_declarator
~kind (decl : (L.t, L.t) Ast.Statement.VariableDeclaration.Declarator.t) =
begin
match decl with
| (_, { Ast.Statement.VariableDeclaration.Declarator.id; init = Some init }) ->
this#handle_require id init
| _ -> ()
end;
super#variable_declarator ~kind decl
method private require_pattern (pattern : (L.t, L.t) Ast.Pattern.t) =
match pattern with
| (_, Ast.Pattern.Identifier { Ast.Pattern.Identifier.name; _ }) ->
Some (BindIdent (Flow_ast_utils.source_of_ident name))
| (_, Ast.Pattern.Object { Ast.Pattern.Object.properties; _ }) ->
let named_bind =
Base.List.fold_result properties ~init:[] ~f:(fun named prop ->
match prop with
| Ast.Pattern.Object.Property
( _,
{
Ast.Pattern.Object.Property.key =
Ast.Pattern.Object.Property.Identifier remote;
pattern;
_;
}
) ->
(match this#require_pattern pattern with
| Some bindings -> Ok ((Flow_ast_utils.source_of_ident remote, bindings) :: named)
| None -> Error ())
| _ -> Error ()
)
in
(match named_bind with
| Ok named_bind -> Some (BindNamed named_bind)
| Error () -> None)
| _ -> None
method private handle_require
(left : (L.t, L.t) Ast.Pattern.t) (right : (L.t, L.t) Ast.Expression.t) =
let open Ast.Expression in
let bindings = this#require_pattern left in
match right with
| (call_loc, Call { Call.callee; targs = _; arguments; comments = _ }) ->
this#handle_call call_loc callee arguments bindings
| _ -> ()
method private handle_call call_loc callee arguments bindings =
let open Ast.Expression in
if not (this#visited_requires_with_bindings call_loc bindings) then (
this#visit_requires_with_bindings call_loc bindings;
match (callee, arguments) with
| ( (_, Identifier (loc, { Ast.Identifier.name = "require"; comments = _ })),
( _,
{
Ast.Expression.ArgList.arguments =
[
Expression
( source_loc,
( Literal { Ast.Literal.value = Ast.Literal.String name; _ }
| TemplateLiteral
{
TemplateLiteral.quasis =
[
( _,
{
TemplateLiteral.Element.value =
{ TemplateLiteral.Element.cooked = name; _ };
_;
}
);
];
_;
} )
);
];
comments = _;
}
)
) ->
if not (Scope_api.is_local_use scope_info loc) then
this#add_require
(Require { source = (source_loc, name); require_loc = call_loc; bindings })
| _ -> ()
)
method private handle_literal loc lit =
let open Ast.Literal in
match opts.module_ref_prefix with
| Some prefix ->
begin
match lit with
| String s when String_utils.string_starts_with s prefix ->
this#add_require
(Require
{
source = (loc, String_utils.lstrip s prefix);
require_loc = loc;
bindings = None;
}
)
| _ -> ()
end
| None -> ()
method! declare_module loc (m : (L.t, L.t) Ast.Statement.DeclareModule.t) =
let name =
let open Ast.Statement.DeclareModule in
match m.id with
| Identifier (_, { Ast.Identifier.name; comments = _ }) -> name
| Literal (_, { Ast.StringLiteral.value; _ }) -> value
in
curr_declare_module <- Some empty_module_sig;
let ret = super#declare_module loc m in
begin
match curr_declare_module with
| None -> failwith "lost curr_declare_module"
| Some m ->
this#update_acc (function
| Error _ as acc -> acc
| Ok (fsig, errs) -> Ok (add_declare_module name m loc fsig, errs)
)
end;
curr_declare_module <- None;
ret
method private export_specifiers stmt_loc kind source =
let open Ast.Statement.ExportNamedDeclaration in
function
| ExportBatchSpecifier (star_loc, Some (loc, { Ast.Identifier.name; comments = _ })) ->
(* export type * as X from "foo" unsupported *)
assert (kind = Ast.Statement.ExportValue);
let mref =
match source with
| Some mref -> mref
| None -> failwith "export batch without source"
in
let export = (stmt_loc, ExportNs { loc; star_loc; source = mref }) in
this#add_exports stmt_loc kind [(name, export)] None
| ExportBatchSpecifier (star_loc, None) ->
let mref =
match source with
| Some mref -> mref
| _ -> failwith "batch export missing source"
in
let export = (stmt_loc, ExportStar { star_loc; source = mref }) in
this#add_exports stmt_loc kind [] (Some export)
| ExportSpecifiers specs ->
let bindings =
List.fold_left
ExportSpecifier.(
fun acc (_, spec) ->
let ({ Ast.Identifier.name; comments = _ }, loc) =
match spec.exported with
| None -> (snd spec.local, fst spec.local)
| Some remote -> (snd remote, fst remote)
in
let export =
( stmt_loc,
ExportNamed
{
loc;
kind =
NamedSpecifier
{ local = Flow_ast_utils.source_of_ident spec.local; source };
}
)
in
(name, export) :: acc
)
[]
specs
in
this#add_exports stmt_loc kind bindings None
method! toplevel_statement_list (stmts : (L.t, L.t) Ast.Statement.t list) =
let open Ast in
let id = Flow_ast_mapper.id in
let map_expression (expr : (L.t, L.t) Expression.t) =
let open Expression in
match expr with
| (loc, Assignment assg) ->
this#handle_assignment ~is_toplevel:true loc assg;
expr
| _ -> this#expression expr
in
let map_expression_statement (stmt : (L.t, L.t) Statement.Expression.t) =
Statement.Expression.(
let { expression; _ } = stmt in
id map_expression expression stmt (fun expr -> { stmt with expression = expr })
)
in
let map_statement (stmt : (L.t, L.t) Statement.t) =
let open Statement in
match stmt with
| (loc, Expression expr) ->
id map_expression_statement expr stmt (fun expr -> (loc, Expression expr))
| _ -> this#statement stmt
in
ListUtils.ident_map map_statement stmts
end
(* Now that we've determined the kind of the export, we can filter out
BadExportPosition and BadExportContext from ES modules. These errors are only
relevant for CommonJS modules, since their goal is to capture aliasing of
`module` and `module.exports`. For ES modules these uses should be allowed,
so we discard these errors to allow more flexibility. Note that this pass only
accummulates BadExportPosition and BadExportContext errors. *)
let filter_irrelevant_errors ~module_kind tolerable_errors =
match module_kind with
| CommonJS _ -> tolerable_errors
| ES _ -> []
let program ~ast ~opts =
let walk = new requires_exports_calculator ~ast ~opts in
match walk#eval walk#program ast with
| Ok (exports, tolerable_errors) ->
let module_kind = exports.module_sig.module_kind in
Ok (exports, filter_irrelevant_errors ~module_kind tolerable_errors)
| Error e -> Error e
class mapper =
object (this)
method file_sig (file_sig : t) =
let { module_sig; declare_modules } = file_sig in
let module_sig' = this#module_sig module_sig in
let declare_modules' =
SMapUtils.ident_map
(fun (loc, module_sig) ->
let loc = this#loc loc in
let module_sig = this#module_sig module_sig in
(loc, module_sig))
declare_modules
in
if module_sig == module_sig' && declare_modules == declare_modules' then
file_sig
else
{ module_sig = module_sig'; declare_modules = declare_modules' }
method module_sig (module_sig : module_sig) =
let { requires; module_kind; type_exports_named; type_exports_star } = module_sig in
let requires' = ListUtils.ident_map this#require requires in
let module_kind' = this#module_kind module_kind in
let type_exports_named' = ListUtils.ident_map this#type_export type_exports_named in
let type_exports_star' = ListUtils.ident_map this#export_star type_exports_star in
if
requires == requires'
&& module_kind == module_kind'
&& type_exports_named == type_exports_named'
&& type_exports_star == type_exports_star'
then
module_sig
else
{
requires = requires';
module_kind = module_kind';
type_exports_named = type_exports_named';
type_exports_star = type_exports_star';
}
method require (require : require) =
match require with
| Require { source; require_loc; bindings } ->
let source' = this#source source in
let require_loc' = this#loc require_loc in
let bindings' = OptionUtils.ident_map this#require_bindings bindings in
if source == source' && require_loc == require_loc' && bindings == bindings' then
require
else
Require { source = source'; require_loc = require_loc'; bindings = bindings' }
| ImportDynamic { source; import_loc } ->
let source' = this#source source in
let import_loc' = this#loc import_loc in
if source == source' && import_loc == import_loc' then
require
else
ImportDynamic { source = source'; import_loc = import_loc' }
| Import0 { source } ->
let source' = this#source source in
if source == source' then
require
else
Import0 { source = source' }
| Import { import_loc; source; named; ns; types; typesof; typesof_ns } ->
let import_loc' = this#loc import_loc in
let source' = this#source source in
let named' =
SMapUtils.ident_map (SMapUtils.ident_map (Nel.ident_map this#imported_locs)) named
in
let ns' = OptionUtils.ident_map this#ident ns in
let types' =
SMapUtils.ident_map (SMapUtils.ident_map (Nel.ident_map this#imported_locs)) types
in
let typesof' =
SMapUtils.ident_map (SMapUtils.ident_map (Nel.ident_map this#imported_locs)) typesof
in
let typesof_ns' = OptionUtils.ident_map this#ident typesof_ns in
if
import_loc == import_loc'
&& source == source'
&& named == named'
&& ns == ns'
&& types == types'
&& typesof == typesof'
&& typesof_ns == typesof_ns'
then
require
else
Import
{
import_loc = import_loc';
source = source';
named = named';
ns = ns';
types = types';
typesof = typesof';
typesof_ns = typesof_ns';
}
method imported_locs (imported_locs : imported_locs) =
let { remote_loc; local_loc } = imported_locs in
let remote_loc' = this#loc remote_loc in
let local_loc' = this#loc local_loc in
if remote_loc == remote_loc' && local_loc == local_loc' then
imported_locs
else
{ remote_loc = remote_loc'; local_loc = local_loc' }
method require_bindings (require_bindings : require_bindings) =
match require_bindings with
| BindIdent ident ->
let ident' = this#ident ident in
if ident == ident' then
require_bindings
else
BindIdent ident'
| BindNamed named ->
let named' =
ListUtils.ident_map
(fun ((remote, require_bindings) as x) ->
let remote' = this#ident remote in
let require_bindings' = this#require_bindings require_bindings in
if remote == remote' && require_bindings == require_bindings' then
x
else
(remote', require_bindings'))
named
in
if named == named' then
require_bindings
else
BindNamed named'
method module_kind (module_kind : module_kind) =
match module_kind with
| CommonJS { mod_exp_loc } ->
let mod_exp_loc' = OptionUtils.ident_map this#loc mod_exp_loc in
if mod_exp_loc == mod_exp_loc' then
module_kind
else
CommonJS { mod_exp_loc = mod_exp_loc' }
| ES { named; star } ->
let named' = ListUtils.ident_map this#export named in
let star' = ListUtils.ident_map this#export_star star in
if named == named' && star == star' then
module_kind
else
ES { named = named'; star = star' }
method named_export_kind (kind : named_export_kind) =
match kind with
| NamedDeclaration -> kind
| NamedSpecifier { local; source } ->
let local' = this#ident local in
let source' = OptionUtils.ident_map this#source source in
if local == local' && source == source' then
kind
else
NamedSpecifier { local = local'; source = source' }
method export (export : string * (L.t * export)) =
match export with
| (n, (export_loc, ExportDefault { default_loc; local })) ->
let export_loc' = this#loc export_loc in
let default_loc' = this#loc default_loc in
let local' = OptionUtils.ident_map this#ident local in
if export_loc == export_loc' && default_loc == default_loc' && local == local' then
export
else
(n, (export_loc', ExportDefault { default_loc = default_loc'; local = local' }))
| (n, (export_loc, ExportNamed { loc; kind })) ->
let export_loc' = this#loc export_loc in
let loc' = this#loc loc in
let kind' = this#named_export_kind kind in
if export_loc == export_loc' && loc == loc' && kind == kind' then
export
else
(n, (export_loc', ExportNamed { loc = loc'; kind = kind' }))
| (n, (export_loc, ExportNs { loc; star_loc; source })) ->
let export_loc' = this#loc export_loc in
let loc' = this#loc loc in
let star_loc' = this#loc star_loc in
let source' = this#source source in
if export_loc == export_loc' && loc == loc' && star_loc == star_loc' && source == source'
then
export
else
(n, (export_loc', ExportNs { loc = loc'; star_loc = star_loc'; source = source' }))
method export_star (export_star : L.t * export_star) =
match export_star with
| (export_loc, ExportStar { star_loc; source }) ->
let export_loc' = this#loc export_loc in
let star_loc' = this#loc star_loc in
let source' = this#source source in
if export_loc == export_loc' && star_loc == star_loc' && source == source' then
export_star
else
(export_loc', ExportStar { star_loc = star_loc'; source = source' })
method type_export (type_export : string * (L.t * type_export)) =
match type_export with
| (n, (export_loc, TypeExportNamed { loc; kind })) ->
let export_loc' = this#loc export_loc in
let loc' = this#loc loc in
let kind' = this#named_export_kind kind in
if export_loc == export_loc' && loc == loc' && kind == kind' then
type_export
else
(n, (export_loc', TypeExportNamed { loc = loc'; kind = kind' }))
method ident (ident : L.t Ast_utils.ident) =
let (loc, str) = ident in
let loc' = this#loc loc in
if loc == loc' then
ident
else
(loc', str)
method source (source : L.t Ast_utils.source) =
let (loc, str) = source in
let loc' = this#loc loc in
if loc == loc' then
source
else
(loc', str)
method tolerable_error (tolerable_error : tolerable_error) =
match tolerable_error with
| BadExportPosition loc ->
let loc' = this#loc loc in
if loc == loc' then
tolerable_error
else
BadExportPosition loc'
| BadExportContext (str, loc) ->
let loc' = this#loc loc in
if loc == loc' then
tolerable_error
else
BadExportContext (str, loc')
| SignatureVerificationError sve ->
Signature_error.(
begin
match sve with
| ExpectedAnnotation (loc, sort) ->
let loc' = this#loc loc in
if loc == loc' then
tolerable_error
else
SignatureVerificationError (ExpectedAnnotation (loc', sort))
| UnexpectedObjectKey (loc, key_loc) ->
let loc' = this#loc loc in
let key_loc' = this#loc key_loc in
if loc == loc' && key_loc == key_loc' then
tolerable_error
else
SignatureVerificationError (UnexpectedObjectKey (loc', key_loc'))
| UnexpectedArraySpread (loc, spread_loc) ->
let loc' = this#loc loc in
let spread_loc' = this#loc spread_loc in
if loc == loc' && spread_loc == spread_loc' then
tolerable_error
else
SignatureVerificationError (UnexpectedArraySpread (loc', spread_loc'))
| UnexpectedArrayHole loc ->
let loc' = this#loc loc in
if loc == loc' then
tolerable_error
else
SignatureVerificationError (UnexpectedArrayHole loc')
| EmptyArray loc ->
let loc' = this#loc loc in
if loc == loc' then
tolerable_error
else
SignatureVerificationError (EmptyArray loc')
| EmptyObject loc ->
let loc' = this#loc loc in
if loc == loc' then
tolerable_error
else
SignatureVerificationError (EmptyObject loc')
| UnexpectedExpression (loc, esort) ->
let loc' = this#loc loc in
if loc == loc' then
tolerable_error
else
SignatureVerificationError (UnexpectedExpression (loc', esort))
end
)
method error (error : error) =
match error with
| IndeterminateModuleType loc ->
let loc' = this#loc loc in
if loc == loc' then
error
else
IndeterminateModuleType loc'
method loc (loc : L.t) = loc
end
end
module With_Loc = Make (Loc_sig.LocS) (Scope_api.With_Loc) (Scope_builder.With_Loc)
module With_ALoc = Make (Loc_sig.ALocS) (Scope_api.With_ALoc) (Scope_builder.With_ALoc)
let abstractify_tolerable_errors =
let module WL = With_Loc in
let module WA = With_ALoc in
let abstractify_tolerable_error = function
| WL.BadExportPosition loc -> WA.BadExportPosition (ALoc.of_loc loc)
| WL.BadExportContext (name, loc) -> WA.BadExportContext (name, ALoc.of_loc loc)
| WL.SignatureVerificationError err ->
WA.SignatureVerificationError (Signature_error.map ALoc.of_loc err)
in
Base.List.map ~f:abstractify_tolerable_error
let abstractify_locs : With_Loc.t -> With_ALoc.t =
let module WL = With_Loc in
let module WA = With_ALoc in
let abstractify_fst (loc, x) = (ALoc.of_loc loc, x) in
let abstractify_imported_locs { WL.remote_loc; local_loc } =
{ WA.remote_loc = ALoc.of_loc remote_loc; local_loc = ALoc.of_loc local_loc }
in
let abstractify_imported_locs_map = SMap.map (SMap.map (Nel.map abstractify_imported_locs)) in
let rec abstractify_require_bindings = function
| WL.BindIdent x -> WA.BindIdent (abstractify_fst x)
| WL.BindNamed named ->
WA.BindNamed
(Base.List.map
~f:(fun (remote, require_bindings) ->
(abstractify_fst remote, abstractify_require_bindings require_bindings))
named
)
in
let abstractify_require = function
| WL.Require { source; require_loc; bindings } ->
WA.Require
{
source = abstractify_fst source;
require_loc = ALoc.of_loc require_loc;
bindings = Base.Option.map ~f:abstractify_require_bindings bindings;
}
| WL.ImportDynamic { source; import_loc } ->
WA.ImportDynamic { source = abstractify_fst source; import_loc = ALoc.of_loc import_loc }
| WL.Import0 { source } -> WA.Import0 { source = abstractify_fst source }
| WL.Import { import_loc; source; named; ns; types; typesof; typesof_ns } ->
WA.Import
{
import_loc = ALoc.of_loc import_loc;
source = abstractify_fst source;
named = abstractify_imported_locs_map named;
ns = Base.Option.map ~f:abstractify_fst ns;
types = abstractify_imported_locs_map types;
typesof = abstractify_imported_locs_map typesof;
typesof_ns = Base.Option.map ~f:abstractify_fst typesof_ns;
}
in
let abstractify_requires = Base.List.map ~f:abstractify_require in
let abstractify_named_export_kind = function
| WL.NamedDeclaration -> WA.NamedDeclaration
| WL.NamedSpecifier { local; source } ->
WA.NamedSpecifier
{ local = abstractify_fst local; source = Base.Option.map ~f:abstractify_fst source }
in
let abstractify_export = function
| WL.ExportDefault { default_loc; local } ->
WA.ExportDefault
{ default_loc = ALoc.of_loc default_loc; local = Base.Option.map ~f:abstractify_fst local }
| WL.ExportNamed { loc; kind } ->
WA.ExportNamed { loc = ALoc.of_loc loc; kind = abstractify_named_export_kind kind }
| WL.ExportNs { loc; star_loc; source } ->
WA.ExportNs
{ loc = ALoc.of_loc loc; star_loc = ALoc.of_loc star_loc; source = abstractify_fst source }
in
let abstractify_named_export (name, (loc, export)) =
(name, (ALoc.of_loc loc, abstractify_export export))
in
let abstractify_named_exports = Base.List.map ~f:abstractify_named_export in
let abstractify_export_star = function
| WL.ExportStar { star_loc; source } ->
WA.ExportStar { star_loc = ALoc.of_loc star_loc; source = abstractify_fst source }
in
let abstractify_es_star =
Base.List.map ~f:(fun (loc, export_star) ->
(ALoc.of_loc loc, abstractify_export_star export_star)
)
in
let abstractify_module_kind = function
| WL.CommonJS { mod_exp_loc } ->
WA.CommonJS { mod_exp_loc = Base.Option.map ~f:ALoc.of_loc mod_exp_loc }
| WL.ES { named; star } ->
WA.ES { named = abstractify_named_exports named; star = abstractify_es_star star }
in
let abstractify_type_export = function
| WL.TypeExportNamed { loc; kind } ->
WA.TypeExportNamed { loc = ALoc.of_loc loc; kind = abstractify_named_export_kind kind }
in
let abstractify_type_exports_named =
Base.List.map ~f:(fun (name, (loc, type_export)) ->
(name, (ALoc.of_loc loc, abstractify_type_export type_export))
)
in
let abstractify_type_exports_star = abstractify_es_star in
let abstractify_module_sig { WL.requires; module_kind; type_exports_named; type_exports_star } =
{
WA.requires = abstractify_requires requires;
module_kind = abstractify_module_kind module_kind;
type_exports_named = abstractify_type_exports_named type_exports_named;
type_exports_star = abstractify_type_exports_star type_exports_star;
}
in
let abstractify_declare_modules =
SMap.map (fun (loc, module_sig) -> (ALoc.of_loc loc, abstractify_module_sig module_sig))
in
fun { WL.module_sig; declare_modules } ->
{
WA.module_sig = abstractify_module_sig module_sig;
declare_modules = abstractify_declare_modules declare_modules;
}
let abstractify (t, tolerable_errors) =
(abstractify_locs t, abstractify_tolerable_errors tolerable_errors)