diff options
| author | nsfisis <nsfisis@gmail.com> | 2026-06-27 19:44:01 +0900 |
|---|---|---|
| committer | nsfisis <nsfisis@gmail.com> | 2026-06-27 20:18:56 +0900 |
| commit | e63ccb5cbb479df0f0e5010065ff57bbf4ca238f (patch) | |
| tree | f91c7db9b3475e845b023f51cef2d6379e80ae5e /crates | |
| parent | 3cb13f1d7025351615c254b9225a4c5d1bcbd170 (diff) | |
| download | php-shirabe-e63ccb5cbb479df0f0e5010065ff57bbf4ca238f.tar.gz php-shirabe-e63ccb5cbb479df0f0e5010065ff57bbf4ca238f.tar.zst php-shirabe-e63ccb5cbb479df0f0e5010065ff57bbf4ca238f.zip | |
fix(json): encode empty stdClass fields as {} not []
Empty PhpMixed::Array now serializes as [], so the several places that build a
PHP `new \stdClass` via an empty Array were emitting [] where Composer writes
{}. Use PhpMixed::Object for those empty-object cases:
- InitCommand: require / require-dev
- Locker::fixupJsonDataType: stability-flags / platform / platform-dev
- JsonConfigSource fallback: require/config keys that must stay objects
Align the affected test expectations with Composer: JsonFile::read() decodes
with assoc=true, collapsing the on-disk {} back to [], so reads expect [].
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Diffstat (limited to 'crates')
| -rw-r--r-- | crates/shirabe/src/command/init_command.rs | 8 | ||||
| -rw-r--r-- | crates/shirabe/src/config/json_config_source.rs | 9 | ||||
| -rw-r--r-- | crates/shirabe/src/package/locker.rs | 3 | ||||
| -rw-r--r-- | crates/shirabe/tests/command/init_command_test.rs | 33 | ||||
| -rw-r--r-- | crates/shirabe/tests/command/repository_command_test.rs | 6 |
5 files changed, 29 insertions, 30 deletions
diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs index ab0e52c..88a2934 100644 --- a/crates/shirabe/src/command/init_command.rs +++ b/crates/shirabe/src/command/init_command.rs @@ -226,8 +226,8 @@ impl Command for InitCommand { .unwrap_or_default(); let formatted = self.format_requirements(req_list)?; if formatted.is_empty() { - // PHP: new \stdClass — represented as an empty IndexMap (JSON object) - PhpMixed::Array(IndexMap::new()) + // PHP: new \stdClass — empty JSON object + PhpMixed::Object(IndexMap::new()) } else { PhpMixed::Array( formatted @@ -238,7 +238,7 @@ impl Command for InitCommand { } } else { // PHP: new \stdClass - PhpMixed::Array(IndexMap::new()) + PhpMixed::Object(IndexMap::new()) }; options.insert("require".to_string(), require_value); @@ -254,7 +254,7 @@ impl Command for InitCommand { .unwrap_or_default(); let formatted = self.format_requirements(req_list)?; let value = if formatted.is_empty() { - PhpMixed::Array(IndexMap::new()) + PhpMixed::Object(IndexMap::new()) } else { PhpMixed::Array( formatted diff --git a/crates/shirabe/src/config/json_config_source.rs b/crates/shirabe/src/config/json_config_source.rs index 2509239..1ce51ad 100644 --- a/crates/shirabe/src/config/json_config_source.rs +++ b/crates/shirabe/src/config/json_config_source.rs @@ -98,7 +98,7 @@ impl JsonConfigSource { && inner.is_empty() { // PHP: $config[$prop] = new \stdClass; - map.insert(prop.to_string(), PhpMixed::Array(IndexMap::new())); + map.insert(prop.to_string(), PhpMixed::Object(IndexMap::new())); } } for prop in ["psr-0", "psr-4"] { @@ -108,14 +108,15 @@ impl JsonConfigSource { && let Some(PhpMixed::Array(inner_map)) = autoload_map.get(prop) && inner_map.is_empty() { - autoload_map.insert(prop.to_string(), PhpMixed::Array(IndexMap::new())); + autoload_map.insert(prop.to_string(), PhpMixed::Object(IndexMap::new())); } if let Some(autoload_dev) = map.get_mut("autoload-dev") && let PhpMixed::Array(autoload_dev_map) = autoload_dev && let Some(PhpMixed::Array(inner_map)) = autoload_dev_map.get(prop) && inner_map.is_empty() { - autoload_dev_map.insert(prop.to_string(), PhpMixed::Array(IndexMap::new())); + autoload_dev_map + .insert(prop.to_string(), PhpMixed::Object(IndexMap::new())); } } } @@ -136,7 +137,7 @@ impl JsonConfigSource { && let Some(PhpMixed::Array(inner_map)) = cfg_map.get(prop) && inner_map.is_empty() { - cfg_map.insert(prop.to_string(), PhpMixed::Array(IndexMap::new())); + cfg_map.insert(prop.to_string(), PhpMixed::Object(IndexMap::new())); } } self.file.borrow().write(config)?; diff --git a/crates/shirabe/src/package/locker.rs b/crates/shirabe/src/package/locker.rs index df5e1de..1f123f1 100644 --- a/crates/shirabe/src/package/locker.rs +++ b/crates/shirabe/src/package/locker.rs @@ -705,7 +705,8 @@ impl Locker { }) .unwrap_or(false); if should_replace { - lock_data.insert(key.to_string(), PhpMixed::Array(IndexMap::new())); + // PHP: new \stdClass() — empty JSON object + lock_data.insert(key.to_string(), PhpMixed::Object(IndexMap::new())); } } diff --git a/crates/shirabe/tests/command/init_command_test.rs b/crates/shirabe/tests/command/init_command_test.rs index efb3f49..32226f6 100644 --- a/crates/shirabe/tests/command/init_command_test.rs +++ b/crates/shirabe/tests/command/init_command_test.rs @@ -159,9 +159,6 @@ fn test_namespace_from_missing_package_name() { } /// @return iterable<string, array{0: array<string, mixed>, 1: array<string, mixed>}> -/// -/// Empty `require` is `{}` (PHP's `new \stdClass`), not `[]`, to match what the command writes: -/// shirabe's `json_decode` keeps the empty object/array distinction that PHP collapses. fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { vec![ // name argument @@ -169,7 +166,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], }), vec![opt("--name", "test/pkg")], ), @@ -177,7 +174,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { ( serde_json::json!({ "name": "test/pkg", - "require": {}, + "require": [], "authors": [{ "name": "Mr. Test", "email": "test@example.org" }], }), vec![ @@ -189,7 +186,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { ( serde_json::json!({ "name": "test/pkg", - "require": {}, + "require": [], "authors": [{ "name": "Mr. Test" }], }), vec![opt("--name", "test/pkg"), opt("--author", "Mr. Test")], @@ -199,7 +196,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "repositories": [{ "type": "vcs", "url": "http://packages.example.com" }], }), vec![ @@ -215,7 +212,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "repositories": [ { "type": "vcs", "url": "http://vcs.example.com" }, { "type": "composer", "url": "http://composer.example.com" }, @@ -243,7 +240,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "minimum-stability": "dev", }), vec![opt("--name", "test/pkg"), opt("--stability", "dev")], @@ -277,7 +274,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "require-dev": { "first/pkg": "1.0.0" }, }), vec![ @@ -290,7 +287,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "require-dev": { "first/pkg": "1.0.0", "second/pkg": "^3.4" }, }), vec![ @@ -303,7 +300,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "autoload": { "psr-4": { "Test\\Pkg\\": "testMapping/" } }, }), vec![opt("--name", "test/pkg"), opt("--autoload", "testMapping/")], @@ -313,7 +310,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "homepage": "https://example.org/", }), vec![ @@ -326,7 +323,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "description": "My first example package", }), vec![ @@ -339,7 +336,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "type": "project", }), vec![opt("--name", "test/pkg"), opt("--type", "project")], @@ -349,7 +346,7 @@ fn run_data_provider() -> Vec<(serde_json::Value, Vec<(PhpMixed, PhpMixed)>)> { serde_json::json!({ "name": "test/pkg", "authors": [default_authors()], - "require": {}, + "require": [], "license": "MIT", }), vec![opt("--name", "test/pkg"), opt("--license", "MIT")], @@ -510,7 +507,7 @@ fn test_run_guess_name_from_dir_sanitizes_dir() { let expected = serde_json::json!({ "name": "vendor-name/foo-bar_baz.qux", "authors": [default_authors()], - "require": {}, + "require": [], }); assert_eq!( expected, @@ -562,7 +559,7 @@ fn test_interactive_run() { "license": "AGPL-3.0-only", "authors": [{ "name": "Mr. Test", "email": "test@example.org" }], "minimum-stability": "stable", - "require": {}, + "require": [], }); assert_eq!(expected, read_composer_json(&dir)); } diff --git a/crates/shirabe/tests/command/repository_command_test.rs b/crates/shirabe/tests/command/repository_command_test.rs index 1d104e7..901b9f3 100644 --- a/crates/shirabe/tests/command/repository_command_test.rs +++ b/crates/shirabe/tests/command/repository_command_test.rs @@ -34,7 +34,7 @@ fn test_list_with_no_repositories() { app_tester.get_display().trim() ); // composer.json should remain unchanged - assert_eq!(serde_json::json!({}), read_composer_json()); + assert_eq!(serde_json::json!([]), read_composer_json()); drop(tear_down); } @@ -216,7 +216,7 @@ fn test_remove_repository() { if let Some(repositories) = json.get("repositories") { assert_eq!(&serde_json::json!([]), repositories); } else { - assert_eq!(serde_json::json!({}), json); + assert_eq!(serde_json::json!([]), json); } drop(tear_down); @@ -392,7 +392,7 @@ fn test_disable_and_enable_packagist() { ) .unwrap(); assert_eq!(0, status_code); - assert_eq!(serde_json::json!({}), read_composer_json()); + assert_eq!(serde_json::json!([]), read_composer_json()); drop(tear_down); } |
