Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@

- Rewatch: enable `--create-sourcedirs` by default (now deprecated when explicitly used). https://github.com/rescript-lang/rescript/pull/8092
- Rewatch: check if filename case for interface and implementation matches. https://github.com/rescript-lang/rescript/pull/8144
- Migration tool: Do not rewrite or format files unless there are actual migrations performed. https://github.com/rescript-lang/rescript/pull/8157
- Migration tool: Do not attempt to run migrations on dependencies. https://github.com/rescript-lang/rescript/pull/8157

#### :house: Internal

Expand Down
11 changes: 11 additions & 0 deletions tests/dependencies/deprecatedlib/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"name": "deprecatedlib",
"version": "0.0.1",
"scripts": {
"build": "rescript-legacy build",
"clean": "rescript clean"
},
"dependencies": {
"rescript": "workspace:^"
}
}
6 changes: 6 additions & 0 deletions tests/dependencies/deprecatedlib/rescript.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"name": "deprecatedlib",
"sources": { "dir": "src", "subdirs": true },
"package-specs": { "module": "commonjs", "in-source": true },
"suffix": ".res.js"
}
7 changes: 7 additions & 0 deletions tests/dependencies/deprecatedlib/src/DeprecatedDep.res
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
@deprecated({
reason: "Use `newThing` instead.",
migrate: DeprecatedDep.newThing(),
})
let oldThing = () => 1

let newThing = () => 2
1 change: 1 addition & 0 deletions tests/tools_tests/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
},
"dependencies": {
"@rescript/react": "link:../dependencies/rescript-react",
"deprecatedlib": "link:../dependencies/deprecatedlib",
"rescript": "workspace:^"
}
}
2 changes: 1 addition & 1 deletion tests/tools_tests/rescript.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,5 @@
"in-source": true
},
"suffix": ".res.js",
"dependencies": ["@rescript/react"]
"dependencies": ["@rescript/react", "deprecatedlib"]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let value = DeprecatedDep.oldThing()

2 changes: 2 additions & 0 deletions tests/tools_tests/src/expected/NoOp.res.expected
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let meaningOfLife = 42

