Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions compiler/bsc/rescript_compiler_main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,8 @@ let setup_outcome_printer () = Lazy.force Res_outcome_printer.setup

let setup_runtime_path path = Runtime_package.path := path

let print_suggested_actions_if_any () = Suggested_actions.print_block_if_any ()

let process_file sourcefile ?kind ppf =
(* This is a better default then "", it will be changed later
The {!Location.input_name} relies on that we write the binary ast
Expand Down Expand Up @@ -114,6 +116,7 @@ let reprint_source_file sourcefile =
if parse_result.invalid then (
Res_diagnostics.print_report parse_result.diagnostics
parse_result.source;
print_suggested_actions_if_any ();
exit 1);
Res_compmisc.init_path ();
parse_result.parsetree
Expand All @@ -131,6 +134,7 @@ let reprint_source_file sourcefile =
if parse_result.invalid then (
Res_diagnostics.print_report parse_result.diagnostics
parse_result.source;
print_suggested_actions_if_any ();
exit 1);
Res_compmisc.init_path ();
parse_result.parsetree
Expand All @@ -143,6 +147,7 @@ let reprint_source_file sourcefile =
print_endline
("Invalid input for reprinting ReScript source. Must be a ReScript \
file: " ^ sourcefile);
print_suggested_actions_if_any ();
exit 2
in
res
Expand Down Expand Up @@ -198,6 +203,7 @@ let bs_version_string = "ReScript " ^ Bs_version.version

let print_version_string () =
print_endline bs_version_string;
print_suggested_actions_if_any ();
exit 0

let[@inline] set s : Bsc_args.spec = Unit (Unit_set s)
Expand Down Expand Up @@ -358,6 +364,14 @@ let command_line_flags : (string * Bsc_args.spec * string) array =
Clflags.ignore_parse_errors := true;
Js_config.cmi_only := true),
"*internal* Enable editor mode." );
( "-llm-mode",
unit_call (fun () ->
Js_config.llm_mode := true;
Clflags.binary_annotations := false;
Clflags.color := Some Never;
Location.draw_underline_in_code_frame := true;
Warnings.llm_mode := true),
"*internal* Enable LLM mode for enhanced tooling" );
( "-ignore-parse-errors",
set Clflags.ignore_parse_errors,
"*internal* continue after parse errors" );
Expand Down Expand Up @@ -443,7 +457,11 @@ let _ : unit =
try Bsc_args.parse_exn ~argv:Sys.argv command_line_flags anonymous ~usage with
| Bsc_args.Bad msg ->
Format.eprintf "%s@." msg;
print_suggested_actions_if_any ();
exit 2
| x ->
Location.report_exception ppf x;
print_suggested_actions_if_any ();
exit 2

let _ = print_suggested_actions_if_any ()
2 changes: 2 additions & 0 deletions compiler/common/js_config.ml
Original file line number Diff line number Diff line change
Expand Up @@ -72,3 +72,5 @@ let jsx_module_of_string = function

let as_pp = ref false
let self_stack : string Stack.t = Stack.create ()

let llm_mode = ref false
2 changes: 2 additions & 0 deletions compiler/common/js_config.mli
Original file line number Diff line number Diff line change
Expand Up @@ -101,3 +101,5 @@ val jsx_module_of_string : string -> jsx_module
val as_pp : bool ref

val self_stack : string Stack.t

val llm_mode : bool ref
64 changes: 64 additions & 0 deletions compiler/ext/suggested_actions.ml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
type suggested_action =
| ApplyAutomaticMigrationsForFullProject
| ApplyAutomaticMigrationsForCurrentFile

type record = {loc: Warnings.loc; action: suggested_action}

let suggestions : record list ref = ref []

let file_of_record (r : record) = r.loc.loc_start.pos_fname

let add r =
let exists =
List.exists
(fun x -> x.action = r.action && file_of_record x = file_of_record r)
!suggestions
in
if not exists then suggestions := r :: !suggestions

let clear () = suggestions := []

let has r =
List.exists
(fun x -> x.action = r.action && file_of_record x = file_of_record r)
!suggestions

let action_id_json = function
| ApplyAutomaticMigrationsForFullProject ->
"\"ApplyAutomaticMigrationsForFullProject\""
| ApplyAutomaticMigrationsForCurrentFile ->
"\"ApplyAutomaticMigrationsForCurrentFile\""

