From 1d792f083e5f3b75430d293fd93f0112f5940481 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Thu, 24 Mar 2022 11:31:26 +0100 Subject: [PATCH 1/8] Test action if-then-else to switch. --- analysis/src/Actions.ml | 89 +++++++++++++++++++++ analysis/src/Commands.ml | 5 ++ analysis/tests/src/Actions.res | 13 +++ analysis/tests/src/expected/Actions.res.txt | 34 ++++++++ analysis/tests/src/expected/Debug.res.txt | 3 +- 5 files changed, 143 insertions(+), 1 deletion(-) create mode 100644 analysis/src/Actions.ml create mode 100644 analysis/tests/src/Actions.res create mode 100644 analysis/tests/src/expected/Actions.res.txt diff --git a/analysis/src/Actions.ml b/analysis/src/Actions.ml new file mode 100644 index 000000000..6d8496c6d --- /dev/null +++ b/analysis/src/Actions.ml @@ -0,0 +1,89 @@ +let posInLoc ~pos ~loc = + Utils.tupleOfLexing loc.Location.loc_start <= pos + && pos < Utils.tupleOfLexing loc.loc_end + +let rec expToPat (exp : Parsetree.expression) = + let mkPat ppat_desc = + Ast_helper.Pat.mk ~loc:exp.pexp_loc ~attrs:exp.pexp_attributes ppat_desc + in + match exp.pexp_desc with + | Pexp_construct (lid, None) -> + Some (mkPat (Parsetree.Ppat_construct (lid, None))) + | Pexp_construct (lid, Some e1) -> ( + match expToPat e1 with + | None -> None + | Some p1 -> Some (mkPat (Parsetree.Ppat_construct (lid, Some p1)))) + | Pexp_constant c -> Some (mkPat (Parsetree.Ppat_constant c)) + | _ -> None + +let mkMapper ~pos ~changed = + let value_binding (mapper : Ast_mapper.mapper) (vb : Parsetree.value_binding) + = + let newExp = + match vb.pvb_pat.ppat_desc with + | Ppat_var {txt; loc} when posInLoc ~pos ~loc -> ( + match vb.pvb_expr.pexp_desc with + | Pexp_ifthenelse + ( { + pexp_desc = + Pexp_apply + ( { + pexp_desc = + Pexp_ident {txt = Lident (("=" | "<>") as op)}; + }, + [(Nolabel, arg1); (Nolabel, arg2)] ); + }, + e1, + Some e2 ) -> ( + let e1, e2 = if op = "=" then (e1, e2) else (e2, e1) in + let mkMatch ~arg ~pat = + let cases = + [ + Ast_helper.Exp.case pat e1; + Ast_helper.Exp.case (Ast_helper.Pat.any ()) e2; + ] + in + Ast_helper.Exp.match_ ~loc:vb.pvb_expr.pexp_loc + ~attrs:vb.pvb_expr.pexp_attributes arg cases + in + + match expToPat arg2 with + | None -> ( + match expToPat arg1 with + | None -> None + | Some pat1 -> + let newExp = mkMatch ~arg:arg2 ~pat:pat1 in + Printf.printf "Hit %s\n" txt; + Some newExp) + | Some pat2 -> + let newExp = mkMatch ~arg:arg1 ~pat:pat2 in + Printf.printf "Hit %s\n" txt; + Some newExp) + | _ -> None) + | _ -> None + in + match newExp with + | Some newExp -> + changed := true; + {vb with pvb_expr = newExp} + | None -> Ast_mapper.default_mapper.value_binding mapper vb + in + + {Ast_mapper.default_mapper with value_binding} + +let command ~path ~pos = + if Filename.check_suffix path ".res" then + let parser = + Res_driver.parsingEngine.parseImplementation ~forPrinter:false + in + let {Res_driver.parsetree = structure; comments; filename} = + parser ~filename:path + in + let printer = + Res_driver.printEngine.printImplementation + ~width:!Res_cli.ResClflags.width ~comments ~filename + in + let changed = ref false in + let mapper = mkMapper ~pos ~changed in + let newStructure = mapper.structure mapper structure in + if !changed then printer newStructure diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 1c4cbdbbc..1586f0fc5 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -400,6 +400,11 @@ let test ~path = SemanticTokens.command ~debug:true ~emitter:(SemanticTokens.Token.createEmitter ()) ~path + | "act" -> + print_endline + ("Actions " ^ path ^ " " ^ string_of_int line ^ ":" + ^ string_of_int col); + Actions.command ~path ~pos:(line, col) | _ -> ()); print_newline ()) in diff --git a/analysis/tests/src/Actions.res b/analysis/tests/src/Actions.res new file mode 100644 index 000000000..4f1228867 --- /dev/null +++ b/analysis/tests/src/Actions.res @@ -0,0 +1,13 @@ +type kind = First | Second | Third + +let _ = (kind, kindStr) => { + let _ifThenElse = if kind == First { + // ^act + "First" + } else { + "Not First" + } + + let _ternary = "First" != kindStr ? "Not First" : "First" + // ^act +} diff --git a/analysis/tests/src/expected/Actions.res.txt b/analysis/tests/src/expected/Actions.res.txt new file mode 100644 index 000000000..a128d4f7a --- /dev/null +++ b/analysis/tests/src/expected/Actions.res.txt @@ -0,0 +1,34 @@ +Actions tests/src/Actions.res 3:10 +Hit _ifThenElse +type kind = First | Second | Third + +let _ = (kind, kindStr) => { + let _ifThenElse = switch kind { + | First => // ^act + "First" + | _ => "Not First" + } + + let _ternary = "First" != kindStr ? "Not First" : "First" + // ^act +} + +Actions tests/src/Actions.res 10:9 +Hit _ternary +type kind = First | Second | Third + +let _ = (kind, kindStr) => { + let _ifThenElse = if kind == First { + // ^act + "First" + } else { + "Not First" + } + + let _ternary = switch kindStr { + | "First" => "First" + | _ => "Not First" + } + // ^act +} + diff --git a/analysis/tests/src/expected/Debug.res.txt b/analysis/tests/src/expected/Debug.res.txt index 2897bf280..f1087de8f 100644 --- a/analysis/tests/src/expected/Debug.res.txt +++ b/analysis/tests/src/expected/Debug.res.txt @@ -4,7 +4,8 @@ Dependencies: @rescript/react Source directories: tests/node_modules/@rescript/react/src tests/node_modules/@rescript/react/src/legacy Source files: tests/node_modules/@rescript/react/src/React.res tests/node_modules/@rescript/react/src/ReactDOM.res tests/node_modules/@rescript/react/src/ReactDOMServer.res tests/node_modules/@rescript/react/src/ReactDOMStyle.res tests/node_modules/@rescript/react/src/ReactEvent.res tests/node_modules/@rescript/react/src/ReactEvent.resi tests/node_modules/@rescript/react/src/ReactTestUtils.res tests/node_modules/@rescript/react/src/ReactTestUtils.resi tests/node_modules/@rescript/react/src/RescriptReactErrorBoundary.res tests/node_modules/@rescript/react/src/RescriptReactErrorBoundary.resi tests/node_modules/@rescript/react/src/RescriptReactRouter.res tests/node_modules/@rescript/react/src/RescriptReactRouter.resi tests/node_modules/@rescript/react/src/legacy/ReactDOMRe.res tests/node_modules/@rescript/react/src/legacy/ReasonReact.res Source directories: tests/src tests/src/expected -Source files: tests/src/Auto.res tests/src/CompletePrioritize1.res tests/src/CompletePrioritize2.res tests/src/Completion.res tests/src/Component.res tests/src/Component.resi tests/src/Cross.res tests/src/Debug.res tests/src/Definition.res tests/src/DefinitionWithInterface.res tests/src/DefinitionWithInterface.resi tests/src/Div.res tests/src/Fragment.res tests/src/Highlight.res tests/src/Hover.res tests/src/Jsx.res tests/src/Jsx.resi tests/src/LongIdentTest.res tests/src/Obj.res tests/src/Patterns.res tests/src/RecModules.res tests/src/RecordCompletion.res tests/src/References.res tests/src/ReferencesWithInterface.res tests/src/ReferencesWithInterface.resi tests/src/Rename.res tests/src/RenameWithInterface.res tests/src/RenameWithInterface.resi tests/src/TableclothMap.ml tests/src/TableclothMap.mli tests/src/TypeDefinition.res +Source files: tests/src/Actions.res tests/src/Auto.res tests/src/CompletePrioritize1.res tests/src/CompletePrioritize2.res tests/src/Completion.res tests/src/Component.res tests/src/Component.resi tests/src/Cross.res tests/src/Debug.res tests/src/Definition.res tests/src/DefinitionWithInterface.res tests/src/DefinitionWithInterface.resi tests/src/Div.res tests/src/Fragment.res tests/src/Highlight.res tests/src/Hover.res tests/src/Jsx.res tests/src/Jsx.resi tests/src/LongIdentTest.res tests/src/Obj.res tests/src/Patterns.res tests/src/RecModules.res tests/src/RecordCompletion.res tests/src/References.res tests/src/ReferencesWithInterface.res tests/src/ReferencesWithInterface.resi tests/src/Rename.res tests/src/RenameWithInterface.res tests/src/RenameWithInterface.resi tests/src/TableclothMap.ml tests/src/TableclothMap.mli tests/src/TypeDefinition.res +Impl cmt:tests/lib/bs/src/Actions.cmt res:tests/src/Actions.res Impl cmt:tests/lib/bs/src/Auto.cmt res:tests/src/Auto.res Impl cmt:tests/lib/bs/src/CompletePrioritize1.cmt res:tests/src/CompletePrioritize1.res Impl cmt:tests/lib/bs/src/CompletePrioritize2.cmt res:tests/src/CompletePrioritize2.res From 308ca1a990b27d2d28360930eedc29f532a59a1b Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 25 Mar 2022 09:19:58 +0100 Subject: [PATCH 2/8] Print to a string, not standard output. --- analysis/src/Actions.ml | 11 +++++------ analysis/tests/src/expected/Actions.res.txt | 2 ++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/analysis/src/Actions.ml b/analysis/src/Actions.ml index 6d8496c6d..a5a147207 100644 --- a/analysis/src/Actions.ml +++ b/analysis/src/Actions.ml @@ -76,14 +76,13 @@ let command ~path ~pos = let parser = Res_driver.parsingEngine.parseImplementation ~forPrinter:false in - let {Res_driver.parsetree = structure; comments; filename} = - parser ~filename:path - in + let {Res_driver.parsetree = structure; comments} = parser ~filename:path in let printer = - Res_driver.printEngine.printImplementation - ~width:!Res_cli.ResClflags.width ~comments ~filename + Res_printer.printImplementation ~width:!Res_cli.ResClflags.width ~comments in let changed = ref false in let mapper = mkMapper ~pos ~changed in let newStructure = mapper.structure mapper structure in - if !changed then printer newStructure + if !changed then + let formatted = printer newStructure in + Printf.printf "Formatted:\n%s" formatted diff --git a/analysis/tests/src/expected/Actions.res.txt b/analysis/tests/src/expected/Actions.res.txt index a128d4f7a..288ad10d1 100644 --- a/analysis/tests/src/expected/Actions.res.txt +++ b/analysis/tests/src/expected/Actions.res.txt @@ -1,5 +1,6 @@ Actions tests/src/Actions.res 3:10 Hit _ifThenElse +Formatted: type kind = First | Second | Third let _ = (kind, kindStr) => { @@ -15,6 +16,7 @@ let _ = (kind, kindStr) => { Actions tests/src/Actions.res 10:9 Hit _ternary +Formatted: type kind = First | Second | Third let _ = (kind, kindStr) => { From 2bf936d08bf6a0956f8998f19c0a437a19755d6f Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 25 Mar 2022 09:42:12 +0100 Subject: [PATCH 3/8] Variants and records. --- analysis/src/Actions.ml | 31 ++++++++++++++++++--- analysis/tests/src/Actions.res | 3 +- analysis/tests/src/expected/Actions.res.txt | 10 ++++--- 3 files changed, 35 insertions(+), 9 deletions(-) diff --git a/analysis/src/Actions.ml b/analysis/src/Actions.ml index a5a147207..9c75932a2 100644 --- a/analysis/src/Actions.ml +++ b/analysis/src/Actions.ml @@ -2,18 +2,41 @@ let posInLoc ~pos ~loc = Utils.tupleOfLexing loc.Location.loc_start <= pos && pos < Utils.tupleOfLexing loc.loc_end +let rec listToPat ~itemToPat = function + | [] -> Some [] + | x :: xList -> ( + match (itemToPat x, listToPat ~itemToPat xList) with + | Some p, Some pList -> Some (p :: pList) + | _ -> None) + let rec expToPat (exp : Parsetree.expression) = let mkPat ppat_desc = Ast_helper.Pat.mk ~loc:exp.pexp_loc ~attrs:exp.pexp_attributes ppat_desc in match exp.pexp_desc with - | Pexp_construct (lid, None) -> - Some (mkPat (Parsetree.Ppat_construct (lid, None))) + | Pexp_construct (lid, None) -> Some (mkPat (Ppat_construct (lid, None))) | Pexp_construct (lid, Some e1) -> ( match expToPat e1 with | None -> None - | Some p1 -> Some (mkPat (Parsetree.Ppat_construct (lid, Some p1)))) - | Pexp_constant c -> Some (mkPat (Parsetree.Ppat_constant c)) + | Some p1 -> Some (mkPat (Ppat_construct (lid, Some p1)))) + | Pexp_variant (label, None) -> Some (mkPat (Ppat_variant (label, None))) + | Pexp_variant (label, Some e1) -> ( + match expToPat e1 with + | None -> None + | Some p1 -> Some (mkPat (Ppat_variant (label, Some p1)))) + | Pexp_constant c -> Some (mkPat (Ppat_constant c)) + | Pexp_tuple eList -> ( + match listToPat ~itemToPat:expToPat eList with + | None -> None + | Some patList -> Some (mkPat (Ppat_tuple patList))) + | Pexp_record (items, None) -> ( + let itemToPat (x, e) = + match expToPat e with None -> None | Some p -> Some (x, p) + in + match listToPat ~itemToPat items with + | None -> None + | Some patItems -> Some (mkPat (Ppat_record (patItems, Closed)))) + | Pexp_record (_, Some _) -> None | _ -> None let mkMapper ~pos ~changed = diff --git a/analysis/tests/src/Actions.res b/analysis/tests/src/Actions.res index 4f1228867..58e29a8a1 100644 --- a/analysis/tests/src/Actions.res +++ b/analysis/tests/src/Actions.res @@ -1,4 +1,5 @@ type kind = First | Second | Third +type r = {name: string, age: int} let _ = (kind, kindStr) => { let _ifThenElse = if kind == First { @@ -8,6 +9,6 @@ let _ = (kind, kindStr) => { "Not First" } - let _ternary = "First" != kindStr ? "Not First" : "First" + let _ternary = #kind("First", {name: "abc", age: 3}) != kindStr ? "Not First" : "First" // ^act } diff --git a/analysis/tests/src/expected/Actions.res.txt b/analysis/tests/src/expected/Actions.res.txt index 288ad10d1..8b6763f3f 100644 --- a/analysis/tests/src/expected/Actions.res.txt +++ b/analysis/tests/src/expected/Actions.res.txt @@ -1,7 +1,8 @@ -Actions tests/src/Actions.res 3:10 +Actions tests/src/Actions.res 4:10 Hit _ifThenElse Formatted: type kind = First | Second | Third +type r = {name: string, age: int} let _ = (kind, kindStr) => { let _ifThenElse = switch kind { @@ -10,14 +11,15 @@ let _ = (kind, kindStr) => { | _ => "Not First" } - let _ternary = "First" != kindStr ? "Not First" : "First" + let _ternary = #kind("First", {name: "abc", age: 3}) != kindStr ? "Not First" : "First" // ^act } -Actions tests/src/Actions.res 10:9 +Actions tests/src/Actions.res 11:9 Hit _ternary Formatted: type kind = First | Second | Third +type r = {name: string, age: int} let _ = (kind, kindStr) => { let _ifThenElse = if kind == First { @@ -28,7 +30,7 @@ let _ = (kind, kindStr) => { } let _ternary = switch kindStr { - | "First" => "First" + | #kind("First", {name: "abc", age: 3}) => "First" | _ => "Not First" } // ^act From 8eb12ea0c975f0b5abd968fb133e6f7ed3062d43 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 25 Mar 2022 11:22:49 +0100 Subject: [PATCH 4/8] Trigger on the body of if-then-else. --- analysis/src/Actions.ml | 76 +++++++++------------ analysis/tests/src/Actions.res | 4 +- analysis/tests/src/expected/Actions.res.txt | 14 ++-- 3 files changed, 42 insertions(+), 52 deletions(-) diff --git a/analysis/src/Actions.ml b/analysis/src/Actions.ml index 9c75932a2..1bb3f73de 100644 --- a/analysis/src/Actions.ml +++ b/analysis/src/Actions.ml @@ -40,59 +40,51 @@ let rec expToPat (exp : Parsetree.expression) = | _ -> None let mkMapper ~pos ~changed = - let value_binding (mapper : Ast_mapper.mapper) (vb : Parsetree.value_binding) - = + let expr (mapper : Ast_mapper.mapper) (e : Parsetree.expression) = let newExp = - match vb.pvb_pat.ppat_desc with - | Ppat_var {txt; loc} when posInLoc ~pos ~loc -> ( - match vb.pvb_expr.pexp_desc with - | Pexp_ifthenelse - ( { - pexp_desc = - Pexp_apply - ( { - pexp_desc = - Pexp_ident {txt = Lident (("=" | "<>") as op)}; - }, - [(Nolabel, arg1); (Nolabel, arg2)] ); - }, - e1, - Some e2 ) -> ( - let e1, e2 = if op = "=" then (e1, e2) else (e2, e1) in - let mkMatch ~arg ~pat = - let cases = - [ - Ast_helper.Exp.case pat e1; - Ast_helper.Exp.case (Ast_helper.Pat.any ()) e2; - ] - in - Ast_helper.Exp.match_ ~loc:vb.pvb_expr.pexp_loc - ~attrs:vb.pvb_expr.pexp_attributes arg cases + match e.pexp_desc with + | Pexp_ifthenelse + ( { + pexp_desc = + Pexp_apply + ( {pexp_desc = Pexp_ident {txt = Lident (("=" | "<>") as op)}}, + [(Nolabel, arg1); (Nolabel, arg2)] ); + }, + e1, + Some e2 ) + when posInLoc ~pos ~loc:e.pexp_loc -> ( + let e1, e2 = if op = "=" then (e1, e2) else (e2, e1) in + let mkMatch ~arg ~pat = + let cases = + [ + Ast_helper.Exp.case pat e1; + Ast_helper.Exp.case (Ast_helper.Pat.any ()) e2; + ] in + Ast_helper.Exp.match_ ~loc:e.pexp_loc ~attrs:e.pexp_attributes arg + cases + in - match expToPat arg2 with - | None -> ( - match expToPat arg1 with - | None -> None - | Some pat1 -> - let newExp = mkMatch ~arg:arg2 ~pat:pat1 in - Printf.printf "Hit %s\n" txt; - Some newExp) - | Some pat2 -> - let newExp = mkMatch ~arg:arg1 ~pat:pat2 in - Printf.printf "Hit %s\n" txt; + match expToPat arg2 with + | None -> ( + match expToPat arg1 with + | None -> None + | Some pat1 -> + let newExp = mkMatch ~arg:arg2 ~pat:pat1 in Some newExp) - | _ -> None) + | Some pat2 -> + let newExp = mkMatch ~arg:arg1 ~pat:pat2 in + Some newExp) | _ -> None in match newExp with | Some newExp -> changed := true; - {vb with pvb_expr = newExp} - | None -> Ast_mapper.default_mapper.value_binding mapper vb + newExp + | None -> Ast_mapper.default_mapper.expr mapper e in - {Ast_mapper.default_mapper with value_binding} + {Ast_mapper.default_mapper with expr} let command ~path ~pos = if Filename.check_suffix path ".res" then diff --git a/analysis/tests/src/Actions.res b/analysis/tests/src/Actions.res index 58e29a8a1..b1ac5cf2a 100644 --- a/analysis/tests/src/Actions.res +++ b/analysis/tests/src/Actions.res @@ -3,12 +3,12 @@ type r = {name: string, age: int} let _ = (kind, kindStr) => { let _ifThenElse = if kind == First { - // ^act + // ^act "First" } else { "Not First" } let _ternary = #kind("First", {name: "abc", age: 3}) != kindStr ? "Not First" : "First" - // ^act + // ^act } diff --git a/analysis/tests/src/expected/Actions.res.txt b/analysis/tests/src/expected/Actions.res.txt index 8b6763f3f..a3dcd3881 100644 --- a/analysis/tests/src/expected/Actions.res.txt +++ b/analysis/tests/src/expected/Actions.res.txt @@ -1,29 +1,27 @@ -Actions tests/src/Actions.res 4:10 -Hit _ifThenElse +Actions tests/src/Actions.res 4:20 Formatted: type kind = First | Second | Third type r = {name: string, age: int} let _ = (kind, kindStr) => { let _ifThenElse = switch kind { - | First => // ^act + | First => // ^act "First" | _ => "Not First" } let _ternary = #kind("First", {name: "abc", age: 3}) != kindStr ? "Not First" : "First" - // ^act + // ^act } -Actions tests/src/Actions.res 11:9 -Hit _ternary +Actions tests/src/Actions.res 11:17 Formatted: type kind = First | Second | Third type r = {name: string, age: int} let _ = (kind, kindStr) => { let _ifThenElse = if kind == First { - // ^act + // ^act "First" } else { "Not First" @@ -33,6 +31,6 @@ let _ = (kind, kindStr) => { | #kind("First", {name: "abc", age: 3}) => "First" | _ => "Not First" } - // ^act + // ^act } From bb3e63e84684636b38aada8e1c6bf9b28c8ecadc Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Fri, 25 Mar 2022 11:28:18 +0100 Subject: [PATCH 5/8] tweak test --- analysis/tests/src/Actions.res | 19 ++++---- analysis/tests/src/expected/Actions.res.txt | 49 +++++++++++---------- 2 files changed, 36 insertions(+), 32 deletions(-) diff --git a/analysis/tests/src/Actions.res b/analysis/tests/src/Actions.res index b1ac5cf2a..4eddc4018 100644 --- a/analysis/tests/src/Actions.res +++ b/analysis/tests/src/Actions.res @@ -1,14 +1,15 @@ type kind = First | Second | Third type r = {name: string, age: int} -let _ = (kind, kindStr) => { - let _ifThenElse = if kind == First { - // ^act - "First" - } else { - "Not First" - } +let ret = _ => assert false +let kind = assert false - let _ternary = #kind("First", {name: "abc", age: 3}) != kindStr ? "Not First" : "First" - // ^act +if kind == First { + // ^act + ret("First") +} else { + ret("Not First") } + +#kind("First", {name: "abc", age: 3}) != kind ? ret("Not First") : ret("First") +// ^act diff --git a/analysis/tests/src/expected/Actions.res.txt b/analysis/tests/src/expected/Actions.res.txt index a3dcd3881..2800a7c68 100644 --- a/analysis/tests/src/expected/Actions.res.txt +++ b/analysis/tests/src/expected/Actions.res.txt @@ -1,36 +1,39 @@ -Actions tests/src/Actions.res 4:20 +Actions tests/src/Actions.res 6:5 Formatted: type kind = First | Second | Third type r = {name: string, age: int} -let _ = (kind, kindStr) => { - let _ifThenElse = switch kind { - | First => // ^act - "First" - | _ => "Not First" - } +let ret = _ => assert false +let kind = assert false - let _ternary = #kind("First", {name: "abc", age: 3}) != kindStr ? "Not First" : "First" - // ^act +switch kind { +| First => + // ^act + ret("First") +| _ => ret("Not First") } -Actions tests/src/Actions.res 11:17 +#kind("First", {name: "abc", age: 3}) != kind ? ret("Not First") : ret("First") +// ^act + +Actions tests/src/Actions.res 13:15 Formatted: type kind = First | Second | Third type r = {name: string, age: int} -let _ = (kind, kindStr) => { - let _ifThenElse = if kind == First { - // ^act - "First" - } else { - "Not First" - } - - let _ternary = switch kindStr { - | #kind("First", {name: "abc", age: 3}) => "First" - | _ => "Not First" - } - // ^act +let ret = _ => assert false +let kind = assert false + +if kind == First { + // ^act + ret("First") +} else { + ret("Not First") +} + +switch kind { +| #kind("First", {name: "abc", age: 3}) => ret("First") +// ^act +| _ => ret("Not First") } From f0c1815cbc17744cc41906e0c5e6f0faf3be3671 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Sat, 26 Mar 2022 10:45:52 +0100 Subject: [PATCH 6/8] Rename/cleanup. --- analysis/src/Actions.ml | 103 --------------- analysis/src/Commands.ml | 6 +- analysis/src/Xform.ml | 117 ++++++++++++++++++ analysis/tests/src/{Actions.res => Xform.res} | 4 +- analysis/tests/src/expected/Debug.res.txt | 4 +- .../{Actions.res.txt => Xform.res.txt} | 16 +-- 6 files changed, 132 insertions(+), 118 deletions(-) delete mode 100644 analysis/src/Actions.ml create mode 100644 analysis/src/Xform.ml rename analysis/tests/src/{Actions.res => Xform.res} (89%) rename analysis/tests/src/expected/{Actions.res.txt => Xform.res.txt} (75%) diff --git a/analysis/src/Actions.ml b/analysis/src/Actions.ml deleted file mode 100644 index 1bb3f73de..000000000 --- a/analysis/src/Actions.ml +++ /dev/null @@ -1,103 +0,0 @@ -let posInLoc ~pos ~loc = - Utils.tupleOfLexing loc.Location.loc_start <= pos - && pos < Utils.tupleOfLexing loc.loc_end - -let rec listToPat ~itemToPat = function - | [] -> Some [] - | x :: xList -> ( - match (itemToPat x, listToPat ~itemToPat xList) with - | Some p, Some pList -> Some (p :: pList) - | _ -> None) - -let rec expToPat (exp : Parsetree.expression) = - let mkPat ppat_desc = - Ast_helper.Pat.mk ~loc:exp.pexp_loc ~attrs:exp.pexp_attributes ppat_desc - in - match exp.pexp_desc with - | Pexp_construct (lid, None) -> Some (mkPat (Ppat_construct (lid, None))) - | Pexp_construct (lid, Some e1) -> ( - match expToPat e1 with - | None -> None - | Some p1 -> Some (mkPat (Ppat_construct (lid, Some p1)))) - | Pexp_variant (label, None) -> Some (mkPat (Ppat_variant (label, None))) - | Pexp_variant (label, Some e1) -> ( - match expToPat e1 with - | None -> None - | Some p1 -> Some (mkPat (Ppat_variant (label, Some p1)))) - | Pexp_constant c -> Some (mkPat (Ppat_constant c)) - | Pexp_tuple eList -> ( - match listToPat ~itemToPat:expToPat eList with - | None -> None - | Some patList -> Some (mkPat (Ppat_tuple patList))) - | Pexp_record (items, None) -> ( - let itemToPat (x, e) = - match expToPat e with None -> None | Some p -> Some (x, p) - in - match listToPat ~itemToPat items with - | None -> None - | Some patItems -> Some (mkPat (Ppat_record (patItems, Closed)))) - | Pexp_record (_, Some _) -> None - | _ -> None - -let mkMapper ~pos ~changed = - let expr (mapper : Ast_mapper.mapper) (e : Parsetree.expression) = - let newExp = - match e.pexp_desc with - | Pexp_ifthenelse - ( { - pexp_desc = - Pexp_apply - ( {pexp_desc = Pexp_ident {txt = Lident (("=" | "<>") as op)}}, - [(Nolabel, arg1); (Nolabel, arg2)] ); - }, - e1, - Some e2 ) - when posInLoc ~pos ~loc:e.pexp_loc -> ( - let e1, e2 = if op = "=" then (e1, e2) else (e2, e1) in - let mkMatch ~arg ~pat = - let cases = - [ - Ast_helper.Exp.case pat e1; - Ast_helper.Exp.case (Ast_helper.Pat.any ()) e2; - ] - in - Ast_helper.Exp.match_ ~loc:e.pexp_loc ~attrs:e.pexp_attributes arg - cases - in - - match expToPat arg2 with - | None -> ( - match expToPat arg1 with - | None -> None - | Some pat1 -> - let newExp = mkMatch ~arg:arg2 ~pat:pat1 in - Some newExp) - | Some pat2 -> - let newExp = mkMatch ~arg:arg1 ~pat:pat2 in - Some newExp) - | _ -> None - in - match newExp with - | Some newExp -> - changed := true; - newExp - | None -> Ast_mapper.default_mapper.expr mapper e - in - - {Ast_mapper.default_mapper with expr} - -let command ~path ~pos = - if Filename.check_suffix path ".res" then - let parser = - Res_driver.parsingEngine.parseImplementation ~forPrinter:false - in - let {Res_driver.parsetree = structure; comments} = parser ~filename:path in - let printer = - Res_printer.printImplementation ~width:!Res_cli.ResClflags.width ~comments - in - let changed = ref false in - let mapper = mkMapper ~pos ~changed in - let newStructure = mapper.structure mapper structure in - if !changed then - let formatted = printer newStructure in - Printf.printf "Formatted:\n%s" formatted diff --git a/analysis/src/Commands.ml b/analysis/src/Commands.ml index 1586f0fc5..dd92ec557 100644 --- a/analysis/src/Commands.ml +++ b/analysis/src/Commands.ml @@ -400,11 +400,11 @@ let test ~path = SemanticTokens.command ~debug:true ~emitter:(SemanticTokens.Token.createEmitter ()) ~path - | "act" -> + | "xfm" -> print_endline - ("Actions " ^ path ^ " " ^ string_of_int line ^ ":" + ("Xform " ^ path ^ " " ^ string_of_int line ^ ":" ^ string_of_int col); - Actions.command ~path ~pos:(line, col) + Xform.command ~path ~pos:(line, col) | _ -> ()); print_newline ()) in diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml new file mode 100644 index 000000000..99a0d0420 --- /dev/null +++ b/analysis/src/Xform.ml @@ -0,0 +1,117 @@ +(** Code transformations using the parser/printer and ast operations *) + +let posInLoc ~pos ~loc = + Utils.tupleOfLexing loc.Location.loc_start <= pos + && pos < Utils.tupleOfLexing loc.loc_end + +module IfThenElse = struct + (* Convert if-then-else to switch *) + + let rec listToPat ~itemToPat = function + | [] -> Some [] + | x :: xList -> ( + match (itemToPat x, listToPat ~itemToPat xList) with + | Some p, Some pList -> Some (p :: pList) + | _ -> None) + + let rec expToPat (exp : Parsetree.expression) = + let mkPat ppat_desc = + Ast_helper.Pat.mk ~loc:exp.pexp_loc ~attrs:exp.pexp_attributes ppat_desc + in + match exp.pexp_desc with + | Pexp_construct (lid, None) -> Some (mkPat (Ppat_construct (lid, None))) + | Pexp_construct (lid, Some e1) -> ( + match expToPat e1 with + | None -> None + | Some p1 -> Some (mkPat (Ppat_construct (lid, Some p1)))) + | Pexp_variant (label, None) -> Some (mkPat (Ppat_variant (label, None))) + | Pexp_variant (label, Some e1) -> ( + match expToPat e1 with + | None -> None + | Some p1 -> Some (mkPat (Ppat_variant (label, Some p1)))) + | Pexp_constant c -> Some (mkPat (Ppat_constant c)) + | Pexp_tuple eList -> ( + match listToPat ~itemToPat:expToPat eList with + | None -> None + | Some patList -> Some (mkPat (Ppat_tuple patList))) + | Pexp_record (items, None) -> ( + let itemToPat (x, e) = + match expToPat e with None -> None | Some p -> Some (x, p) + in + match listToPat ~itemToPat items with + | None -> None + | Some patItems -> Some (mkPat (Ppat_record (patItems, Closed)))) + | Pexp_record (_, Some _) -> None + | _ -> None + + let mkMapper ~pos ~changed = + let expr (mapper : Ast_mapper.mapper) (e : Parsetree.expression) = + let newExp = + match e.pexp_desc with + | Pexp_ifthenelse + ( { + pexp_desc = + Pexp_apply + ( { + pexp_desc = + Pexp_ident {txt = Lident (("=" | "<>") as op)}; + }, + [(Nolabel, arg1); (Nolabel, arg2)] ); + }, + e1, + Some e2 ) + when posInLoc ~pos ~loc:e.pexp_loc -> ( + let e1, e2 = if op = "=" then (e1, e2) else (e2, e1) in + let mkMatch ~arg ~pat = + let cases = + [ + Ast_helper.Exp.case pat e1; + Ast_helper.Exp.case (Ast_helper.Pat.any ()) e2; + ] + in + Ast_helper.Exp.match_ ~loc:e.pexp_loc ~attrs:e.pexp_attributes arg + cases + in + + match expToPat arg2 with + | None -> ( + match expToPat arg1 with + | None -> None + | Some pat1 -> + let newExp = mkMatch ~arg:arg2 ~pat:pat1 in + Some newExp) + | Some pat2 -> + let newExp = mkMatch ~arg:arg1 ~pat:pat2 in + Some newExp) + | _ -> None + in + match newExp with + | Some newExp -> + changed := true; + newExp + | None -> Ast_mapper.default_mapper.expr mapper e + in + + {Ast_mapper.default_mapper with expr} + + let xform ~pos structure = + let changed = ref false in + let mapper = mkMapper ~pos ~changed in + let newStructure = mapper.structure mapper structure in + if !changed then Some newStructure else None +end + +let command ~path ~pos = + if Filename.check_suffix path ".res" then + let parser = + Res_driver.parsingEngine.parseImplementation ~forPrinter:false + in + let {Res_driver.parsetree = structure; comments} = parser ~filename:path in + let printer = + Res_printer.printImplementation ~width:!Res_cli.ResClflags.width ~comments + in + match IfThenElse.xform ~pos structure with + | None -> () + | Some newStructure -> + let formatted = printer newStructure in + Printf.printf "Hit IfThenElse. Formatted:\n%s" formatted diff --git a/analysis/tests/src/Actions.res b/analysis/tests/src/Xform.res similarity index 89% rename from analysis/tests/src/Actions.res rename to analysis/tests/src/Xform.res index 4eddc4018..4229ea8f4 100644 --- a/analysis/tests/src/Actions.res +++ b/analysis/tests/src/Xform.res @@ -5,11 +5,11 @@ let ret = _ => assert false let kind = assert false if kind == First { - // ^act + // ^xfm ret("First") } else { ret("Not First") } #kind("First", {name: "abc", age: 3}) != kind ? ret("Not First") : ret("First") -// ^act +// ^xfm diff --git a/analysis/tests/src/expected/Debug.res.txt b/analysis/tests/src/expected/Debug.res.txt index f1087de8f..0cf048a86 100644 --- a/analysis/tests/src/expected/Debug.res.txt +++ b/analysis/tests/src/expected/Debug.res.txt @@ -4,8 +4,7 @@ Dependencies: @rescript/react Source directories: tests/node_modules/@rescript/react/src tests/node_modules/@rescript/react/src/legacy Source files: tests/node_modules/@rescript/react/src/React.res tests/node_modules/@rescript/react/src/ReactDOM.res tests/node_modules/@rescript/react/src/ReactDOMServer.res tests/node_modules/@rescript/react/src/ReactDOMStyle.res tests/node_modules/@rescript/react/src/ReactEvent.res tests/node_modules/@rescript/react/src/ReactEvent.resi tests/node_modules/@rescript/react/src/ReactTestUtils.res tests/node_modules/@rescript/react/src/ReactTestUtils.resi tests/node_modules/@rescript/react/src/RescriptReactErrorBoundary.res tests/node_modules/@rescript/react/src/RescriptReactErrorBoundary.resi tests/node_modules/@rescript/react/src/RescriptReactRouter.res tests/node_modules/@rescript/react/src/RescriptReactRouter.resi tests/node_modules/@rescript/react/src/legacy/ReactDOMRe.res tests/node_modules/@rescript/react/src/legacy/ReasonReact.res Source directories: tests/src tests/src/expected -Source files: tests/src/Actions.res tests/src/Auto.res tests/src/CompletePrioritize1.res tests/src/CompletePrioritize2.res tests/src/Completion.res tests/src/Component.res tests/src/Component.resi tests/src/Cross.res tests/src/Debug.res tests/src/Definition.res tests/src/DefinitionWithInterface.res tests/src/DefinitionWithInterface.resi tests/src/Div.res tests/src/Fragment.res tests/src/Highlight.res tests/src/Hover.res tests/src/Jsx.res tests/src/Jsx.resi tests/src/LongIdentTest.res tests/src/Obj.res tests/src/Patterns.res tests/src/RecModules.res tests/src/RecordCompletion.res tests/src/References.res tests/src/ReferencesWithInterface.res tests/src/ReferencesWithInterface.resi tests/src/Rename.res tests/src/RenameWithInterface.res tests/src/RenameWithInterface.resi tests/src/TableclothMap.ml tests/src/TableclothMap.mli tests/src/TypeDefinition.res -Impl cmt:tests/lib/bs/src/Actions.cmt res:tests/src/Actions.res +Source files: tests/src/Auto.res tests/src/CompletePrioritize1.res tests/src/CompletePrioritize2.res tests/src/Completion.res tests/src/Component.res tests/src/Component.resi tests/src/Cross.res tests/src/Debug.res tests/src/Definition.res tests/src/DefinitionWithInterface.res tests/src/DefinitionWithInterface.resi tests/src/Div.res tests/src/Fragment.res tests/src/Highlight.res tests/src/Hover.res tests/src/Jsx.res tests/src/Jsx.resi tests/src/LongIdentTest.res tests/src/Obj.res tests/src/Patterns.res tests/src/RecModules.res tests/src/RecordCompletion.res tests/src/References.res tests/src/ReferencesWithInterface.res tests/src/ReferencesWithInterface.resi tests/src/Rename.res tests/src/RenameWithInterface.res tests/src/RenameWithInterface.resi tests/src/TableclothMap.ml tests/src/TableclothMap.mli tests/src/TypeDefinition.res tests/src/Xform.res Impl cmt:tests/lib/bs/src/Auto.cmt res:tests/src/Auto.res Impl cmt:tests/lib/bs/src/CompletePrioritize1.cmt res:tests/src/CompletePrioritize1.res Impl cmt:tests/lib/bs/src/CompletePrioritize2.cmt res:tests/src/CompletePrioritize2.res @@ -31,6 +30,7 @@ Impl cmt:tests/lib/bs/src/Rename.cmt res:tests/src/Rename.res IntfAndImpl cmti:tests/lib/bs/src/RenameWithInterface.cmti resi:tests/src/RenameWithInterface.resi cmt:tests/lib/bs/src/RenameWithInterface.cmt res:tests/src/RenameWithInterface.res IntfAndImpl cmti:tests/lib/bs/src/TableclothMap.cmti resi:tests/src/TableclothMap.mli cmt:tests/lib/bs/src/TableclothMap.cmt res:tests/src/TableclothMap.ml Impl cmt:tests/lib/bs/src/TypeDefinition.cmt res:tests/src/TypeDefinition.res +Impl cmt:tests/lib/bs/src/Xform.cmt res:tests/src/Xform.res Dependency dirs: tests/node_modules/@rescript/react/lib/bs/src tests/node_modules/@rescript/react/lib/bs/src/legacy Opens from bsconfig: locItems: diff --git a/analysis/tests/src/expected/Actions.res.txt b/analysis/tests/src/expected/Xform.res.txt similarity index 75% rename from analysis/tests/src/expected/Actions.res.txt rename to analysis/tests/src/expected/Xform.res.txt index 2800a7c68..1dd67d1c6 100644 --- a/analysis/tests/src/expected/Actions.res.txt +++ b/analysis/tests/src/expected/Xform.res.txt @@ -1,5 +1,5 @@ -Actions tests/src/Actions.res 6:5 -Formatted: +Xform tests/src/Xform.res 6:5 +Hit IfThenElse. Formatted: type kind = First | Second | Third type r = {name: string, age: int} @@ -8,16 +8,16 @@ let kind = assert false switch kind { | First => - // ^act + // ^xfm ret("First") | _ => ret("Not First") } #kind("First", {name: "abc", age: 3}) != kind ? ret("Not First") : ret("First") -// ^act +// ^xfm -Actions tests/src/Actions.res 13:15 -Formatted: +Xform tests/src/Xform.res 13:15 +Hit IfThenElse. Formatted: type kind = First | Second | Third type r = {name: string, age: int} @@ -25,7 +25,7 @@ let ret = _ => assert false let kind = assert false if kind == First { - // ^act + // ^xfm ret("First") } else { ret("Not First") @@ -33,7 +33,7 @@ if kind == First { switch kind { | #kind("First", {name: "abc", age: 3}) => ret("First") -// ^act +// ^xfm | _ => ret("Not First") } From 560591e4f9ace551abe873ef0755afb1134dba6d Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Sat, 26 Mar 2022 10:56:39 +0100 Subject: [PATCH 7/8] refactor --- analysis/src/Xform.ml | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 99a0d0420..1d0d58dca 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -101,17 +101,21 @@ module IfThenElse = struct if !changed then Some newStructure else None end +let parse ~filename = + let {Res_driver.parsetree = structure; comments} = + Res_driver.parsingEngine.parseImplementation ~forPrinter:false ~filename + in + let print ~structure = + Res_printer.printImplementation ~width:!Res_cli.ResClflags.width ~comments + structure + in + (structure, print) + let command ~path ~pos = if Filename.check_suffix path ".res" then - let parser = - Res_driver.parsingEngine.parseImplementation ~forPrinter:false - in - let {Res_driver.parsetree = structure; comments} = parser ~filename:path in - let printer = - Res_printer.printImplementation ~width:!Res_cli.ResClflags.width ~comments - in + let structure, print = parse ~filename:path in match IfThenElse.xform ~pos structure with | None -> () | Some newStructure -> - let formatted = printer newStructure in + let formatted = print newStructure in Printf.printf "Hit IfThenElse. Formatted:\n%s" formatted From 3c70f524abda926402a73975ace48008934ad6b4 Mon Sep 17 00:00:00 2001 From: Cristiano Calcagno Date: Sat, 26 Mar 2022 11:28:38 +0100 Subject: [PATCH 8/8] Use simple diff to output xform result. --- analysis/src/Xform.ml | 31 ++++++++++++++++++++++- analysis/tests/src/expected/Xform.res.txt | 27 ++------------------ 2 files changed, 32 insertions(+), 26 deletions(-) diff --git a/analysis/src/Xform.ml b/analysis/src/Xform.ml index 1d0d58dca..5a149e3b7 100644 --- a/analysis/src/Xform.ml +++ b/analysis/src/Xform.ml @@ -111,6 +111,29 @@ let parse ~filename = in (structure, print) +let diff ~filename ~newContents = + match Files.readFile ~filename with + | None -> assert false + | Some oldContents -> + let rec findFirstLineDifferent n old new_ = + match (old, new_) with + | old1 :: oldRest, new1 :: newRest -> + if old1 = new1 then findFirstLineDifferent (n + 1) oldRest newRest + else (n, old, new_) + | _ -> (n, old, new_) + in + let oldLines = String.split_on_char '\n' oldContents in + let newLines = String.split_on_char '\n' newContents in + let firstLineDifferent, old, new_ = + findFirstLineDifferent 0 oldLines newLines + in + let firstLineR, _oldR, newR = + findFirstLineDifferent 0 (List.rev old) (List.rev new_) + in + let lastLineEqual = firstLineDifferent + List.length old - firstLineR in + let newLines = List.rev newR in + (firstLineDifferent, lastLineEqual, newLines) + let command ~path ~pos = if Filename.check_suffix path ".res" then let structure, print = parse ~filename:path in @@ -118,4 +141,10 @@ let command ~path ~pos = | None -> () | Some newStructure -> let formatted = print newStructure in - Printf.printf "Hit IfThenElse. Formatted:\n%s" formatted + let firstLineDifferent, lastLineEqual, newLines = + diff ~filename:path ~newContents:formatted + in + Printf.printf + "Hit IfThenElse firstLineDifferent:%d lastLineEqual:%d newLines:\n%s\n" + firstLineDifferent lastLineEqual + (newLines |> String.concat "\n") diff --git a/analysis/tests/src/expected/Xform.res.txt b/analysis/tests/src/expected/Xform.res.txt index 1dd67d1c6..4c3378e6b 100644 --- a/analysis/tests/src/expected/Xform.res.txt +++ b/analysis/tests/src/expected/Xform.res.txt @@ -1,36 +1,13 @@ Xform tests/src/Xform.res 6:5 -Hit IfThenElse. Formatted: -type kind = First | Second | Third -type r = {name: string, age: int} - -let ret = _ => assert false -let kind = assert false - +Hit IfThenElse firstLineDifferent:6 lastLineEqual:11 newLines: switch kind { | First => // ^xfm ret("First") | _ => ret("Not First") -} - -#kind("First", {name: "abc", age: 3}) != kind ? ret("Not First") : ret("First") -// ^xfm Xform tests/src/Xform.res 13:15 -Hit IfThenElse. Formatted: -type kind = First | Second | Third -type r = {name: string, age: int} - -let ret = _ => assert false -let kind = assert false - -if kind == First { - // ^xfm - ret("First") -} else { - ret("Not First") -} - +Hit IfThenElse firstLineDifferent:13 lastLineEqual:15 newLines: switch kind { | #kind("First", {name: "abc", age: 3}) => ret("First") // ^xfm