1 change: 1 addition & 0 deletions tests/tools_tests/src/migrate/DependencyDeprecation.res
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let value = DeprecatedDep.oldThing()
1 change: 1 addition & 0 deletions tests/tools_tests/src/migrate/NoOp.res
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
let meaningOfLife = 42
3 changes: 2 additions & 1 deletion tests/tools_tests/test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ done
# Test migrate command
for file in src/migrate/*.{res,resi}; do
output="src/expected/$(basename $file).expected"
../../_build/install/default/bin/rescript-tools migrate "$file" --stdout > $output
# Capture stderr too so warnings would surface in expected output if they occur.
../../_build/install/default/bin/rescript-tools migrate "$file" --stdout 2>&1 > $output
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3 Badge Capture stderr correctly in migrate test

The redirection order 2>&1 > $output redirects stderr to the original stdout before stdout is sent to the file, so stderr still goes to the terminal and will not appear in the expected output. This contradicts the comment and means warnings won’t be captured by the test; use > $output 2>&1 (or an equivalent pipe) to combine the streams.

Useful? React with 👍 / 👎.

if [ "$RUNNER_OS" == "Windows" ]; then
perl -pi -e 's/\r\n/\n/g' -- $output
fi
Expand Down
40 changes: 23 additions & 17 deletions tools/bin/main.ml
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,14 @@ let main () =
| "migrate" :: file :: opts -> (
let isStdout = List.mem "--stdout" opts in
let outputMode = if isStdout then `Stdout else `File in
match
(Tools.Migrate.migrate ~entryPointFile:file ~outputMode, outputMode)
with
| Ok content, `Stdout -> print_endline content
| result, `File -> logAndExit result
| Error e, _ -> logAndExit (Error e))
let base = Filename.basename file in
match Tools.Migrate.migrate ~outputMode file with
| Ok (`Changed content | `Unchanged content) when isStdout ->
print_endline content
| Ok (`Changed _) -> logAndExit (Ok (base ^ ": File migrated successfully"))
| Ok (`Unchanged _) ->
logAndExit (Ok (base ^ ": File did not need migration"))
| Error e -> logAndExit (Error e))
| "migrate-all" :: root :: _opts -> (
let rootPath =
if Filename.is_relative root then Unix.realpath root else root
Expand Down Expand Up @@ -106,24 +108,28 @@ let main () =
let total = List.length files in
if total = 0 then logAndExit (Ok "No source files found to migrate")
else
let canonical p = try Unix.realpath p with _ -> p in
let dependency_paths =
Analysis.SharedTypes.FileSet.fold
(fun p acc ->
acc
|> Analysis.SharedTypes.FileSet.add p
|> Analysis.SharedTypes.FileSet.add (canonical p))
package.dependenciesFiles Analysis.SharedTypes.FileSet.empty
in
let process_one file =
(file, Tools.Migrate.migrate ~entryPointFile:file ~outputMode:`File)
( file,
Tools.Migrate.migrate ~package ~dependency_paths ~outputMode:`File
file )
in
let results = List.map process_one files in
let migrated, unchanged, failures =
results
|> List.fold_left
(fun (migrated, unchanged, failures) (file, res) ->
(fun (migrated, unchanged, failures) (_file, res) ->
match res with
| Ok msg ->
let base = Filename.basename file in
if msg = base ^ ": File migrated successfully" then
(migrated + 1, unchanged, failures)
else if msg = base ^ ": File did not need migration" then
(migrated, unchanged + 1, failures)
else
(* Unknown OK message, count as unchanged *)
(migrated, unchanged + 1, failures)
| Ok (`Changed _) -> (migrated + 1, unchanged, failures)
| Ok (`Unchanged _) -> (migrated, unchanged + 1, failures)
| Error _ -> (migrated, unchanged, failures + 1))
(0, 0, 0)
in
Expand Down
133 changes: 95 additions & 38 deletions tools/src/migrate.ml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,33 @@ open Analysis
module StringMap = Map.Make (String)
module StringSet = Set.Make (String)
module IntSet = Set.Make (Int)
module FileSet = SharedTypes.FileSet

let filter_deprecations_for_project ?package ?dependency_paths ~entryPointFile
~deprecated_used =
let canonical p = try Unix.realpath p with _ -> p in
let package =
match package with
| Some p -> Some p
| None ->
let uri = Uri.fromPath entryPointFile in
Packages.getPackage ~uri
in
match package with
| None -> deprecated_used
| Some package ->
let dependency_paths =
match dependency_paths with
| Some paths -> paths
| None ->
FileSet.fold
(fun p acc -> acc |> FileSet.add p |> FileSet.add (canonical p))
package.dependenciesFiles FileSet.empty
in
deprecated_used
|> List.filter (fun (d : Cmt_utils.deprecated_used) ->
let loc_path = canonical d.source_loc.Location.loc_start.pos_fname in
not (FileSet.mem loc_path dependency_paths))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Identify dependency migrations by definition location

The dependency filter is keyed off d.source_loc, which is the location of the deprecated use (recorded via Builtin_attributes.check_deprecated at the call site). That means when a project file calls a deprecated API from a dependency, source_loc still points to the project file, so this filter won’t drop it and the migration will still rewrite user code. This contradicts the intended “skip dependency migrations” behavior (e.g., a project’s DeprecatedDep.oldThing() call will still be migrated). To suppress dependency-originated migrations you need to filter by where the deprecated item is defined, not the usage location.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How would you suggest fixing this?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@codex verify that this has been fixed now please.


(* Public API: migrate ~entryPointFile ~outputMode *)

Expand Down Expand Up @@ -736,18 +763,14 @@ let makeMapper (deprecated_used : Cmt_utils.deprecated_used list) =
in
mapper

let migrate ~entryPointFile ~outputMode =
let migrate ?package ?dependency_paths ~outputMode entryPointFile =
let path =
match Filename.is_relative entryPointFile with
| true -> Unix.realpath entryPointFile
| false -> entryPointFile
in
let result =
if Filename.check_suffix path ".res" then
let parser =
Res_driver.parsing_engine.parse_implementation ~for_printer:true
in
let {Res_driver.parsetree; comments; source} = parser ~filename:path in
match Cmt.loadCmtInfosFromPath ~path with
| None ->
Error
Expand All @@ -756,31 +779,43 @@ let migrate ~entryPointFile ~outputMode =
could not be found. try to build the project"
path)
| Some {cmt_extra_info = {deprecated_used}} ->
let mapper = makeMapper deprecated_used in
let astMapped = mapper.structure mapper parsetree in
(* Second pass: apply any post-migration transforms signaled via @apply.transforms *)
let apply_transforms =
let expr mapper (e : Parsetree.expression) =
let e = Ast_mapper.default_mapper.expr mapper e in
MapperUtils.ApplyTransforms.apply_on_self e
in
{Ast_mapper.default_mapper with expr}
let deprecated_used =
filter_deprecations_for_project ~entryPointFile:path ~deprecated_used
?package ?dependency_paths
in
let astTransformed =
apply_transforms.structure apply_transforms astMapped
in
Ok
( Res_printer.print_implementation
~width:Res_printer.default_print_width astTransformed ~comments,
source )
if Ext_list.is_empty deprecated_used then
match outputMode with
| `Stdout ->
let source = Res_io.read_file ~filename:path in
Ok (`Unchanged source)
| `File -> Ok (`Unchanged "")
else
let parser =
Res_driver.parsing_engine.parse_implementation ~for_printer:true
in
let {Res_driver.parsetree; comments; source} =
parser ~filename:path
in
let mapper = makeMapper deprecated_used in
let astMapped = mapper.structure mapper parsetree in
(* Second pass: apply any post-migration transforms signaled via @apply.transforms *)
let apply_transforms =
let expr mapper (e : Parsetree.expression) =
let e = Ast_mapper.default_mapper.expr mapper e in
MapperUtils.ApplyTransforms.apply_on_self e
in
{Ast_mapper.default_mapper with expr}
in
let astTransformed =
apply_transforms.structure apply_transforms astMapped
in
let contents =
Res_printer.print_implementation
~width:Res_printer.default_print_width astTransformed ~comments
in
if contents = source then Ok (`Unchanged source)
else Ok (`Changed contents)
else if Filename.check_suffix path ".resi" then
let parser =
Res_driver.parsing_engine.parse_interface ~for_printer:true
in
let {Res_driver.parsetree = signature; comments; source} =
parser ~filename:path
in

match Cmt.loadCmtInfosFromPath ~path with
| None ->
Error
Expand All @@ -789,9 +824,29 @@ let migrate ~entryPointFile ~outputMode =
could not be found. try to build the project"
path)
| Some {cmt_extra_info = {deprecated_used}} ->
let mapper = makeMapper deprecated_used in
let astMapped = mapper.signature mapper signature in
Ok (Res_printer.print_interface astMapped ~comments, source)
let deprecated_used =
filter_deprecations_for_project ~entryPointFile:path ~deprecated_used
?package ?dependency_paths
in
if Ext_list.is_empty deprecated_used then
match outputMode with
| `Stdout ->
let source = Res_io.read_file ~filename:path in
Ok (`Unchanged source)
| `File -> Ok (`Unchanged "")
else
let parser =
Res_driver.parsing_engine.parse_interface ~for_printer:true
in
let {Res_driver.parsetree = signature; comments; source} =
parser ~filename:path
in

let mapper = makeMapper deprecated_used in
let astMapped = mapper.signature mapper signature in
let contents = Res_printer.print_interface astMapped ~comments in
if contents = source then Ok (`Unchanged source)
else Ok (`Changed contents)
else
Error
(Printf.sprintf
Expand All @@ -800,15 +855,17 @@ let migrate ~entryPointFile ~outputMode =
in
match result with
| Error e -> Error e
| Ok (contents, source) when contents <> source -> (
| Ok (`Unchanged source) -> (
match outputMode with
| `Stdout -> Ok contents
| `Stdout -> Ok (`Unchanged source)
| `File ->
Ok (`Unchanged (Filename.basename path ^ ": File did not need migration"))
)
| Ok (`Changed contents) -> (
match outputMode with
| `Stdout -> Ok (`Changed contents)
| `File ->
let oc = open_out path in
Printf.fprintf oc "%s" contents;
close_out oc;
Ok (Filename.basename path ^ ": File migrated successfully"))
| Ok (contents, _) -> (
match outputMode with
| `Stdout -> Ok contents
| `File -> Ok (Filename.basename path ^ ": File did not need migration"))
Ok (`Changed (Filename.basename path ^ ": File migrated successfully")))
13 changes: 11 additions & 2 deletions tools/src/migrate.mli
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
val migrate :
entryPointFile:string ->
?package:Analysis.SharedTypes.package ->
?dependency_paths:Analysis.SharedTypes.FileSet.t ->
outputMode:[`File | `Stdout] ->
(string, string) result
string ->
([`Changed of string | `Unchanged of string], string) result

val filter_deprecations_for_project :
?package:Analysis.SharedTypes.package ->
?dependency_paths:Analysis.SharedTypes.FileSet.t ->
entryPointFile:string ->
deprecated_used:Cmt_utils.deprecated_used list ->
Cmt_utils.deprecated_used list
15 changes: 15 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -783,6 +783,7 @@ __metadata:
resolution: "@tests/tools@workspace:tests/tools_tests"
dependencies:
"@rescript/react": "link:../dependencies/rescript-react"
deprecatedlib: "link:../dependencies/deprecatedlib"
rescript: "workspace:^"
languageName: unknown
linkType: soft
Expand Down Expand Up @@ -1213,6 +1214,20 @@ __metadata:
languageName: node
linkType: hard

"deprecatedlib@link:../dependencies/deprecatedlib::locator=%40tests%2Ftools%40workspace%3Atests%2Ftools_tests":
version: 0.0.0-use.local
resolution: "deprecatedlib@link:../dependencies/deprecatedlib::locator=%40tests%2Ftools%40workspace%3Atests%2Ftools_tests"
languageName: node
linkType: soft

"deprecatedlib@workspace:tests/dependencies/deprecatedlib":
version: 0.0.0-use.local
resolution: "deprecatedlib@workspace:tests/dependencies/deprecatedlib"
dependencies:
rescript: "workspace:^"
languageName: unknown
linkType: soft

"diff@npm:^7.0.0":
version: 7.0.0
resolution: "diff@npm:7.0.0"
Expand Down
Loading