diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__all_middleware.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__all_middleware.snap index fc1be6fe3..5b10b4aec 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__all_middleware.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__all_middleware.snap @@ -4,7 +4,6 @@ assertion_line: 48 expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" --- added_files: - - default.project.json - "src\\csv.csv" - "src\\csv_init\\init.csv" - "src\\dir\\client_script.client.luau" diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__duplicate_rojo_id.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__duplicate_rojo_id.snap new file mode 100644 index 000000000..407cd50fe --- /dev/null +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__duplicate_rojo_id.snap @@ -0,0 +1,11 @@ +--- +source: tests/rojo_test/syncback_util.rs +assertion_line: 48 +expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" +--- +added_files: + - container.model.json +added_dirs: [] +removed_files: [] +removed_dirs: [] + diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects.snap index af2306edb..3531a587d 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects.snap @@ -4,7 +4,6 @@ assertion_line: 48 expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" --- added_files: - - default.project.json - nested.project.json - string_value.txt added_dirs: [] diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird.snap index 51d45a951..b9eac53e3 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__nested_projects_weird.snap @@ -4,9 +4,6 @@ assertion_line: 48 expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" --- added_files: - - client-only.project.json - - default.project.json - - server-only.project.json - "src/modules\\ClientModule.luau" - "src/modules\\ServerModule.luau" added_dirs: diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_all_middleware.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_all_middleware.snap new file mode 100644 index 000000000..119ed2573 --- /dev/null +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_all_middleware.snap @@ -0,0 +1,26 @@ +--- +source: tests/rojo_test/syncback_util.rs +assertion_line: 48 +expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" +--- +added_files: + - src/client_script.client.luau + - src/csv.csv + - "src/csv_init\\init.csv" + - "src/init_client_script\\init.client.lua" + - "src/init_module_script\\init.lua" + - "src/init_server_script\\init.server.lua" + - src/model_json.model.json + - src/module_script.luau + - src/project_json.project.json + - src/rbxmx.rbxmx + - src/server_script.server.luau + - src/text.txt +added_dirs: + - src/csv_init + - src/init_client_script + - src/init_module_script + - src/init_server_script +removed_files: [] +removed_dirs: [] + diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_init.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_init.snap index 1d17d5029..dc907e7a1 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_init.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_init.snap @@ -4,7 +4,6 @@ assertion_line: 48 expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" --- added_files: - - default.project.json - "src\\init.lua" added_dirs: - src diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_reserialize.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_reserialize.snap new file mode 100644 index 000000000..e88330b24 --- /dev/null +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__project_reserialize.snap @@ -0,0 +1,12 @@ +--- +source: tests/rojo_test/syncback_util.rs +assertion_line: 48 +expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" +--- +added_files: + - attribute_mismatch.luau + - property_mismatch.project.json +added_dirs: [] +removed_files: [] +removed_dirs: [] + diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback.snap index ab189346c..7a29413b5 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__rbxm_fallback.snap @@ -1,11 +1,11 @@ --- source: tests/rojo_test/syncback_util.rs +assertion_line: 48 expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" --- added_files: - "ReplicatedStorage\\ChildWithDuplicates.rbxm" - "ReplicatedStorage\\ChildWithoutDuplicates\\Child\\.gitkeep" - - default.project.json added_dirs: - ReplicatedStorage - "ReplicatedStorage\\ChildWithoutDuplicates" diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties.snap index a4a353073..69959cdc3 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties.snap @@ -1,9 +1,9 @@ --- source: tests/rojo_test/syncback_util.rs +assertion_line: 48 expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" --- added_files: - - default.project.json - "src\\pointer.model.json" - "src\\target.model.json" added_dirs: diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_update.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_update.snap index b422e9982..69959cdc3 100644 --- a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_update.snap +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__ref_properties_update.snap @@ -4,7 +4,6 @@ assertion_line: 48 expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" --- added_files: - - default.project.json - "src\\pointer.model.json" - "src\\target.model.json" added_dirs: diff --git a/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__string_value_project.snap b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__string_value_project.snap new file mode 100644 index 000000000..fb423b5e7 --- /dev/null +++ b/rojo-test/syncback-test-snapshots/end_to_end__rojo_test__syncback_util__string_value_project.snap @@ -0,0 +1,12 @@ +--- +source: tests/rojo_test/syncback_util.rs +assertion_line: 48 +expression: "visualize_fs_snapshot(&fs_snapshot, &output_path)" +--- +added_files: + - default.project.json + - string_value.txt +added_dirs: [] +removed_files: [] +removed_dirs: [] + diff --git a/rojo-test/syncback-tests/duplicate_rojo_id/expected/container.model.json b/rojo-test/syncback-tests/duplicate_rojo_id/expected/container.model.json new file mode 100644 index 000000000..aeb7b2b35 --- /dev/null +++ b/rojo-test/syncback-tests/duplicate_rojo_id/expected/container.model.json @@ -0,0 +1,21 @@ +{ + "className": "Folder", + "children": [ + { + "name": "value_1", + "className": "ObjectValue", + "attributes": { + "Rojo_Id": "value_1", + "Rojo_Target_Value": "value_1" + } + }, + { + "name": "value_2", + "className": "ObjectValue", + "attributes": { + "Rojo_Id": "72bc28150ada2e6206442ee300004084", + "Rojo_Target_Value": "72bc28150ada2e6206442ee300004084" + } + } + ] +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/duplicate_rojo_id/expected/default.project.json b/rojo-test/syncback-tests/duplicate_rojo_id/expected/default.project.json new file mode 100644 index 000000000..3343677aa --- /dev/null +++ b/rojo-test/syncback-tests/duplicate_rojo_id/expected/default.project.json @@ -0,0 +1,11 @@ +{ + "name": "duplicate_rojo_id", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "container": { + "$path": "container.model.json" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/duplicate_rojo_id/input.rbxl b/rojo-test/syncback-tests/duplicate_rojo_id/input.rbxl new file mode 100644 index 000000000..8e3e83691 Binary files /dev/null and b/rojo-test/syncback-tests/duplicate_rojo_id/input.rbxl differ diff --git a/rojo-test/syncback-tests/duplicate_rojo_id/output/container.model.json b/rojo-test/syncback-tests/duplicate_rojo_id/output/container.model.json new file mode 100644 index 000000000..9c551865b --- /dev/null +++ b/rojo-test/syncback-tests/duplicate_rojo_id/output/container.model.json @@ -0,0 +1,13 @@ +{ + "className": "Folder", + "children": [ + { + "name": "value_1", + "className": "ObjectValue" + }, + { + "name": "value_2", + "className": "ObjectValue" + } + ] +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/duplicate_rojo_id/output/default.project.json b/rojo-test/syncback-tests/duplicate_rojo_id/output/default.project.json new file mode 100644 index 000000000..3343677aa --- /dev/null +++ b/rojo-test/syncback-tests/duplicate_rojo_id/output/default.project.json @@ -0,0 +1,11 @@ +{ + "name": "duplicate_rojo_id", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "container": { + "$path": "container.model.json" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/default.project.json b/rojo-test/syncback-tests/project_all_middleware/expected/default.project.json new file mode 100644 index 000000000..2fd8876cd --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/default.project.json @@ -0,0 +1,53 @@ +{ + "name": "project_all_middleware", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "client_script": { + "$path": "src/client_script.client.luau" + }, + "csv": { + "$path": "src/csv.csv" + }, + "csv_init": { + "$path": "src/csv_init" + }, + "dir": { + "$path": "src/dir" + }, + "dir_with_meta": { + "$path": "src/dir_with_meta" + }, + "init_client_script": { + "$path": "src/init_client_script" + }, + "init_module_script": { + "$path": "src/init_module_script" + }, + "init_server_script": { + "$path": "src/init_server_script" + }, + "model_json": { + "$path": "src/model_json.model.json" + }, + "module_script": { + "$path": "src/module_script.luau" + }, + "project_json": { + "$path": "src/project_json.project.json" + }, + "rbxm": { + "$path": "src/rbxm.rbxm" + }, + "rbxmx": { + "$path": "src/rbxmx.rbxmx" + }, + "server_script": { + "$path": "src/server_script.server.luau" + }, + "text": { + "$path": "src/text.txt" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/client_script.client.luau b/rojo-test/syncback-tests/project_all_middleware/expected/src/client_script.client.luau new file mode 100644 index 000000000..1a6501120 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/client_script.client.luau @@ -0,0 +1 @@ +-- ghostwriter notorious mutter restless punish \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/csv.csv b/rojo-test/syncback-tests/project_all_middleware/expected/src/csv.csv new file mode 100644 index 000000000..b7c422222 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/csv.csv @@ -0,0 +1,2 @@ +Key,Source,Context,Example,es +Ack,Ack!,,An exclamation of despair,¡Ay! diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/csv_init/init.csv b/rojo-test/syncback-tests/project_all_middleware/expected/src/csv_init/init.csv new file mode 100644 index 000000000..61a49274e --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/csv_init/init.csv @@ -0,0 +1,2 @@ +Key,Source,Context,Example,en +Rojo,Rojo,,Rojo is a really cool program,Red diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/dir_with_meta/init.meta.json b/rojo-test/syncback-tests/project_all_middleware/expected/src/dir_with_meta/init.meta.json new file mode 100644 index 000000000..d62029576 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/dir_with_meta/init.meta.json @@ -0,0 +1,3 @@ +{ + "className": "Configuration" +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/init_client_script/init.client.lua b/rojo-test/syncback-tests/project_all_middleware/expected/src/init_client_script/init.client.lua new file mode 100644 index 000000000..2330a0023 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/init_client_script/init.client.lua @@ -0,0 +1 @@ +-- brag season coffin dilute flourish \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/init_module_script/init.lua b/rojo-test/syncback-tests/project_all_middleware/expected/src/init_module_script/init.lua new file mode 100644 index 000000000..e36c2d878 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/init_module_script/init.lua @@ -0,0 +1 @@ +-- absorb dragon coat crowd effect \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/init_server_script/init.server.lua b/rojo-test/syncback-tests/project_all_middleware/expected/src/init_server_script/init.server.lua new file mode 100644 index 000000000..8518c74fe --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/init_server_script/init.server.lua @@ -0,0 +1 @@ +-- rojo syncback very cool dekkonot \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/model_json.model.json b/rojo-test/syncback-tests/project_all_middleware/expected/src/model_json.model.json new file mode 100644 index 000000000..a75990c38 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/model_json.model.json @@ -0,0 +1,6 @@ +{ + "className": "StringValue", + "properties": { + "Value": "i understand how person299 felt" + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/module_script.luau b/rojo-test/syncback-tests/project_all_middleware/expected/src/module_script.luau new file mode 100644 index 000000000..b5c15e2b1 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/module_script.luau @@ -0,0 +1 @@ +-- hospitality publish accumulation onion shaft \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/project_json.project.json b/rojo-test/syncback-tests/project_all_middleware/expected/src/project_json.project.json new file mode 100644 index 000000000..cb0ce6a2e --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/project_json.project.json @@ -0,0 +1,13 @@ +{ + "name": "project_json", + "tree": { + "$className": "Color3Value", + "$properties": { + "Value": [ + 1.0, + 0.5, + 0.0 + ] + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/rbxm.rbxm b/rojo-test/syncback-tests/project_all_middleware/expected/src/rbxm.rbxm new file mode 100644 index 000000000..1fc6714ae Binary files /dev/null and b/rojo-test/syncback-tests/project_all_middleware/expected/src/rbxm.rbxm differ diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/rbxmx.rbxmx b/rojo-test/syncback-tests/project_all_middleware/expected/src/rbxmx.rbxmx new file mode 100644 index 000000000..50a48b184 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/rbxmx.rbxmx @@ -0,0 +1,13 @@ + + + + rbxmx + + 0 + false + -1 + + ripe alike review heart dry + + + \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/server_script.server.luau b/rojo-test/syncback-tests/project_all_middleware/expected/src/server_script.server.luau new file mode 100644 index 000000000..ba51694c9 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/server_script.server.luau @@ -0,0 +1 @@ +-- ostracize fraud consciousness seal architecture \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/expected/src/text.txt b/rojo-test/syncback-tests/project_all_middleware/expected/src/text.txt new file mode 100644 index 000000000..a4cb228b7 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/expected/src/text.txt @@ -0,0 +1 @@ +According to all known laws of aviation \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/input.rbxl b/rojo-test/syncback-tests/project_all_middleware/input.rbxl new file mode 100644 index 000000000..824960bac Binary files /dev/null and b/rojo-test/syncback-tests/project_all_middleware/input.rbxl differ diff --git a/rojo-test/syncback-tests/project_all_middleware/output/default.project.json b/rojo-test/syncback-tests/project_all_middleware/output/default.project.json new file mode 100644 index 000000000..2fd8876cd --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/output/default.project.json @@ -0,0 +1,53 @@ +{ + "name": "project_all_middleware", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "client_script": { + "$path": "src/client_script.client.luau" + }, + "csv": { + "$path": "src/csv.csv" + }, + "csv_init": { + "$path": "src/csv_init" + }, + "dir": { + "$path": "src/dir" + }, + "dir_with_meta": { + "$path": "src/dir_with_meta" + }, + "init_client_script": { + "$path": "src/init_client_script" + }, + "init_module_script": { + "$path": "src/init_module_script" + }, + "init_server_script": { + "$path": "src/init_server_script" + }, + "model_json": { + "$path": "src/model_json.model.json" + }, + "module_script": { + "$path": "src/module_script.luau" + }, + "project_json": { + "$path": "src/project_json.project.json" + }, + "rbxm": { + "$path": "src/rbxm.rbxm" + }, + "rbxmx": { + "$path": "src/rbxmx.rbxmx" + }, + "server_script": { + "$path": "src/server_script.server.luau" + }, + "text": { + "$path": "src/text.txt" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/client_script.client.luau b/rojo-test/syncback-tests/project_all_middleware/output/src/client_script.client.luau new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/csv.csv b/rojo-test/syncback-tests/project_all_middleware/output/src/csv.csv new file mode 100644 index 000000000..c5f48af47 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/output/src/csv.csv @@ -0,0 +1,2 @@ +Key,Source,Context,Example,es +,,,, \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/csv_init/init.csv b/rojo-test/syncback-tests/project_all_middleware/output/src/csv_init/init.csv new file mode 100644 index 000000000..24244d6c1 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/output/src/csv_init/init.csv @@ -0,0 +1,2 @@ +Key,Source,Context,Example,en +,,,, diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/dir_with_meta/init.meta.json b/rojo-test/syncback-tests/project_all_middleware/output/src/dir_with_meta/init.meta.json new file mode 100644 index 000000000..d62029576 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/output/src/dir_with_meta/init.meta.json @@ -0,0 +1,3 @@ +{ + "className": "Configuration" +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/init_client_script/init.client.lua b/rojo-test/syncback-tests/project_all_middleware/output/src/init_client_script/init.client.lua new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/init_module_script/init.lua b/rojo-test/syncback-tests/project_all_middleware/output/src/init_module_script/init.lua new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/init_server_script/init.server.lua b/rojo-test/syncback-tests/project_all_middleware/output/src/init_server_script/init.server.lua new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/model_json.model.json b/rojo-test/syncback-tests/project_all_middleware/output/src/model_json.model.json new file mode 100644 index 000000000..a095016a7 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/output/src/model_json.model.json @@ -0,0 +1,3 @@ +{ + "className": "StringValue" +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/module_script.luau b/rojo-test/syncback-tests/project_all_middleware/output/src/module_script.luau new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/project_json.project.json b/rojo-test/syncback-tests/project_all_middleware/output/src/project_json.project.json new file mode 100644 index 000000000..ed8452050 --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/output/src/project_json.project.json @@ -0,0 +1,6 @@ +{ + "name": "project_json", + "tree": { + "$className": "Color3Value" + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/rbxm.rbxm b/rojo-test/syncback-tests/project_all_middleware/output/src/rbxm.rbxm new file mode 100644 index 000000000..1fc6714ae Binary files /dev/null and b/rojo-test/syncback-tests/project_all_middleware/output/src/rbxm.rbxm differ diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/rbxmx.rbxmx b/rojo-test/syncback-tests/project_all_middleware/output/src/rbxmx.rbxmx new file mode 100644 index 000000000..81a52114b --- /dev/null +++ b/rojo-test/syncback-tests/project_all_middleware/output/src/rbxmx.rbxmx @@ -0,0 +1,7 @@ + + + + rbxmx + + + \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/server_script.server.luau b/rojo-test/syncback-tests/project_all_middleware/output/src/server_script.server.luau new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_all_middleware/output/src/text.txt b/rojo-test/syncback-tests/project_all_middleware/output/src/text.txt new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_reserialize/expected/attribute_mismatch.luau b/rojo-test/syncback-tests/project_reserialize/expected/attribute_mismatch.luau new file mode 100644 index 000000000..b6c5da36f --- /dev/null +++ b/rojo-test/syncback-tests/project_reserialize/expected/attribute_mismatch.luau @@ -0,0 +1 @@ +-- satellite beef psychology response supply \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_reserialize/expected/default.project.json b/rojo-test/syncback-tests/project_reserialize/expected/default.project.json new file mode 100644 index 000000000..bf56d2e07 --- /dev/null +++ b/rojo-test/syncback-tests/project_reserialize/expected/default.project.json @@ -0,0 +1,29 @@ +{ + "name": "project_reserialize", + "tree": { + "$className": "DataModel", + "Workspace": { + "attribute_mismatch": { + "$attributes": { + "foo": "bar" + }, + "$path": "attribute_mismatch.luau" + }, + "property_mismatch": { + "$path": "property_mismatch.project.json" + }, + "$properties": { + "CSGAsyncDynamicCollision": { + "Enum": 0 + }, + "DecreaseMinimumPartDensityMode": { + "Enum": 0 + }, + "StreamingEnabled": true + }, + "$attributes": { + "Rojo_Target_CurrentCamera": "6d6ae1d713c82fae0620aa1300000375" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_reserialize/expected/property_mismatch.project.json b/rojo-test/syncback-tests/project_reserialize/expected/property_mismatch.project.json new file mode 100644 index 000000000..1ca8b303e --- /dev/null +++ b/rojo-test/syncback-tests/project_reserialize/expected/property_mismatch.project.json @@ -0,0 +1,11 @@ +{ + "name": "property_mismatch", + "tree": { + "$className": "BrickColorValue", + "$properties": { + "Value": { + "BrickColor": 345 + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_reserialize/input.rbxl b/rojo-test/syncback-tests/project_reserialize/input.rbxl new file mode 100644 index 000000000..6d5da6818 Binary files /dev/null and b/rojo-test/syncback-tests/project_reserialize/input.rbxl differ diff --git a/rojo-test/syncback-tests/project_reserialize/output/attribute_mismatch.luau b/rojo-test/syncback-tests/project_reserialize/output/attribute_mismatch.luau new file mode 100644 index 000000000..e69de29bb diff --git a/rojo-test/syncback-tests/project_reserialize/output/default.project.json b/rojo-test/syncback-tests/project_reserialize/output/default.project.json new file mode 100644 index 000000000..bf56d2e07 --- /dev/null +++ b/rojo-test/syncback-tests/project_reserialize/output/default.project.json @@ -0,0 +1,29 @@ +{ + "name": "project_reserialize", + "tree": { + "$className": "DataModel", + "Workspace": { + "attribute_mismatch": { + "$attributes": { + "foo": "bar" + }, + "$path": "attribute_mismatch.luau" + }, + "property_mismatch": { + "$path": "property_mismatch.project.json" + }, + "$properties": { + "CSGAsyncDynamicCollision": { + "Enum": 0 + }, + "DecreaseMinimumPartDensityMode": { + "Enum": 0 + }, + "StreamingEnabled": true + }, + "$attributes": { + "Rojo_Target_CurrentCamera": "6d6ae1d713c82fae0620aa1300000375" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/project_reserialize/output/property_mismatch.project.json b/rojo-test/syncback-tests/project_reserialize/output/property_mismatch.project.json new file mode 100644 index 000000000..cb34ee96d --- /dev/null +++ b/rojo-test/syncback-tests/project_reserialize/output/property_mismatch.project.json @@ -0,0 +1,6 @@ +{ + "name": "property_mismatch", + "tree": { + "$className": "BrickColorValue" + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/string_value_project/expected/default.project.json b/rojo-test/syncback-tests/string_value_project/expected/default.project.json new file mode 100644 index 000000000..7e717416c --- /dev/null +++ b/rojo-test/syncback-tests/string_value_project/expected/default.project.json @@ -0,0 +1,20 @@ +{ + "name": "string_value_project", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "inside_project_file": { + "$className": "StringValue", + "$properties": { + "Value": "imgettingverytiredofwritingthesetests2" + } + }, + "on_file_system": { + "$attributes": { + "imgettingverytiredofwritingthesetests": "person299 was ahead of his time" + }, + "$path": "string_value.txt" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/string_value_project/expected/string_value.txt b/rojo-test/syncback-tests/string_value_project/expected/string_value.txt new file mode 100644 index 000000000..a29b38294 --- /dev/null +++ b/rojo-test/syncback-tests/string_value_project/expected/string_value.txt @@ -0,0 +1 @@ +shout out to the brown bug anthology in person299's admin commands \ No newline at end of file diff --git a/rojo-test/syncback-tests/string_value_project/input.rbxl b/rojo-test/syncback-tests/string_value_project/input.rbxl new file mode 100644 index 000000000..cea6a4853 Binary files /dev/null and b/rojo-test/syncback-tests/string_value_project/input.rbxl differ diff --git a/rojo-test/syncback-tests/string_value_project/output/default.project.json b/rojo-test/syncback-tests/string_value_project/output/default.project.json new file mode 100644 index 000000000..00fa7c2d2 --- /dev/null +++ b/rojo-test/syncback-tests/string_value_project/output/default.project.json @@ -0,0 +1,14 @@ +{ + "name": "string_value_project", + "tree": { + "$className": "DataModel", + "ReplicatedStorage": { + "on_file_system": { + "$path": "string_value.txt" + }, + "inside_project_file": { + "$className": "StringValue" + } + } + } +} \ No newline at end of file diff --git a/rojo-test/syncback-tests/string_value_project/output/string_value.txt b/rojo-test/syncback-tests/string_value_project/output/string_value.txt new file mode 100644 index 000000000..e69de29bb diff --git a/src/cli/syncback.rs b/src/cli/syncback.rs index d8782fac7..4ce557ffd 100644 --- a/src/cli/syncback.rs +++ b/src/cli/syncback.rs @@ -254,11 +254,14 @@ fn list_files(snapshot: &FsSnapshot, color: ColorChoice, base_path: &Path) -> io let mut buffer = writer.buffer(); if snapshot.is_empty() { - writeln!(&mut buffer, "No files/added would be removed or added.")?; + writeln!( + &mut buffer, + "No files/directories would be removed or added." + )?; } else { let added = snapshot.added_paths(); if !added.is_empty() { - writeln!(&mut buffer, "Writing files/folders:")?; + writeln!(&mut buffer, "Writing files/directories:")?; buffer.set_color(&add_color)?; for path in added { writeln!( @@ -271,7 +274,7 @@ fn list_files(snapshot: &FsSnapshot, color: ColorChoice, base_path: &Path) -> io } let removed = snapshot.removed_paths(); if !removed.is_empty() { - writeln!(&mut buffer, "Removing files/folders:")?; + writeln!(&mut buffer, "Removing files/directories:")?; buffer.set_color(&remove_color)?; for path in removed { writeln!( diff --git a/src/snapshot/metadata.rs b/src/snapshot/metadata.rs index 55158518a..ffdaedbe4 100644 --- a/src/snapshot/metadata.rs +++ b/src/snapshot/metadata.rs @@ -63,6 +63,8 @@ pub struct InstanceMetadata { /// Indicates the ID used for Ref properties pointing to this Instance. pub specified_id: Option, + /// The Middleware that was used to create this Instance. Should generally + /// not be `None` except if the snapshotting process is not completed. pub middleware: Option, } @@ -106,6 +108,13 @@ impl InstanceMetadata { } } + pub fn middleware(self, middleware: Middleware) -> Self { + Self { + middleware: Some(middleware), + ..self + } + } + pub fn specified_id(self, id: Option) -> Self { Self { specified_id: id, diff --git a/src/snapshot_middleware/meta_file.rs b/src/snapshot_middleware/meta_file.rs index 089fffa60..604de4500 100644 --- a/src/snapshot_middleware/meta_file.rs +++ b/src/snapshot_middleware/meta_file.rs @@ -81,7 +81,7 @@ impl AdjacentMetadata { .unwrap_or_default(); let class = &snapshot.new_inst().class; - for (name, value) in snapshot.get_filtered_properties(snapshot.new).unwrap() { + for (name, value) in snapshot.get_path_filtered_properties(snapshot.new).unwrap() { match value { Variant::Attributes(attrs) => { for (attr_name, attr_value) in attrs.iter() { @@ -262,7 +262,7 @@ impl DirectoryMetadata { .unwrap_or_default(); let class = &snapshot.new_inst().class; - for (name, value) in snapshot.get_filtered_properties(snapshot.new).unwrap() { + for (name, value) in snapshot.get_path_filtered_properties(snapshot.new).unwrap() { match value { Variant::Attributes(attrs) => { for (name, value) in attrs.iter() { diff --git a/src/snapshot_middleware/mod.rs b/src/snapshot_middleware/mod.rs index cd0ccb39f..029f4f305 100644 --- a/src/snapshot_middleware/mod.rs +++ b/src/snapshot_middleware/mod.rs @@ -284,6 +284,21 @@ impl Middleware { ) } + /// Returns whether this particular middleware sets its own properties. + /// This applies to things like `JsonModel` and `Project`, since they + /// set properties without needing a meta.json file. + /// + /// It does not cover middleware like `ServerScript` or `Csv` because they + /// need a meta.json file set properties that aren't their designated + /// 'special' properties. + #[inline] + pub fn handles_own_properties(&self) -> bool { + matches!( + self, + Middleware::JsonModel | Middleware::Project | Middleware::Rbxm | Middleware::Rbxmx + ) + } + /// Attempts to return a middleware that should be used for the given path. /// /// Returns `Err` only if the Vfs cannot read information about the path. diff --git a/src/snapshot_middleware/project.rs b/src/snapshot_middleware/project.rs index 4009a0289..01e33282f 100644 --- a/src/snapshot_middleware/project.rs +++ b/src/snapshot_middleware/project.rs @@ -20,7 +20,8 @@ use crate::{ PathIgnoreRule, SyncRule, }, snapshot_middleware::Middleware, - syncback::{FsSnapshot, SyncbackReturn, SyncbackSnapshot}, + syncback::{filter_properties, FsSnapshot, SyncbackReturn, SyncbackSnapshot}, + variant_eq::variant_eq, RojoRef, }; @@ -341,6 +342,7 @@ pub fn syncback_project<'sync>( let mut old_child_map = HashMap::new(); let mut new_child_map = HashMap::new(); + let mut node_changed_map = Vec::new(); let mut node_queue = VecDeque::with_capacity(1); node_queue.push_back((&mut project.tree, old_inst, snapshot.new_inst())); @@ -348,51 +350,21 @@ pub fn syncback_project<'sync>( log::debug!("Processing node {}", old_inst.name()); if old_inst.class_name() != new_inst.class { anyhow::bail!( - "Cannot change the class of {} in project file {}", + "Cannot change the class of {} in project file {}.\n\ + Current class is {}, it is a {} in the input file.", old_inst.name(), - project_path.display() + project_path.display(), + old_inst.class_name(), + new_inst.class ); } - let filtered_properties = snapshot - .get_filtered_properties(new_inst.referent()) - .unwrap(); - let properties = &mut node.properties; - let mut attributes = BTreeMap::new(); - - // We would ideally have different behavior here based on whether a - // node has `$path` set. However, due to an issue with Middleware not - // knowing whether they originate from a project or not, we just skip - // writing metadata for things from projects. So to avoid properties - // being dropped, we don't filter them specially. - // TODO: We should handle this branching somehow so that meta.json files - // are respected. - for (name, value) in filtered_properties { - match value { - Variant::Attributes(attrs) => { - for (attr_name, attr_value) in attrs.iter() { - attributes.insert( - attr_name.clone(), - UnresolvedValue::from_variant_unambiguous(attr_value.clone()), - ); - } - } - Variant::SharedString(_) => { - log::warn!( - "Rojo cannot serialize the property {}.{name} in project files.\n\ - If this is not acceptable, resave the Instance at '{}' manually as an RBXM or RBXMX.", new_inst.class, snapshot.get_new_inst_path(snapshot.new) - ); - } - _ => { - properties.insert( - name.to_string(), - UnresolvedValue::from_variant(value.clone(), &new_inst.class, name), - ); - } - } - } - node.attributes = attributes; - + // TODO handle meta.json files in this branch. Right now, we perform + // syncback if a node has `$path` set but the Middleware aren't aware + // that the Instances they're running on originate in a project.json. + // As a result, the `meta.json` syncback code is hardcoded to not work + // if the Instance originates from a project file. However, we should + // ideally use a .meta.json over the project node if it exists already. if node.path.is_some() { // Since the node has a path, we have to run syncback on it. let node_path = node.path.as_ref().map(PathNode::path).expect( @@ -405,16 +377,34 @@ pub fn syncback_project<'sync>( base_path.join(node_path) }; - let snapshot = syncback_project_node( - snapshot, + let middleware = match Middleware::middleware_for_path( + snapshot.vfs(), &project.sync_rules, &full_path, - new_inst, - old_inst, - )?; - descendant_snapshots.push(snapshot); + )? { + Some(middleware) => middleware, + // The only way this can happen at this point is if the path does + // not exist on the file system or there's no middleware for it. + None => anyhow::bail!( + "path does not exist or could not be turned into a file Rojo understands: {}", + full_path.display() + ), + }; + + descendant_snapshots.push( + snapshot + .with_new_path(full_path.clone(), new_inst.referent(), Some(old_inst.id())) + .middleware(middleware), + ); ref_to_path_map.insert(new_inst.referent(), full_path); + + // We only want to set properties if it needs it. + if !middleware.handles_own_properties() { + project_node_property_syncback_path(snapshot, new_inst, node); + } + } else { + project_node_property_syncback_no_path(snapshot, new_inst, node); } for child_ref in new_inst.children() { @@ -511,41 +501,126 @@ pub fn syncback_project<'sync>( } } removed_descendants.extend(old_child_map.drain().map(|(_, v)| v)); + node_changed_map.push((&node.properties, &node.attributes, old_inst)) + } + let mut fs_snapshot = FsSnapshot::new(); + + for (node_properties, node_attributes, old_inst) in node_changed_map { + if project_node_should_reserialize(node_properties, node_attributes, old_inst)? { + fs_snapshot.add_file(project_path, serde_json::to_vec_pretty(&project)?); + break; + } } Ok(SyncbackReturn { inst_snapshot: InstanceSnapshot::from_instance(snapshot.new_inst()), - fs_snapshot: FsSnapshot::new() - .with_added_file(project_path, serde_json::to_vec_pretty(&project)?), + fs_snapshot, children: descendant_snapshots, removed_children: removed_descendants, }) } -fn syncback_project_node<'sync>( - snapshot: &SyncbackSnapshot<'sync>, - sync_rules: &[SyncRule], - node_path: &Path, +fn project_node_property_syncback<'inst>( + snapshot: &SyncbackSnapshot, + filtered_properties: HashMap<&'inst str, &'inst Variant>, new_inst: &Instance, - old_inst: InstanceWithMeta, -) -> anyhow::Result> { - let middleware = match Middleware::middleware_for_path(snapshot.vfs(), sync_rules, node_path)? { - Some(stuff) => stuff, - // The only way this can happen at this point is if the path does - // not exist on the file system or there's no middleware for it. - None => anyhow::bail!( - "path does not exist or could not be turned into a file Rojo understands: {}", - node_path.display() - ), - }; + node: &mut ProjectNode, +) { + let properties = &mut node.properties; + let mut attributes = BTreeMap::new(); + for (name, value) in filtered_properties { + match value { + Variant::Attributes(attrs) => { + for (attr_name, attr_value) in attrs.iter() { + attributes.insert( + attr_name.clone(), + UnresolvedValue::from_variant_unambiguous(attr_value.clone()), + ); + } + } + Variant::SharedString(_) => { + log::warn!( + "Rojo cannot serialize the property {}.{name} in project files.\n\ + If this is not acceptable, resave the Instance at '{}' manually as an RBXM or RBXMX.", new_inst.class, snapshot.get_new_inst_path(snapshot.new) + ); + } + _ => { + properties.insert( + name.to_string(), + UnresolvedValue::from_variant(value.clone(), &new_inst.class, name), + ); + } + } + } + node.attributes = attributes; +} - Ok(snapshot - .with_new_path( - node_path.to_path_buf(), - new_inst.referent(), - Some(old_inst.id()), - ) - .middleware(middleware)) +fn project_node_property_syncback_path( + snapshot: &SyncbackSnapshot, + new_inst: &Instance, + node: &mut ProjectNode, +) { + let filtered_properties = snapshot + .get_path_filtered_properties(new_inst.referent()) + .unwrap(); + project_node_property_syncback(snapshot, filtered_properties, new_inst, node) +} + +fn project_node_property_syncback_no_path( + snapshot: &SyncbackSnapshot, + new_inst: &Instance, + node: &mut ProjectNode, +) { + let filtered_properties = filter_properties(snapshot.project(), new_inst); + project_node_property_syncback(snapshot, filtered_properties, new_inst, node) +} + +fn project_node_should_reserialize( + node_properties: &BTreeMap, + node_attributes: &BTreeMap, + instance: InstanceWithMeta, +) -> anyhow::Result { + for (prop_name, unresolved_node_value) in node_properties { + if let Some(inst_value) = instance.properties().get(prop_name) { + let node_value = unresolved_node_value + .clone() + .resolve(instance.name(), prop_name)?; + if !variant_eq(inst_value, &node_value) { + return Ok(true); + } + } else { + return Ok(true); + } + } + + match instance.properties().get("Attributes") { + Some(Variant::Attributes(inst_attributes)) => { + // This will also catch if one is empty but the other isn't + if node_attributes.len() != inst_attributes.len() { + Ok(true) + } else { + for (attr_name, unresolved_node_value) in node_attributes { + if let Some(inst_value) = inst_attributes.get(attr_name.as_str()) { + let node_value = unresolved_node_value.clone().resolve_unambiguous()?; + if !variant_eq(inst_value, &node_value) { + return Ok(true); + } + } else { + return Ok(true); + } + } + Ok(false) + } + } + Some(_) => Ok(true), + None => { + if !node_attributes.is_empty() { + Ok(true) + } else { + Ok(false) + } + } + } } fn infer_class_name(name: &str, parent_class: Option<&str>) -> Option> { diff --git a/src/syncback/mod.rs b/src/syncback/mod.rs index 7242ba90e..70ae56167 100644 --- a/src/syncback/mod.rs +++ b/src/syncback/mod.rs @@ -63,7 +63,7 @@ pub fn syncback_loop( strip_unknown_root_children(&mut new_tree, old_tree); log::debug!("Collecting referents for new DOM..."); - let deferred_referents = collect_referents(&new_tree)?; + let deferred_referents = collect_referents(&new_tree); log::debug!("Pre-filtering properties on DOMs"); for referent in descendants(&new_tree, new_tree.root_ref()) { @@ -113,6 +113,8 @@ pub fn syncback_loop( log::debug!("Skipping referent linking as per project syncback rules"); } + new_tree.root_mut().name = project.name.clone(); + log::debug!("Hashing project DOM"); let old_hashes = hash_tree(project, old_tree.inner(), old_tree.get_root_id()); log::debug!("Hashing file DOM"); @@ -265,7 +267,7 @@ pub fn get_best_middleware(snapshot: &SyncbackSnapshot) -> Middleware { let mut middleware; if let Some(override_middleware) = snapshot.middleware { - middleware = override_middleware; + return override_middleware; } else if let Some(old_middleware) = old_middleware { return old_middleware; } else if json_model_classes.contains(inst.class.as_str()) { diff --git a/src/syncback/ref_properties.rs b/src/syncback/ref_properties.rs index cc08267c3..26544d9b8 100644 --- a/src/syncback/ref_properties.rs +++ b/src/syncback/ref_properties.rs @@ -1,17 +1,25 @@ //! Implements iterating through an entire WeakDom and linking all Ref //! properties using attributes. -use std::collections::{HashMap, VecDeque}; +use std::collections::{hash_map::Entry, HashMap, VecDeque}; use rbx_dom_weak::{ - types::{Attributes, Ref, Variant}, + types::{Attributes, Ref, UniqueId, Variant}, Instance, WeakDom, }; use crate::{multimap::MultiMap, REF_ID_ATTRIBUTE_NAME, REF_POINTER_ATTRIBUTE_PREFIX}; +pub struct RefLinks { + /// A map of referents to each of their Ref properties. + prop_links: MultiMap, + /// A set of referents that need their ID rewritten. This includes + /// Instances that have no existing ID. + need_rewrite: Vec, +} + #[derive(PartialEq, Eq)] -pub struct RefLink { +struct RefLink { /// The name of a property name: String, /// The value of the property. @@ -22,7 +30,9 @@ pub struct RefLink { /// /// They can be linked to a dom later using the `link` method on the returned /// struct. -pub fn collect_referents(dom: &WeakDom) -> anyhow::Result> { +pub fn collect_referents(dom: &WeakDom) -> RefLinks { + let mut existing_ids = HashMap::new(); + let mut need_rewrite = Vec::new(); let mut links = MultiMap::new(); let mut queue = VecDeque::new(); @@ -30,98 +40,169 @@ pub fn collect_referents(dom: &WeakDom) -> anyhow::Result // Note that this is back-in, front-out. This is important because // VecDeque::extend is the equivalent to using push_back. queue.push_back(dom.root_ref()); - while let Some(referent) = queue.pop_front() { - let instance = dom.get_by_ref(referent).unwrap(); + while let Some(inst_ref) = queue.pop_front() { + let instance = dom.get_by_ref(inst_ref).unwrap(); queue.extend(instance.children().iter().copied()); - for (name, value) in &instance.properties { - if let Variant::Ref(prop_value) = value { - if dom.get_by_ref(*prop_value).is_some() { - links.insert( - referent, - RefLink { - name: name.clone(), - value: *prop_value, - }, - ); + // Collect all referent properties for easy access later + for (property_name, prop_value) in &instance.properties { + if let Variant::Ref(prop_ref) = prop_value { + // Any Instance that's pointed to as a property needs an ID. + let existing_id = match dom.get_by_ref(*prop_ref) { + Some(inst) => get_existing_id(inst), + None => continue, + }; + if let Some(existing_id) = existing_id { + match existing_ids.entry(existing_id) { + Entry::Occupied(entry) => { + if entry.get() != prop_ref { + need_rewrite.push(*prop_ref); + } + } + Entry::Vacant(entry) => { + entry.insert(*prop_ref); + } + } + } else { + need_rewrite.push(*prop_ref); } + // We also need a list of these properties for linking later + links.insert( + inst_ref, + RefLink { + name: property_name.to_owned(), + value: *prop_ref, + }, + ); } } } - Ok(links) + RefLinks { + prop_links: links, + need_rewrite, + } } -pub fn link_referents(link_list: MultiMap, dom: &mut WeakDom) -> anyhow::Result<()> { - let mut pointer_attributes = HashMap::new(); - for (pointer_ref, ref_properties) in link_list { - if dom.get_by_ref(pointer_ref).is_none() { - continue; - } +pub fn link_referents(links: RefLinks, dom: &mut WeakDom) -> anyhow::Result<()> { + write_id_attributes(&links, dom)?; + + let mut prop_list = Vec::new(); + let mut attribute_list = HashMap::new(); - // In this loop, we need to add the `Rojo_Id` attributes to the - // Instances. - for ref_link in ref_properties { - let target_inst = match dom.get_by_ref_mut(ref_link.value) { + for (inst_id, properties) in links.prop_links { + for ref_link in properties { + let prop_inst = match dom.get_by_ref(ref_link.value) { Some(inst) => inst, - None => { - continue; - } + None => continue, }; - pointer_attributes.insert(ref_link.name, get_or_insert_id(target_inst)?); + let id = get_existing_id(prop_inst) + .expect("all Instances that are pointed to should have an ID"); + prop_list.push((ref_link.name, Variant::String(id.to_owned()))); } + let inst = match dom.get_by_ref_mut(inst_id) { + Some(inst) => inst, + None => continue, + }; - let pointer_inst = dom.get_by_ref_mut(pointer_ref).unwrap(); - let pointer_attrs = get_or_insert_attributes(pointer_inst)?; - for (name, id) in pointer_attributes.drain() { - pointer_attrs.insert( - format!("{REF_POINTER_ATTRIBUTE_PREFIX}{name}"), - Variant::BinaryString(id.into_bytes().into()), + // TODO: Replace this whole rigamarole with `Attributes::drain` + // eventually. + let attributes = match inst.properties.remove("Attributes") { + Some(Variant::Attributes(attrs)) => attrs, + None => Attributes::new(), + Some(value) => { + anyhow::bail!( + "expected Attributes to be of type 'Attributes' but it was of type '{:?}'", + value.ty() + ); + } + }; + for (name, value) in attributes.into_iter() { + if !name.starts_with(REF_POINTER_ATTRIBUTE_PREFIX) { + attribute_list.insert(name, value); + } + } + + for (prop_name, prop_value) in prop_list.drain(..) { + attribute_list.insert( + format!("{REF_POINTER_ATTRIBUTE_PREFIX}{prop_name}"), + prop_value, ); } + + // TODO: Same as above, when `Attributes::drain` is live, replace this + // with it. + inst.properties.insert( + "Attributes".into(), + Attributes::from_iter(attribute_list.drain()).into(), + ); } Ok(()) } -fn get_or_insert_attributes(inst: &mut Instance) -> anyhow::Result<&mut Attributes> { - if !inst.properties.contains_key("Attributes") { - inst.properties - .insert("Attributes".into(), Attributes::new().into()); - } - match inst.properties.get_mut("Attributes") { - Some(Variant::Attributes(attrs)) => Ok(attrs), - Some(ty) => Err(anyhow::format_err!( - "expected property Attributes to be an Attributes but it was {:?}", - ty.ty() - )), - None => unreachable!(), +fn write_id_attributes(links: &RefLinks, dom: &mut WeakDom) -> anyhow::Result<()> { + for referent in &links.need_rewrite { + let inst = match dom.get_by_ref_mut(*referent) { + Some(inst) => inst, + None => continue, + }; + let unique_id = match inst.properties.get("UniqueId") { + Some(Variant::UniqueId(id)) => Some(*id), + _ => None, + } + .unwrap_or_else(|| UniqueId::now().unwrap()); + + let attributes = match inst.properties.get_mut("Attributes") { + Some(Variant::Attributes(attrs)) => attrs, + None => { + inst.properties + .insert("Attributes".into(), Attributes::new().into()); + match inst.properties.get_mut("Attributes") { + Some(Variant::Attributes(attrs)) => attrs, + _ => unreachable!(), + } + } + Some(value) => { + anyhow::bail!( + "expected Attributes to be of type 'Attributes' but it was of type '{:?}'", + value.ty() + ); + } + }; + attributes.insert( + REF_ID_ATTRIBUTE_NAME.into(), + Variant::String(unique_id.to_string()), + ); } + Ok(()) } -fn get_or_insert_id(inst: &mut Instance) -> anyhow::Result { - let unique_id = match inst.properties.get("UniqueId") { - Some(Variant::UniqueId(id)) => Some(*id), - _ => None, - }; - let referent = inst.referent(); - let attributes = get_or_insert_attributes(inst)?; - match attributes.get(REF_ID_ATTRIBUTE_NAME) { - Some(Variant::String(str)) => return Ok(str.clone()), - Some(Variant::BinaryString(bytes)) => match std::str::from_utf8(bytes.as_ref()) { - Ok(str) => return Ok(str.to_string()), - Err(_) => { - anyhow::bail!("expected attribute {REF_ID_ATTRIBUTE_NAME} to be a UTF-8 string") - } - }, - _ => {} +fn get_existing_id(inst: &Instance) -> Option<&str> { + if let Variant::Attributes(attrs) = inst.properties.get("Attributes")? { + let id = attrs.get(REF_ID_ATTRIBUTE_NAME)?; + match id { + Variant::String(str) => Some(str), + Variant::BinaryString(bstr) => match std::str::from_utf8(bstr.as_ref()) { + Ok(str) => Some(str), + Err(_) => None, + }, + _ => None, + } + } else { + None } - let id_string = unique_id - .map(|id| id.to_string()) - .unwrap_or_else(|| referent.to_string()); - attributes.insert( - REF_ID_ATTRIBUTE_NAME.to_string(), - Variant::BinaryString(id_string.clone().into_bytes().into()), - ); - Ok(id_string) } + +/* +When loading IDs we need to create a list and if there's a collision, +cause it to have a stroke. If that works to catch the duplicates then we just +need to re-serialize the IDs that collide. I think it's probably acceptable to +just pick one of the two if we can't tell the difference between them and give +the other a new ID. Maybe we emit a warning. + +This is gonna require a redo of the linking a bit, since it'll mean that we +can't just blindly use the old IDs. The plus side is that it means we can +collect these in a way that isn't awful via mapping an ID to a referent, so +it may end up improving the overall quality of the above code. +*/ diff --git a/src/syncback/snapshot.rs b/src/syncback/snapshot.rs index 5fbdce738..178880931 100644 --- a/src/syncback/snapshot.rs +++ b/src/syncback/snapshot.rs @@ -5,7 +5,6 @@ use std::{ }; use crate::{ - glob::Glob, snapshot::{InstanceWithMeta, RojoTree}, snapshot_middleware::Middleware, Project, @@ -101,13 +100,15 @@ impl<'sync> SyncbackSnapshot<'sync> { } /// Returns a map of properties for an Instance from the 'new' tree - /// with filtering done to avoid noise. Returns `None` only if `new_ref` - /// instance is not in the new tree. + /// with filtering done to avoid noise. This method filters out properties + /// that are not meant to be present in Instances that are represented + /// specially by a path, like `LocalScript.Source` and `StringValue.Value`. /// - /// This method is not necessary or desired for blobs like RBXM or RBXMX. + /// This method is not necessary or desired for blobs like Rbxm or non-path + /// middlewares like JsonModel. #[inline] #[must_use] - pub fn get_filtered_properties( + pub fn get_path_filtered_properties( &self, new_ref: Ref, ) -> Option> { diff --git a/tests/tests/syncback.rs b/tests/tests/syncback.rs index 529cf7e09..b51562953 100644 --- a/tests/tests/syncback.rs +++ b/tests/tests/syncback.rs @@ -24,4 +24,8 @@ syncback_basic_test! { ref_properties_blank, ref_properties_update, ignore_paths, + project_reserialize, + project_all_middleware, + duplicate_rojo_id, + string_value_project, }