Skip to content

Commit

Permalink
[flow] Support directory-conditional `module.system.node.root_relativ…
Browse files Browse the repository at this point in the history
…e_dirname`

Summary: Changelog: [feature] `module.system.node.root_relative_dirname` will allow conditional mapping like `module.system.node.root_relative_dirname='<PROJECT_ROOT>/foo' -> 'bar'`. Under such config, `import 'a'` will only be resolved to `<PROJECT_ROOT>/bar/a` if `import 'a'` is in a file in the `<PROJECT_ROOT>/foo` directory. This feature will be helpful if you want to combine two flow roots with different `module.system.node.root_relative_dirname` config.

Reviewed By: panagosg7

Differential Revision: D69466985

fbshipit-source-id: 11ae4cb230f8f3d98fbb39b1780e88284d16c804
  • Loading branch information
SamChou19815 authored and facebook-github-bot committed Feb 12, 2025
1 parent 90f20be commit d7815da
Show file tree
Hide file tree
Showing 10 changed files with 71 additions and 25 deletions.
6 changes: 5 additions & 1 deletion src/commands/commandUtils.ml
Original file line number Diff line number Diff line change
Expand Up @@ -1450,7 +1450,11 @@ let make_options
opt_saved_state_verify = saved_state_options_flags.saved_state_verify;
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;
Base.List.map
(FlowConfig.node_resolver_root_relative_dirnames flowconfig)
~f:(fun (applicable_dir_opt, dirname) ->
(Base.Option.map ~f:(Files.expand_project_root_token ~root) applicable_dir_opt, dirname)
);
opt_include_suppressions = options_flags.include_suppressions;
opt_distributed = options_flags.distributed;
opt_use_mixed_in_catch_variables =
Expand Down
39 changes: 29 additions & 10 deletions src/commands/config/flowConfig.ml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ module Opts = struct
node_package_export_conditions: string list;
node_resolver_allow_root_relative: bool;
node_resolver_dirnames: string list;
node_resolver_root_relative_dirnames: string list;
node_resolver_root_relative_dirnames: (string option * string) list;
pattern_matching: bool option;
react_custom_jsx_typing: bool;
react_ref_as_prop: Options.ReactRefAsProp.t;
Expand Down Expand Up @@ -260,7 +260,7 @@ module Opts = struct
node_package_export_conditions = [];
node_resolver_allow_root_relative = false;
node_resolver_dirnames = ["node_modules"];
node_resolver_root_relative_dirnames = [""];
node_resolver_root_relative_dirnames = [(None, "")];
pattern_matching = None;
react_custom_jsx_typing = false;
react_ref_as_prop = Options.ReactRefAsProp.PartialSupport;
Expand Down Expand Up @@ -367,14 +367,23 @@ module Opts = struct
)
)

let optparse_mapping =
let mapping_regexp =
let regexp_str = "^'\\([^']*\\)'[ \t]*->[ \t]*'\\([^']*\\)'$" in
let regexp = Str.regexp regexp_str in
fun str ->
if Str.string_match regexp str 0 then
Ok (Str.matched_group 1 str, Str.matched_group 2 str)
else
Error ("Expected a mapping of form: " ^ "'single-quoted-string' -> 'single-quoted-string'")
Str.regexp regexp_str

let optparse_mapping str =
if Str.string_match mapping_regexp str 0 then
Ok (Str.matched_group 1 str, Str.matched_group 2 str)
else
Error ("Expected a mapping of form: " ^ "'single-quoted-string' -> 'single-quoted-string'")

let optparse_conditional_mapping str =
if Str.string_match mapping_regexp str 0 then
Ok (Str.matched_group 1 str, Some (Str.matched_group 2 str))
else
match optparse_string str with
| Ok s -> Ok (s, None)
| Error e -> Error e

let boolean = enum [("true", true); ("false", false)]

Expand All @@ -395,6 +404,12 @@ module Opts = struct
fn v
)

