diff --git a/src/commands/commandUtils.ml b/src/commands/commandUtils.ml index d33ee07f173..e39c3276bed 100644 --- a/src/commands/commandUtils.ml +++ b/src/commands/commandUtils.ml @@ -1282,6 +1282,8 @@ let make_options ~flowconfig_name ~flowconfig ~lazy_mode ~root (options_flags : opt_no_saved_state = options_flags.no_saved_state; opt_node_resolver_allow_root_relative = FlowConfig.node_resolver_allow_root_relative flowconfig; + opt_node_resolver_root_relative_dirnames = + FlowConfig.node_resolver_root_relative_dirnames flowconfig; opt_arch; opt_abstract_locations; opt_cache_direct_dependents = FlowConfig.cache_direct_dependents flowconfig; diff --git a/src/commands/config/flowConfig.ml b/src/commands/config/flowConfig.ml index d8e0dcd8d3e..51c03b4ecd0 100644 --- a/src/commands/config/flowConfig.ml +++ b/src/commands/config/flowConfig.ml @@ -93,6 +93,7 @@ module Opts = struct no_flowlib: bool; node_resolver_allow_root_relative: bool; node_resolver_dirnames: string list; + node_resolver_root_relative_dirnames: string list; recursion_limit: int; root_name: string option; saved_state_fetcher: Options.saved_state_fetcher; @@ -191,6 +192,7 @@ module Opts = struct no_flowlib = false; node_resolver_allow_root_relative = false; node_resolver_dirnames = ["node_modules"]; + node_resolver_root_relative_dirnames = [""]; recursion_limit = 10000; root_name = None; saved_state_fetcher = Options.Dummy_fetcher; @@ -470,6 +472,15 @@ module Opts = struct else let node_resolver_dirnames = v :: opts.node_resolver_dirnames in Ok { opts with node_resolver_dirnames }) ); + ( "module.system.node.root_relative_dirname", + string + ~init:(fun opts -> { opts with node_resolver_root_relative_dirnames = [] }) + ~multiple:true + (fun opts v -> + let node_resolver_root_relative_dirnames = + v :: opts.node_resolver_root_relative_dirnames + in + Ok { opts with node_resolver_root_relative_dirnames }) ); ("module.use_strict", boolean (fun opts v -> Ok { opts with modules_are_use_strict = v })); ("munge_underscores", boolean (fun opts v -> Ok { opts with munge_underscores = v })); ( "name", @@ -997,7 +1008,7 @@ let is_not_comment = (* Line starts with ; *) Str.regexp_string "\240\159\146\169"; (* Line starts with poop emoji *) - + ] in fun (_, line) -> @@ -1189,6 +1200,8 @@ let node_resolver_allow_root_relative c = c.options.Opts.node_resolver_allow_roo let node_resolver_dirnames c = c.options.Opts.node_resolver_dirnames +let node_resolver_root_relative_dirnames c = c.options.Opts.node_resolver_root_relative_dirnames + let recursion_limit c = c.options.Opts.recursion_limit let root_name c = c.options.Opts.root_name diff --git a/src/commands/config/flowConfig.mli b/src/commands/config/flowConfig.mli index 9086b2b513e..06920e8805a 100644 --- a/src/commands/config/flowConfig.mli +++ b/src/commands/config/flowConfig.mli @@ -142,6 +142,8 @@ val node_resolver_allow_root_relative : config -> bool val node_resolver_dirnames : config -> string list +val node_resolver_root_relative_dirnames : config -> string list + val required_version : config -> string option val recursion_limit : config -> int diff --git a/src/common/options.ml b/src/common/options.ml index 4192e1a52aa..4e82bb6b190 100644 --- a/src/common/options.ml +++ b/src/common/options.ml @@ -101,6 +101,7 @@ type t = { opt_modules_are_use_strict: bool; opt_munge_underscores: bool; opt_node_resolver_allow_root_relative: bool; + opt_node_resolver_root_relative_dirnames: string list; opt_no_saved_state: bool; opt_profile: bool; opt_quiet: bool; @@ -203,6 +204,8 @@ let no_saved_state opts = opts.opt_no_saved_state let node_resolver_allow_root_relative opts = opts.opt_node_resolver_allow_root_relative +let node_resolver_root_relative_dirnames opts = opts.opt_node_resolver_root_relative_dirnames + let recursion_limit opts = opts.opt_recursion_limit let root opts = opts.opt_root diff --git a/src/services/inference/module/module_js.ml b/src/services/inference/module/module_js.ml index 08789ebbe4d..84aaad22091 100644 --- a/src/services/inference/module/module_js.ml +++ b/src/services/inference/module/module_js.ml @@ -370,7 +370,18 @@ module Node = struct [ lazy ( if Options.node_resolver_allow_root_relative options then - resolve_relative ~options ~reader loc ?resolution_acc root_str import_str + lazy_seq + ( Options.node_resolver_root_relative_dirnames options + |> Core_list.map ~f:(fun root_relative_dirname -> + lazy + (let root_str = + if root_relative_dirname = "" then + root_str + else + Filename.concat root_str root_relative_dirname + in + resolve_relative ~options ~reader loc ?resolution_acc root_str import_str)) + ) else None ); lazy diff --git a/tests/config_module_system_node_root_relative_dirnames/.flowconfig b/tests/config_module_system_node_root_relative_dirnames/.flowconfig new file mode 100644 index 00000000000..4a1a6ab5091 --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/.flowconfig @@ -0,0 +1,4 @@ +[options] +module.system.node.allow_root_relative=true +module.system.node.root_relative_dirname=first +module.system.node.root_relative_dirname=second diff --git a/tests/config_module_system_node_root_relative_dirnames/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/ambiguous.js new file mode 100644 index 00000000000..e005bf9d0ec --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "user_code" = "user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/config_module_system_node_root_relative_dirnames.exp b/tests/config_module_system_node_root_relative_dirnames/config_module_system_node_root_relative_dirnames.exp new file mode 100644 index 00000000000..4d864a7a98d --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/config_module_system_node_root_relative_dirnames.exp @@ -0,0 +1,156 @@ +Error ----------------------------------------------------------------------------------------------------- test.js:16:2 + +Cannot cast `ambiguous` to empty because string literal `first_user_code` [1] is incompatible with empty [2]. + + test.js:16:2 + 16| (ambiguous: empty) + ^^^^^^^^^ + +References: + first/ambiguous.js:3:14 + 3| const value: "first_user_code" = "first_user_code"; + ^^^^^^^^^^^^^^^^^ [1] + test.js:16:13 + 16| (ambiguous: empty) + ^^^^^ [2] + + +Error ----------------------------------------------------------------------------------------------------- test.js:19:2 + +Cannot cast `sub_ambiguous` to empty because string literal `first_user_code` [1] is incompatible with empty [2]. + + test.js:19:2 + 19| (sub_ambiguous: empty) + ^^^^^^^^^^^^^ + +References: + first/subdir/ambiguous.js:3:14 + 3| const value: "first_user_code" = "first_user_code"; + ^^^^^^^^^^^^^^^^^ [1] + test.js:19:17 + 19| (sub_ambiguous: empty) + ^^^^^ [2] + + +Error ----------------------------------------------------------------------------------------------------- test.js:23:2 + +Cannot cast `user_code` to empty because string literal `first_user_code` [1] is incompatible with empty [2]. + + test.js:23:2 + 23| (user_code: empty) + ^^^^^^^^^ + +References: + first/user_code.js:3:14 + 3| const value: "first_user_code" = "first_user_code"; + ^^^^^^^^^^^^^^^^^ [1] + test.js:23:13 + 23| (user_code: empty) + ^^^^^ [2] + + +Error ----------------------------------------------------------------------------------------------------- test.js:26:2 + +Cannot cast `sub_user_code` to empty because string literal `first_user_code` [1] is incompatible with empty [2]. + + test.js:26:2 + 26| (sub_user_code: empty) + ^^^^^^^^^^^^^ + +References: + first/subdir/user_code.js:3:14 + 3| const value: "first_user_code" = "first_user_code"; + ^^^^^^^^^^^^^^^^^ [1] + test.js:26:17 + 26| (sub_user_code: empty) + ^^^^^ [2] + + +Error ----------------------------------------------------------------------------------------------------- test.js:30:2 + +Cannot cast `second_only_user_code` to empty because string literal `second_only_user_code` [1] is incompatible with +empty [2]. + + test.js:30:2 + 30| (second_only_user_code: empty) + ^^^^^^^^^^^^^^^^^^^^^ + +References: + second/second_only_user_code.js:3:14 + 3| const value: "second_only_user_code" = "second_only_user_code"; + ^^^^^^^^^^^^^^^^^^^^^^^ [1] + test.js:30:25 + 30| (second_only_user_code: empty) + ^^^^^ [2] + + +Error ----------------------------------------------------------------------------------------------------- test.js:33:2 + +Cannot cast `sub_second_only_user_code` to empty because string literal `second_only_user_code` [1] is incompatible with +empty [2]. + + test.js:33:2 + 33| (sub_second_only_user_code: empty) + ^^^^^^^^^^^^^^^^^^^^^^^^^ + +References: + second/subdir/second_only_user_code.js:3:14 + 3| const value: "second_only_user_code" = "second_only_user_code"; + ^^^^^^^^^^^^^^^^^^^^^^^ [1] + test.js:33:29 + 33| (sub_second_only_user_code: empty) + ^^^^^ [2] + + +Error ----------------------------------------------------------------------------------------------------- test.js:37:2 + +Cannot cast `node_code` to empty because string literal `node_code` [1] is incompatible with empty [2]. + + test.js:37:2 + 37| (node_code: empty) + ^^^^^^^^^ + +References: + node_modules/node_code.js:3:14 + 3| const value: "node_code" = "node_code"; + ^^^^^^^^^^^ [1] + test.js:37:13 + 37| (node_code: empty) + ^^^^^ [2] + + +Error ----------------------------------------------------------------------------------------------------- test.js:40:2 + +Cannot cast `sub_node_code` to empty because string literal `node_code` [1] is incompatible with empty [2]. + + test.js:40:2 + 40| (sub_node_code: empty) + ^^^^^^^^^^^^^ + +References: + node_modules/subdir/node_code.js:3:14 + 3| const value: "node_code" = "node_code"; + ^^^^^^^^^^^ [1] + test.js:40:17 + 40| (sub_node_code: empty) + ^^^^^ [2] + + +Error ---------------------------------------------------------------------------------------------------- test.js:43:25 + +Cannot resolve module `nonexistent`. + + 43| import nonexistent from 'nonexistent' + ^^^^^^^^^^^^^ + + +Error ---------------------------------------------------------------------------------------------------- test.js:44:29 + +Cannot resolve module `subdir/nonexistent`. + + 44| import sub_nonexistent from 'subdir/nonexistent' + ^^^^^^^^^^^^^^^^^^^^ + + + +Found 10 errors diff --git a/tests/config_module_system_node_root_relative_dirnames/first/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/first/ambiguous.js new file mode 100644 index 00000000000..17c248882e6 --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/first/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "first_user_code" = "first_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/first/subdir/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/first/subdir/ambiguous.js new file mode 100644 index 00000000000..17c248882e6 --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/first/subdir/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "first_user_code" = "first_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/first/subdir/user_code.js b/tests/config_module_system_node_root_relative_dirnames/first/subdir/user_code.js new file mode 100644 index 00000000000..17c248882e6 --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/first/subdir/user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "first_user_code" = "first_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/first/user_code.js b/tests/config_module_system_node_root_relative_dirnames/first/user_code.js new file mode 100644 index 00000000000..17c248882e6 --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/first/user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "first_user_code" = "first_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/node_modules/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/node_modules/ambiguous.js new file mode 100644 index 00000000000..fc3d412f3bb --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/node_modules/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "node_code" = "node_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/node_modules/node_code.js b/tests/config_module_system_node_root_relative_dirnames/node_modules/node_code.js new file mode 100644 index 00000000000..fc3d412f3bb --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/node_modules/node_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "node_code" = "node_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/node_modules/subdir/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/node_modules/subdir/ambiguous.js new file mode 100644 index 00000000000..fc3d412f3bb --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/node_modules/subdir/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "node_code" = "node_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/node_modules/subdir/node_code.js b/tests/config_module_system_node_root_relative_dirnames/node_modules/subdir/node_code.js new file mode 100644 index 00000000000..fc3d412f3bb --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/node_modules/subdir/node_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "node_code" = "node_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/second/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/second/ambiguous.js new file mode 100644 index 00000000000..13091f0409c --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/second/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "second_user_code" = "second_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/second/second_only_user_code.js b/tests/config_module_system_node_root_relative_dirnames/second/second_only_user_code.js new file mode 100644 index 00000000000..273e24a6bc5 --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/second/second_only_user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "second_only_user_code" = "second_only_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/second/subdir/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/second/subdir/ambiguous.js new file mode 100644 index 00000000000..13091f0409c --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/second/subdir/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "second_user_code" = "second_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/second/subdir/second_only_user_code.js b/tests/config_module_system_node_root_relative_dirnames/second/subdir/second_only_user_code.js new file mode 100644 index 00000000000..273e24a6bc5 --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/second/subdir/second_only_user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "second_only_user_code" = "second_only_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/second/subdir/user_code.js b/tests/config_module_system_node_root_relative_dirnames/second/subdir/user_code.js new file mode 100644 index 00000000000..13091f0409c --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/second/subdir/user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "second_user_code" = "second_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/second/user_code.js b/tests/config_module_system_node_root_relative_dirnames/second/user_code.js new file mode 100644 index 00000000000..13091f0409c --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/second/user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "second_user_code" = "second_user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/subdir/ambiguous.js b/tests/config_module_system_node_root_relative_dirnames/subdir/ambiguous.js new file mode 100644 index 00000000000..e005bf9d0ec --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/subdir/ambiguous.js @@ -0,0 +1,4 @@ +// @flow + +const value: "user_code" = "user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/subdir/user_code.js b/tests/config_module_system_node_root_relative_dirnames/subdir/user_code.js new file mode 100644 index 00000000000..e005bf9d0ec --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/subdir/user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "user_code" = "user_code"; +export default value; diff --git a/tests/config_module_system_node_root_relative_dirnames/test.js b/tests/config_module_system_node_root_relative_dirnames/test.js new file mode 100644 index 00000000000..d6de2afdbea --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/test.js @@ -0,0 +1,44 @@ +// @flow + +/** + * 1) All "ambiguous" code exists in first/, second/ and node_modules/. + * first/ wins + * 2) All "user_code" code exists in first/ and second/ + * first/ wins + * 3) All "second_only" code exists only in second/ + * second/ wins + * 4) All "node_code" code exists in only node_modules/ + * node_modules/ wins + */ + +// These exist in both user code and node_modules, but user code wins +import ambiguous from 'ambiguous'; +(ambiguous: empty) + +import sub_ambiguous from 'subdir/ambiguous'; +(sub_ambiguous: empty) + +// These exist in only user code +import user_code from 'user_code'; +(user_code: empty) + +import sub_user_code from 'subdir/user_code'; +(sub_user_code: empty) + +// These exist in only user code, and only in second/ +import second_only_user_code from 'second_only_user_code'; +(second_only_user_code: empty) + +import sub_second_only_user_code from 'subdir/second_only_user_code'; +(sub_second_only_user_code: empty) + +// These exist in only node code +import node_code from 'node_code'; +(node_code: empty) + +import sub_node_code from 'subdir/node_code'; +(sub_node_code: empty) + +// These exist nowhere +import nonexistent from 'nonexistent' +import sub_nonexistent from 'subdir/nonexistent' diff --git a/tests/config_module_system_node_root_relative_dirnames/user_code.js b/tests/config_module_system_node_root_relative_dirnames/user_code.js new file mode 100644 index 00000000000..e005bf9d0ec --- /dev/null +++ b/tests/config_module_system_node_root_relative_dirnames/user_code.js @@ -0,0 +1,4 @@ +// @flow + +const value: "user_code" = "user_code"; +export default value;