let print_block_if_any () =
match List.rev !suggestions with
| [] -> ()
| xs ->
let tag_name_of_action = function
| ApplyAutomaticMigrationsForCurrentFile -> "apply_all_migrations_in_file"
| ApplyAutomaticMigrationsForFullProject ->
"apply_all_migrations_in_project"
in
let description_of_action = function
| ApplyAutomaticMigrationsForCurrentFile ->
"Applies all automatic migrations in the current file."
| ApplyAutomaticMigrationsForFullProject ->
"Applies all automatic migrations in the project."
in
let print_entry ({loc; action} : record) =
let path =
let f = loc.loc_start.pos_fname in
if Filename.is_relative f then Ext_path.absolute_cwd_path f else f
in
let tag = tag_name_of_action action in
Printf.printf " <%s>\n" tag;
Printf.printf " <description>%s</description>\n"
(description_of_action action);
print_endline " <command>";
Printf.printf " perform-action %s %s\n" path (action_id_json action);
print_endline " </command>";
Printf.printf " </%s>\n" tag
in
print_endline "<suggested_actions>";
List.iter print_entry xs;
print_endline "</suggested_actions>";
flush stdout
10 changes: 10 additions & 0 deletions compiler/ext/suggested_actions.mli
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type suggested_action =
| ApplyAutomaticMigrationsForFullProject
| ApplyAutomaticMigrationsForCurrentFile

type record = {loc: Warnings.loc; action: suggested_action}

val add : record -> unit
val clear : unit -> unit
val has : record -> bool
val print_block_if_any : unit -> unit
15 changes: 11 additions & 4 deletions compiler/ext/warnings.ml
Original file line number Diff line number Diff line change
Expand Up @@ -296,6 +296,8 @@ let reset () =

let () = reset ()

let llm_mode = ref false

let message = function
| Comment_start -> "this is the start of a comment."
| Comment_not_end -> "this is not the end of a comment."
Expand All @@ -310,10 +312,15 @@ let message = function
^
if can_be_automigrated then
"\n\n\
\ This can be automatically migrated by the ReScript migration tool. \
Run `rescript-tools migrate-all <project-root>` to run all automatic \
migrations available in your project, or `rescript-tools migrate \
<file>` to migrate a single file."
\ This can be automatically migrated by the ReScript migration tool. "
^
if !llm_mode then
"Run the perform-action MCP tool for automatic migration, suggested \
below, to fix this automatically."
else
"Run `rescript-tools migrate-all <project-root>` to run all automatic \
migrations available in your project, or `rescript-tools migrate \
<file>` to migrate a single file."
else ""
| Fragile_match "" -> "this pattern-matching is fragile."
| Fragile_match s ->
Expand Down
2 changes: 2 additions & 0 deletions compiler/ext/warnings.mli
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,5 @@ val loc_to_string : loc -> string
(**
Turn the location into a string with (line,column--line,column) format.
*)

val llm_mode : bool ref
146 changes: 102 additions & 44 deletions compiler/ml/code_frame.ml
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ end

let setup = Color.setup

type gutter = Number of int | Elided
type gutter = Number of int | Elided | UnderlinedRow
type highlighted_string = {s: string; start: int; end_: int}
type line = {gutter: gutter; content: highlighted_string list}