let conditional_mapping fn =
opt (fun str ->
let%bind v = optparse_conditional_mapping str in
fn v
)

let optparse_json str =
try Ok (Hh_json.json_of_string str) with
| Hh_json.Syntax_error msg -> Error (spf "Failed to parse JSON: %s" msg)
Expand Down Expand Up @@ -883,7 +898,11 @@ module Opts = struct
boolean (fun opts v -> Ok { opts with node_resolver_allow_root_relative = v })

let node_resolver_root_relative_dirnames_parser =
string
conditional_mapping
(function
| (applicable_directory, Some root_relative_dirname) ->
Ok (Some applicable_directory, root_relative_dirname)
| (root_relative_dirname, None) -> Ok (None, root_relative_dirname))
~init:(fun opts -> { opts with node_resolver_root_relative_dirnames = [] })
~multiple:true
(fun opts v ->
Expand Down
2 changes: 1 addition & 1 deletion src/commands/config/flowConfig.mli
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ val node_resolver_dirnames : config -> string list

val pattern_matching : config -> bool option

val node_resolver_root_relative_dirnames : config -> string list
val node_resolver_root_relative_dirnames : config -> (string option * string) list

val react_custom_jsx_typing : config -> bool

Expand Down
2 changes: 1 addition & 1 deletion src/common/options.ml
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ type t = {
opt_node_main_fields: string list;
opt_node_package_export_conditions: string list;
opt_node_resolver_allow_root_relative: bool;
opt_node_resolver_root_relative_dirnames: string list;
opt_node_resolver_root_relative_dirnames: (string option * string) list;
opt_profile: bool;
opt_quiet: bool;
opt_react_custom_jsx_typing: bool;
Expand Down
33 changes: 22 additions & 11 deletions src/services/module/module_js.ml
Original file line number Diff line number Diff line change
Expand Up @@ -518,24 +518,35 @@ module Node = struct
if Options.node_resolver_allow_root_relative options then
let dirnames = Options.node_resolver_root_relative_dirnames options in
let root = Options.root options |> File_path.to_string in
let f dirname =
let f (applicable_dirname_opt, dirname) =
let relative_to_directory =
if dirname = "" then
root
else
Files.normalize_path root dirname
in
lazy
(resolve_relative
~options
~reader
~phantom_acc
~importing_file
~relative_to_directory
import_specifier
)
let applicable =
Base.Option.value_map
applicable_dirname_opt
~f:(fun prefix -> Files.is_prefix prefix (File_key.to_string importing_file))
~default:true
in
if applicable then
Some
( lazy
(resolve_relative
~options
~reader
~phantom_acc
~importing_file
~relative_to_directory
import_specifier
)
)
else
None
in
lazy_seq (Base.List.map ~f dirnames)
lazy_seq (Base.List.filter_map ~f dirnames)
else
None

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ module.system.node.allow_root_relative=true
module.system.node.root_relative_dirname=first
module.system.node.root_relative_dirname=second
module.system.node.root_relative_dirname=./third
module.system.node.root_relative_dirname='<PROJECT_ROOT>/only-here-can-we-use-root-relative-fourth' -> 'fourth'
Original file line number Diff line number Diff line change
Expand Up @@ -176,5 +176,13 @@ References:
^ [2]


Error ----------------------------------------------------------------------------------------------------- test.js:46:8

Found 11 errors
Cannot resolve module `4`. [cannot-resolve-module]

46| import '4'; // error: not in only-here-can-we-use-root-relative-fourth
^^^



Found 12 errors
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
import '4'; // ok, we are in only-here-can-we-use-root-relative-fourth
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,5 @@ import nonexistent from 'nonexistent'
import sub_nonexistent from 'subdir/nonexistent'

require('js_flow') as 4 // resolved, but number ~> 4 error

import '4'; // error: not in only-here-can-we-use-root-relative-fourth

0 comments on commit d7815da

Please sign in to comment.