diff --git a/Makefile b/Makefile index faa833cbd93..8e42b76a627 100644 --- a/Makefile +++ b/Makefile @@ -346,6 +346,35 @@ js: _build/scripts/ppx_gen_flowlibs.native $(BUILT_OBJECT_FILES) $(COPIED_FLOWLI exit 1; \ fi +REL_DIR=src/commands/config + +flowconfig-js: + $(OCB) \ + -pkgs js_of_ocaml \ + -pkgs js_of_ocaml-ppx \ + -lflags -custom \ + $(INCLUDE_OPTS) $(FINDLIB_OPTS) \ + -lflags "$(BYTECODE_LINKER_FLAGS) -warn-error -31" \ + $(REL_DIR)/flowConfig_dot_js.byte; \ + [ -e "$(REL_DIR)/flowconfig_parser.js" -a "$(REL_DIR)/flowconfig_parser.js" -nt "_build/$(REL_DIR)/flowConfig_dot_js.byte" ] || \ + js_of_ocaml \ + --opt 3 \ + --disable genprim \ + --extern-fs \ + -o $(REL_DIR)/flowconfig_parser.js \ + $(REL_DIR)/js/stdlib.js $(JS_STUBS) \ + _build/$(REL_DIR)/flowConfig_dot_js.byte; + # Disable errors because we are overriding primitives + # ret=$$?; \ + # if [ ! $$ret ]; then \ + # exit $$ret; \ + # elif [ -s _build/$(REL_DIR)/js_of_ocaml.err ]; then \ + # printf "js_of_ocaml produced output on stderr:\n" 1>&2; \ + # cat _build/$(REL_DIR)/js_of_ocaml.err 1>&2; \ + # exit 1; \ + # fi + + dist/flow/flow$(EXE): build-flow mkdir -p $(@D) cp _build/src/flow.native $@ diff --git a/js/str.js b/js/str.js index 2dabb0659e4..bec9ccdc121 100644 --- a/js/str.js +++ b/js/str.js @@ -1,14 +1,217 @@ +// Partially from +// https://github.com/ejgallego/jscoq/blob/2718f9caf31398704c2d84ff089e3f6f0321eada/coq-js/js_stub/str.js + +//Provides: str_ll +function str_ll(s, args) { if (str_ll.log) joo_global_object.console.warn(s, args); } +str_ll.log = false; + +//Provides: re_string_match +//Requires: str_ll, re_match +function re_string_match(re, s, pos) { + // external re_string_match : regexp -> string -> int -> int array + //str_ll('re_string_match', arguments); + var res = re_match(re, s, pos, 0); + return (res === 0) ? [0] : res; +} + //Provides: re_search_forward -function re_search_forward() { - throw new Error("re_search_forward: not implemented in JS"); +//Requires: str_ll, re_search_forward_naive +function re_search_forward(re, s, pos) { + // external re_search_forward: regexp -> string -> int -> int array + //str_ll('re_search_forward', arguments); + return re_search_forward_naive(re, s, pos); } +//Provides: re_partial_match +//Requires: str_ll +// external re_partial_match: regexp -> string -> int -> int array +function re_partial_match() { + //str_ll('re_partial_match', arguments); + return [0]; } //Provides: re_replacement_text -function re_replacement_text() { - throw new Error('re_replacement_text: not implemented in JS'); -} +//Requires: str_ll +// external re_replacement_text: string -> int array -> string -> string +function re_replacement_text(r, a, s) { + //str_ll('re_replacement_text', arguments); + return s; } +//Provides: re_search_backward +//Requires: str_ll +// external re_search_backward: regexp -> string -> int -> int array +function re_search_backward() { + //str_ll('re_search_backward', arguments); + return [0]; } -//Provides: re_string_match -function re_string_match() { - throw new Error('re_string_match: not implemented in JS'); +//Provides: re_match +// Based on +// https://github.com/ocaml/ocaml/blob/4.07/otherlibs/str/strstubs.c + +var re_match = function () { + + var opcodes = { + CHAR: 0, CHARNORM: 1, STRING: 2, STRINGNORM: 3, CHARCLASS: 4, + BOL: 5, EOL: 6, WORDBOUNDARY: 7, + BEGGROUP: 8, ENDGROUP: 9, REFGROUP: 10, + ACCEPT: 11, + SIMPLEOPT: 12, SIMPLESTAR: 13, SIMPLEPLUS: 14, + GOTO: 15, PUSHBACK: 16, SETMARK: 17, + CHECKPROGRESS: 18 + }; + + function in_bitset(s, i) { + return (s.c.charCodeAt(i >> 3) >> (i & 7)) & 1; + } + + function re_match_impl(re, s, pos, partial) { + + var prog = re[1].slice(1), + cpool = re[2].slice(1), + numgroups = re[4], + numregisters = re[5], + startchars = re[6]; + + var pc = 0, quit = false, txt = s.c, + stack = [], + groups = new Array(numgroups).fill(0).map(function () { return {}; }), + re_register = new Array(numregisters); + + groups[0].start = pos; + + var backtrack = function () { + while (stack.length) { + var item = stack.pop(), obj, prop; + if (item.undo) { + [obj, prop] = item.undo.loc; + obj[prop] = item.undo.value; + } + else { + [pc, pos] = [item.pos.pc, item.pos.txt]; + return; + } + } + quit = true; + }; + var push = function (item) { stack.push(item); }; + + + var accept = function () { + return Array.prototype.concat.apply([0], + groups.map(function (g) { return g.start >= 0 && g.end >= 0 ? [g.start, g.end] : [-1, -1]; })) + }; + + /* Main DFA interpreter loop */ + while (!quit) { + var op = prog[pc] & 0xff, + sarg = prog[pc] >> 8, + uarg = sarg & 0xff, + c = txt.charCodeAt(pos), + group; + + pc++; + + switch (op) { + case opcodes.CHAR: + case opcodes.CHARNORM: + if (c === uarg) pos++; + else backtrack(); + break; + case opcodes.STRING: + case opcodes.STRINGNORM: + for (var w = cpool[uarg].c, i = 0; i < w.length; i++) { + if (c === w.charCodeAt(i)) + c = txt.charCodeAt(++pos); + else { backtrack(); break; } + } + break; + case opcodes.CHARCLASS: + if (!isNaN(c) && in_bitset(cpool[uarg], c)) pos++; + else backtrack(); + break; + + case opcodes.BOL: + if (pos > c.t && !isNaN(c) && txt.charCodeAt(pos - 1) !== '\n') + backtrack() + break + + case opcodes.EOL: + if (pos < c.l && !isNaN(c) && c !== '\n') + backtrack() + break + + case opcodes.BEGGROUP: + group = groups[uarg]; + push({ + undo: { + loc: [group, 'start'], + value: group.start + } + }); + group.start = pos; + break; + case opcodes.ENDGROUP: + group = groups[uarg]; + push({ + undo: { + loc: [group, 'end'], + value: group.end + } + }); + group.end = pos; + break; + + case opcodes.SIMPLEOPT: + if (!isNaN(c) && in_bitset(cpool[uarg], c)) pos++; + break; + case opcodes.SIMPLESTAR: + while (!isNaN(c) && in_bitset(cpool[uarg], c)) + c = txt.charCodeAt(++pos); + break; + case opcodes.SIMPLEPLUS: + if (!isNaN(c) && in_bitset(cpool[uarg], c)) { + do { + c = txt.charCodeAt(++pos); + } while (!isNaN(c) && in_bitset(cpool[uarg], c)); + } + else backtrack(); + break; + + case opcodes.ACCEPT: + groups[0].end = pos; + return accept(); + + case opcodes.GOTO: + pc = pc + sarg; + break; + case opcodes.PUSHBACK: + push({ pos: { pc: pc + sarg, txt: pos } }); + break; + case opcodes.SETMARK: + push({ undo: { loc: [re_register, uarg], value: re_register[uarg] } }); + re_register[uarg] = pos; + break; + case opcodes.CHECKPROGRESS: + if (re_register[uarg] === pos) backtrack(); + break; + + default: + throw new Error("unimplemented regexp opcode " + op + "(" + sarg + ")"); + } + } + + return 0; + } + + return re_match_impl; +}(); + + +//Provides: re_search_forward_naive +//Requires: re_match +function re_search_forward_naive(re, s, pos) { + while (pos < s.l) { + var res = re_match(re, s, pos); + if (res) return res; + pos++; + } + + return [0]; /* [||] : int array */ } diff --git a/packages/flowconfig-parser/.gitignore b/packages/flowconfig-parser/.gitignore new file mode 100644 index 00000000000..50ec8f75f8e --- /dev/null +++ b/packages/flowconfig-parser/.gitignore @@ -0,0 +1,2 @@ +/node_modues/ +flowconfig_parser.js \ No newline at end of file diff --git a/packages/flowconfig-parser/Makefile b/packages/flowconfig-parser/Makefile new file mode 100644 index 00000000000..692f5b69e0d --- /dev/null +++ b/packages/flowconfig-parser/Makefile @@ -0,0 +1,26 @@ +# Copyright (c) Facebook, Inc. and its affiliates. +# +# This source code is licensed under the MIT license found in the +# LICENSE file in the root directory of this source tree. + +.PHONY: all +all: js + +.PHONY: clean +clean: + rm -rf flowconfig_parser.js + +# copies flowconfig_parser.js into place, either using a copy that was put in +# dist/flowconfig_parser.js externally (e.g. during a Circle CI build), or by +# building it. +.PHONY: flowconfig_parser.js +flowconfig_parser.js: + if [ -e dist/flowconfig_parser.js ]; then \ + [ $@ -nt dist/flowconfig_parser.js ] || cp dist/flowconfig_parser.js $@; \ + else \ + cd ../..; \ + $(MAKE) flowconfig-js; \ + cp src/commands/config/flowconfig_parser.js $@; \ + fi + +js: flowconfig_parser.js diff --git a/packages/flowconfig-parser/package.json b/packages/flowconfig-parser/package.json new file mode 100644 index 00000000000..d39a05dec6a --- /dev/null +++ b/packages/flowconfig-parser/package.json @@ -0,0 +1,26 @@ +{ + "name": "flowconfig-parser", + "version": "0.98.1", + "homepage": "https://flow.org", + "license": "MIT", + "author": { + "name": "Flow Team", + "email": "flow@fb.com" + }, + "files": [ + "flowconfig_parser.js" + ], + "main": "flowconfig_parser.js", + "repository": { + "type": "git", + "url": "https://github.com/facebook/flow.git" + }, + "scripts": { + "test": "node test/run_tests.js", + "prepublish": "make js" + }, + "dependencies": {}, + "engines": { + "node": ">=0.4.0" + } +} diff --git a/src/commands/config/.gitignore b/src/commands/config/.gitignore new file mode 100644 index 00000000000..8691b1d271e --- /dev/null +++ b/src/commands/config/.gitignore @@ -0,0 +1 @@ +flowconfig_parser.js \ No newline at end of file diff --git a/src/commands/config/flowConfig.mli b/src/commands/config/flowConfig.mli index b1fd17a664c..b018e1653a7 100644 --- a/src/commands/config/flowConfig.mli +++ b/src/commands/config/flowConfig.mli @@ -6,6 +6,8 @@ *) type config + +type line = int * string type warning = int * string type error = int * string @@ -13,6 +15,9 @@ val get: ?allow_cache:bool -> string -> (config * warning list, error) result val get_hash: ?allow_cache:bool -> string -> Xx.hash val empty_config: config +val is_not_comment: line -> bool +val parse: config -> line list -> (config * warning list, error) result + val init: ignores: string list -> untyped: string list -> diff --git a/src/commands/config/flowConfig_dot_js.ml b/src/commands/config/flowConfig_dot_js.ml new file mode 100644 index 00000000000..3ae34879b62 --- /dev/null +++ b/src/commands/config/flowConfig_dot_js.ml @@ -0,0 +1,176 @@ +open Js_of_ocaml +open FlowConfig + +let (%%) f g x = f (g x) + +let to_js_string_array (list: string list) = + Js.array (Array.of_list (List.map Js.string list)) + +let array_of_list f lst = + Array.of_list (List.map f lst) + +let set_to_js + (type t elt) + (module S: Set.S with type t = t and type elt = elt) + (t: t) + (f: elt -> 'b) + : 'b Js.js_array Js.t += + let els = S.elements t in + let set = Js.array @@ Array.of_list @@ List.map f els in + set + +let sset_to_js t = + set_to_js (module SSet) t Js.string + +let strict_mode_settings_to_js (t: StrictModeSettings.t) = + let els = StrictModeSettings.elements t in + let set = Js.array @@ Array.of_list @@ List.map (Js.string %% Lints.string_of_kind) els in + set + +let lintmap_to_js (t: 'a Lints.LintMap.t) (f: 'a -> 'b) = + let module M = Lints.LintMap in + let props = Array.of_list ( + M.fold (fun k v acc -> + (Lints.string_of_kind k, Js.Unsafe.inject (f v)) :: acc) t [] + ) in + let map = Js.Unsafe.inject (Js.Unsafe.obj props) in + map + +let js_opt t f = match t with + | Some t -> Js.some (f t) + | None -> Js.null + +let opts_to_js t = FlowConfig.(object%js + val all = Js.bool (all t) + val emoji = Js.bool (emoji t) + val enableConstParams = Js.bool (enable_const_params t) + val enforceStrictCallArity = Js.bool (enforce_strict_call_arity t) + val enforceWellFormedExports = Js.bool (enforce_well_formed_exports t) + val enforceWellFormedExportsWhitelist = to_js_string_array (enforce_well_formed_exports_whitelist t) + val esproposalClassInstanceFields = Js.string (Options.esproposal_feature_mode_to_string (esproposal_class_instance_fields t)) + val esproposalClassStaticFields = Js.string (Options.esproposal_feature_mode_to_string (esproposal_class_static_fields t)) + val esproposalDecorators = Js.string (Options.esproposal_feature_mode_to_string (esproposal_decorators t)) + val esproposalExportStarAs = Js.string (Options.esproposal_feature_mode_to_string (esproposal_export_star_as t)) + val esproposalNullishCoalescing = Js.string (Options.esproposal_feature_mode_to_string (esproposal_nullish_coalescing t)) + val esproposalOptionalChaining = Js.string (Options.esproposal_feature_mode_to_string (esproposal_optional_chaining t)) + val facebookFbs = js_opt (facebook_fbs t) Js.string + val facebookFbt = js_opt (facebook_fbt t) Js.string + val fileWatcher = js_opt (file_watcher t) (Js.string %% Options.file_watcher_to_string) + val hasteModuleRefPrefix = js_opt (haste_module_ref_prefix t) Js.string + val hasteNameReducers = Js.null (* TODO: Regexp *) + val hastePathsBlacklist = to_js_string_array (haste_paths_blacklist t) + val hastePathsWhitelist = to_js_string_array (haste_paths_whitelist t) + val hasteUseNameReducers = Js.bool (haste_use_name_reducers t) + val ignoreNonLiteralRequires = Js.bool (ignore_non_literal_requires t) + val includeWarnings = Js.bool (include_warnings t) + val lazyMode = js_opt (lazy_mode t) (Js.string %% Options.lazy_mode_to_string) + val logFile = js_opt (log_file t) (fun t -> Js.string (Path.to_string t)) + val maxHeaderTokens = max_header_tokens t + val maxLiteralLength = max_literal_length t + val maxWorkers = max_workers t + val mergeTimeout = js_opt (merge_timeout t) (fun t -> t) + val moduleFileExts = sset_to_js (module_file_exts t) + val moduleNameMappers = Js.null (* TODO: Regexp *) + val moduleResolver = js_opt (module_resolver t) (fun t -> Js.string (Path.to_string t)) + val moduleResourceExts = sset_to_js (module_resource_exts t) + val moduleSystem = Js.string (Options.module_system_to_string (module_system t)) + val modulesAreUseStrict = Js.bool (modules_are_use_strict t) + val mungeUnderscores = Js.bool (munge_underscores t) + val noFlowlib = Js.bool (no_flowlib t) + val nodeResolverDirnames = to_js_string_array (node_resolver_dirnames t) + val rootName = js_opt (root_name t) Js.string + val savedStateFetcher = Js.string (Options.saved_state_fetcher_to_string (saved_state_fetcher t)) + val shmDepTablePow = shm_dep_table_pow t + val shmDirs = to_js_string_array (shm_dirs t) + val shmGlobalSize = shm_global_size t + val shmHashTablePow = shm_hash_table_pow t + val shmHeapSize = shm_heap_size t + val shmLogLevel = shm_log_level t + val shmMinAvail = shm_min_avail t + val suppressComments = Js.array (Array.of_list [Js.null]) (* TODO: Regexp *) + val suppressTypes = sset_to_js (suppress_types t) + val tempDir = Js.string (temp_dir t) + val traces = traces t + val trustMode = Js.string (Options.trust_mode_to_string (trust_mode t)) + val version = js_opt (required_version t) Js.string + val waitForRecheck = Js.bool (wait_for_recheck t) + val weak = Js.bool (weak t) +end) + +let file_key_to_js (t: File_key.t) = + let make1 typ str1 = object%js + val _type = Js.string typ + val value = Js.string str1 + end in + let make0 typ = object%js + val _type = Js.string typ + end in + File_key.( + match t with + | LibFile s -> Js.Unsafe.inject (make1 "LibFile" s) + | SourceFile s -> Js.Unsafe.inject (make1 "SourceFile" s) + | JsonFile s -> Js.Unsafe.inject (make1 "JsonFile" s) + | ResourceFile s -> Js.Unsafe.inject (make1 "ResourceFile" s) + | Builtins -> Js.Unsafe.inject (make0 "Builtins") + ) + +let position_to_js (p: Loc.position) = Loc.(object%js + val line = p.line + val column = p.column +end) + +let loc_to_js (l: Loc.t) = Loc.(object%js + val source = js_opt l.source file_key_to_js + val start = position_to_js l.start + val _end = position_to_js l._end +end) + +let severity_lintSettings_to_js (t: Severity.severity LintSettings.t) = object%js + val defaultValue = Js.string @@ Severity.string_of_severity (LintSettings.get_default t) + val explicitValues = + let f (s, l) = + let s' = Js.string @@ Severity.string_of_severity s in + let l' = js_opt l loc_to_js in + Js.array [|Js.Unsafe.inject s'; Js.Unsafe.inject l'|] + in + lintmap_to_js (LintSettings.get_explicit_values t) f +end + +let config_to_js (config: config) = FlowConfig.(object%js + val ignores = to_js_string_array (ignores config) + val untyped = to_js_string_array (untyped config) + val declarations = to_js_string_array (declarations config) + val includes = to_js_string_array (includes config) + val libs = to_js_string_array (libs config) + val lintSeverities = severity_lintSettings_to_js (lint_severities config) + val strictMode = strict_mode_settings_to_js (strict_mode config) + val options = opts_to_js config +end) + +let warnings_to_js warnings = + let warning_to_js (n, s) = Js.array (Array.of_list [ + Js.Unsafe.inject n; + Js.Unsafe.inject (Js.string s) + ]) in + let items = array_of_list warning_to_js warnings in + Js.array items + +let parse contents = + let contents = Js.to_string contents in + let config = FlowConfig.empty_config in + let lines = contents + |> Sys_utils.split_lines + |> List.mapi (fun i line -> (i+1, String.trim line)) + |> List.filter FlowConfig.is_not_comment + in + match FlowConfig.parse config lines with + | Error (line, msg) -> failwith + @@ "parse error " ^ string_of_int line ^ ": " ^ msg + | Ok (config, warnings) -> object%js + val config = config_to_js config + val warnings = warnings_to_js warnings + end + +let () = Js.export + "parse" (Js.wrap_callback parse) diff --git a/src/commands/config/js/stdlib.js b/src/commands/config/js/stdlib.js new file mode 100644 index 00000000000..e7ae5684af7 --- /dev/null +++ b/src/commands/config/js/stdlib.js @@ -0,0 +1,21 @@ +// Stdlib overrides until js_of_ocaml fixes it + +//Provides: caml_parse_sign_and_base +//Requires: caml_string_unsafe_get, caml_ml_string_length +function caml_parse_sign_and_base(s) { + var i = 0, len = caml_ml_string_length(s), base = 10, sign = 1; + if (len > 0) { + switch (caml_string_unsafe_get(s, i)) { + case 45: i++; sign = -1; break; + case 43: i++; sign = 1; break; + } + } + if (i + 1 < len && caml_string_unsafe_get(s, i) == 48) + switch (caml_string_unsafe_get(s, i + 1)) { + case 120: case 88: base = 16; i += 2; break; + case 111: case 79: base = 8; i += 2; break; + case 98: case 66: base = 2; i += 2; break; + case 117: case 85: sign = 0; i += 2; break; + } + return [i, sign, base]; +} \ No newline at end of file diff --git a/src/common/lints/lintSettings.ml b/src/common/lints/lintSettings.ml index 5c305a76f94..39bfd375c3f 100644 --- a/src/common/lints/lintSettings.ml +++ b/src/common/lints/lintSettings.ml @@ -46,6 +46,8 @@ let set_all entries settings = let get_default settings = settings.default_value +let get_explicit_values settings = settings.explicit_values + let get_value lint_kind settings = LintMap.get lint_kind settings.explicit_values |> Option.value_map ~f:fst ~default:settings.default_value diff --git a/src/common/lints/lintSettings.mli b/src/common/lints/lintSettings.mli index a25c3183cb9..9646d6d157a 100644 --- a/src/common/lints/lintSettings.mli +++ b/src/common/lints/lintSettings.mli @@ -17,6 +17,7 @@ val set_value: lint_kind -> ('a * Loc.t option) -> 'a t -> 'a t val set_all: (lint_kind * ('a * Loc.t option)) list -> 'a t -> 'a t val get_default: 'a t -> 'a +val get_explicit_values: 'a t -> ('a * Loc.t option) LintMap.t (* Get the state of a lint kind in the provided settings *) val get_value: lint_kind -> 'a t -> 'a (* True iff the severity for the provided lint has been explicitly set *) diff --git a/src/common/lints/strictModeSettings.ml b/src/common/lints/strictModeSettings.ml index 9d840d848e6..c961dd120c3 100644 --- a/src/common/lints/strictModeSettings.ml +++ b/src/common/lints/strictModeSettings.ml @@ -12,6 +12,7 @@ type t = LintSet.t let empty = LintSet.empty let fold = LintSet.fold let iter = LintSet.iter +let elements = LintSet.elements let of_lines = let parse_line (label, line) = diff --git a/src/common/lints/strictModeSettings.mli b/src/common/lints/strictModeSettings.mli index 03f71777724..01201b26d7f 100644 --- a/src/common/lints/strictModeSettings.mli +++ b/src/common/lints/strictModeSettings.mli @@ -13,3 +13,4 @@ val empty: t val fold: (lint_kind -> 'acc -> 'acc) -> t -> 'acc -> 'acc val iter: (lint_kind -> unit) -> t -> unit val of_lines: (int * string) list -> (t, int * string) result +val elements: t -> lint_kind list diff --git a/src/common/options.ml b/src/common/options.ml index ef48b6a2f37..0383764cac0 100644 --- a/src/common/options.ml +++ b/src/common/options.ml @@ -180,3 +180,32 @@ let lazy_mode_to_string lazy_mode = | LAZY_MODE_IDE -> "ide" | LAZY_MODE_WATCHMAN -> "watchman" | NON_LAZY_MODE -> "none" + +let esproposal_feature_mode_to_string esproposal_feature_mode = + match esproposal_feature_mode with + | ESPROPOSAL_ENABLE -> "enable" + | ESPROPOSAL_IGNORE -> "ignore" + | ESPROPOSAL_WARN -> "warn" + +let file_watcher_to_string file_watcher = + match file_watcher with + | NoFileWatcher -> "none" + | DFind -> "dfind" + | Watchman -> "watchman" + +let module_system_to_string module_system = + match module_system with + | Node -> "node" + | Haste -> "haste" + +let saved_state_fetcher_to_string saved_state_fetcher = + match saved_state_fetcher with + | Dummy_fetcher -> "none" + | Local_fetcher -> "local" + | Fb_fetcher -> "fb" + +let trust_mode_to_string trust_mode = + match trust_mode with + | NoTrust -> "none" + | SilentTrust -> "silent" + | CheckTrust -> "check" \ No newline at end of file