Expand All @@ -116,7 +116,7 @@ type line = {gutter: gutter; content: highlighted_string list}
- center snippet when it's heavily indented
- ellide intermediate lines when the reported range is huge
*)
let print ~is_warning ~src ~(start_pos : Lexing.position)
let print ~is_warning ~draw_underline ~src ~(start_pos : Lexing.position)
~(end_pos : Lexing.position) =
let indent = 2 in
let highlight_line_start_line = start_pos.pos_lnum in
Expand All @@ -134,27 +134,22 @@ let print ~is_warning ~src ~(start_pos : Lexing.position)
let max_line_digits_count = digits_count last_shown_line in
(* TODO: change this back to a fixed 100? *)
(* 3 for separator + the 2 spaces around it *)
let line_width = 78 - max_line_digits_count - indent - 3 in
let line_width = max 1 (78 - max_line_digits_count - indent - 3) in
let lines =
if
start_line_line_offset >= 0
&& end_line_line_end_offset >= start_line_line_offset
then
String.sub src start_line_line_offset
(end_line_line_end_offset - start_line_line_offset)
|> String.split_on_char '\n'
|> filter_mapi (fun i line ->
let line_number = i + first_shown_line in
if more_than_5_highlighted_lines then
if line_number = highlight_line_start_line + 2 then
Some (Elided, line)
else if
line_number > highlight_line_start_line + 2
&& line_number < highlight_line_end_line - 1
then None
else Some (Number line_number, line)
else Some (Number line_number, line))
else []
String.sub src start_line_line_offset
(end_line_line_end_offset - start_line_line_offset)
|> String.split_on_char '\n'
|> filter_mapi (fun i line ->
let line_number = i + first_shown_line in
if more_than_5_highlighted_lines then
if line_number = highlight_line_start_line + 2 then
Some (Elided, line)
else if
line_number > highlight_line_start_line + 2
&& line_number < highlight_line_end_line - 1
then None
else Some (Number line_number, line)
else Some (Number line_number, line))
in
let leading_space_to_cut =
lines
Expand All @@ -178,41 +173,98 @@ let print ~is_warning ~src ~(start_pos : Lexing.position)
String.sub line leading_space_to_cut
(String.length line - leading_space_to_cut)
|> break_long_line line_width
|> List.mapi (fun i line ->
|> List.mapi (fun i chunk ->
match gutter with
| Elided -> {s = line; start = 0; end_ = 0}
| Elided | UnderlinedRow ->
{s = chunk; start = 0; end_ = 0}
| Number line_number ->
let highlight_line_start_offset =
let hl_start_off =
start_pos.pos_cnum - start_pos.pos_bol
in
let highlight_line_end_offset =
end_pos.pos_cnum - end_pos.pos_bol
let hl_end_off = end_pos.pos_cnum - end_pos.pos_bol in
(* Offsets within the trimmed line (after leading-space cut) *)
let trimmed_hl_start =
if line_number = highlight_line_start_line then
max 0 (hl_start_off - leading_space_to_cut)
else if line_number > highlight_line_start_line then 0
else (* before start line *) max_int
in
let start =
if i = 0 && line_number = highlight_line_start_line
then
highlight_line_start_offset - leading_space_to_cut
let trimmed_hl_end =
if line_number < highlight_line_start_line then 0
else if line_number = highlight_line_start_line
&& line_number = highlight_line_end_line
then max 0 (hl_end_off - leading_space_to_cut)
else if line_number = highlight_line_start_line then
(* highlight runs through end of this line *)
String.length chunk (* placeholder; refined below *)
else if line_number < highlight_line_end_line then
(* full line highlight for interior lines *)
String.length chunk (* placeholder; refined below *)
else if line_number = highlight_line_end_line then
max 0 (hl_end_off - leading_space_to_cut)
else 0
in
let end_ =
if line_number < highlight_line_start_line then 0
else if
(* Map highlight to this chunk using its offset *)
let chunk_offset = i * line_width in
let clen = String.length chunk in
let start_rel =
if trimmed_hl_start = max_int then 0
else trimmed_hl_start - chunk_offset
in
let end_rel =
if
line_number = highlight_line_start_line
&& line_number = highlight_line_end_line
then highlight_line_end_offset - leading_space_to_cut
else if line_number = highlight_line_start_line then
String.length line
&& line_number <> highlight_line_end_line
&& i = 0
then clen
else if
line_number > highlight_line_start_line
&& line_number < highlight_line_end_line
then String.length line
else if line_number = highlight_line_end_line then
highlight_line_end_offset - leading_space_to_cut
else 0
then clen
else trimmed_hl_end - chunk_offset
in
{s = line; start; end_})
let start = max 0 (min clen start_rel) in
let end_ = max 0 (min clen end_rel) in
let start, end_ =
if start >= end_ then (0, 0) else (start, end_)
in
{s = chunk; start; end_})
in
{gutter; content = new_content})
if draw_underline then
let has_highlight =
List.exists (fun {start; end_} -> start < end_) new_content
in
if has_highlight then
let underline_content =
List.map
(fun {start; end_} ->
if start < end_ then
let overline_char = "^" in
let underline_length = end_ - start in
let underline =
String.concat ""
(List.init underline_length (fun _ -> overline_char))
in
[
{
s = String.make start ' ' ^ underline;
start = 0;
end_ = 0;
};
]
else [{s = ""; start = 0; end_ = 0}])
new_content
in
[
{gutter; content = new_content};
{
gutter = UnderlinedRow;
content = List.flatten underline_content;
};
]
else [{gutter; content = new_content}]
else [{gutter; content = new_content}])
|> List.flatten
in
let buf = Buffer.create 100 in
let open Color in
Expand Down Expand Up @@ -280,5 +332,11 @@ let print ~is_warning ~src ~(start_pos : Lexing.position)
else NoColor
in
add_ch c ch);
add_ch NoColor '\n')
| UnderlinedRow ->
content
|> List.iter (fun line ->
draw_gutter NoColor "";
line.s |> String.iter (fun ch -> add_ch NoColor ch);
add_ch NoColor '\n'));
Buffer.contents buf
Loading
Loading