aboutsummaryrefslogtreecommitdiffhomepage
path: root/crates/shirabe/src/command
diff options
context:
space:
mode:
authornsfisis <nsfisis@gmail.com>2026-06-08 04:22:49 +0900
committernsfisis <nsfisis@gmail.com>2026-06-08 04:23:11 +0900
commit632c9793927a30c4bca3c91888e66b369d732dfe (patch)
tree5bafb9d7fa2523d5a2f8d70ff59c3173e962e389 /crates/shirabe/src/command
parent243450fee9853c2fea66ae0642dabb8db5227d6f (diff)
downloadphp-shirabe-632c9793927a30c4bca3c91888e66b369d732dfe.tar.gz
php-shirabe-632c9793927a30c4bca3c91888e66b369d732dfe.tar.zst
php-shirabe-632c9793927a30c4bca3c91888e66b369d732dfe.zip
feat(phase-c): resolve PhpMixed-conversion phase-b TODOs
Implement the foundational PhpMixed conversion infrastructure (From<bool|i64|f64|String>, order-sensitive PartialEq matching PHP ===) and resolve the category-G phase-b TODOs that depend on it: - Fix VCS driver cache paths that discarded parsed JSON or diverged on null caches (svn/forgejo/gitlab/git-bitbucket/github). - Wire up real conversions previously stubbed or dropped: suggests platform config, audit ignore-severities, composer_repository search and ProviderInfo, class_loader prefix/classmap merges, locker lock diff comparison, advisory JSON serialization, SPDX license fields. - Make GenericRule take a typed ReasonData; populate RULE_ROOT_REQUIRE with the constraint and convert PhpMixed at the call sites.
Diffstat (limited to 'crates/shirabe/src/command')
-rw-r--r--crates/shirabe/src/command/audit_command.rs21
-rw-r--r--crates/shirabe/src/command/base_dependency_command.rs2
-rw-r--r--crates/shirabe/src/command/init_command.rs1
-rw-r--r--crates/shirabe/src/command/search_command.rs5
-rw-r--r--crates/shirabe/src/command/show_command.rs39
-rw-r--r--crates/shirabe/src/command/suggests_command.rs9
-rw-r--r--crates/shirabe/src/command/update_command.rs3
7 files changed, 52 insertions, 28 deletions
diff --git a/crates/shirabe/src/command/audit_command.rs b/crates/shirabe/src/command/audit_command.rs
index 2d25052..20532f1 100644
--- a/crates/shirabe/src/command/audit_command.rs
+++ b/crates/shirabe/src/command/audit_command.rs
@@ -113,10 +113,19 @@ impl AuditCommand {
let abandoned = abandoned.unwrap_or_else(|| audit_config.audit_abandoned.clone());
- let ignore_severities = array_merge(
- array_fill_keys(input.borrow().get_option("ignore-severity"), PhpMixed::Null),
- PhpMixed::from(audit_config.ignore_severity_for_audit.clone()),
- );
+ let mut ignore_severities: indexmap::IndexMap<String, Option<String>> =
+ indexmap::IndexMap::new();
+ let cli_severities = input.borrow().get_option("ignore-severity");
+ if let Some(list) = cli_severities.as_list() {
+ for sev in list {
+ if let Some(s) = sev.as_string() {
+ ignore_severities.insert(s.to_string(), None);
+ }
+ }
+ }
+ for (k, v) in audit_config.ignore_severity_for_audit.clone() {
+ ignore_severities.insert(k, v);
+ }
let ignore_unreachable = input
.borrow()
.get_option("ignore-unreachable")
@@ -125,8 +134,6 @@ impl AuditCommand {
|| audit_config.ignore_unreachable;
let audit_format = self.get_audit_format(input, "format")?;
- // TODO(phase-b): ignore_severities is PhpMixed; need conversion to IndexMap<String, Option<String>>
- let _ = ignore_severities;
Ok(auditor
.audit(
&mut *self.get_io().borrow_mut(),
@@ -136,7 +143,7 @@ impl AuditCommand {
false,
audit_config.ignore_list_for_audit.clone(),
&abandoned,
- indexmap::IndexMap::new(),
+ ignore_severities,
ignore_unreachable,
audit_config.ignore_abandoned_for_audit.clone(),
)?
diff --git a/crates/shirabe/src/command/base_dependency_command.rs b/crates/shirabe/src/command/base_dependency_command.rs
index 1f4d5d4..9317b31 100644
--- a/crates/shirabe/src/command/base_dependency_command.rs
+++ b/crates/shirabe/src/command/base_dependency_command.rs
@@ -379,8 +379,6 @@ pub trait BaseDependencyCommand: BaseCommand {
new_table.extend(table);
table = new_table;
}
- // TODO(phase-b): render_table expects Vec<PhpMixed>; build PhpMixed cells once a
- // converter exists for Vec<String> rows.
let table_as_mixed: Vec<PhpMixed> = table
.into_iter()
.map(|row| {
diff --git a/crates/shirabe/src/command/init_command.rs b/crates/shirabe/src/command/init_command.rs
index f146b30..8e200f0 100644
--- a/crates/shirabe/src/command/init_command.rs
+++ b/crates/shirabe/src/command/init_command.rs
@@ -134,7 +134,6 @@ impl InitCommand {
"license".to_string(),
"autoload".to_string(),
];
- // TODO(phase-b): adapt PhpMixed<->Box<PhpMixed> for array_filter_map
let filtered_input: IndexMap<String, Box<PhpMixed>> = array_intersect_key(
&input.borrow().get_options(),
&array_flip_strings(&allowlist),
diff --git a/crates/shirabe/src/command/search_command.rs b/crates/shirabe/src/command/search_command.rs
index 1cd8e59..b9eacf5 100644
--- a/crates/shirabe/src/command/search_command.rs
+++ b/crates/shirabe/src/command/search_command.rs
@@ -199,7 +199,10 @@ impl SearchCommand {
}
}
} else if format == "json" {
- // TODO(phase-b): JsonFile::encode takes &PhpMixed; convert Vec<SearchResult> into PhpMixed
+ // TODO(phase-c): faithful JSON output requires SearchResult to retain the raw result
+ // array. PHP's fulltext search passes through arbitrary API fields (downloads, favers,
+ // repository, ...) which the typed SearchResult (name/description/abandoned/url) drops,
+ // so encoding it here would diverge from Composer's output.
let _ = &results;
io.write(&JsonFile::encode(&PhpMixed::Null));
}
diff --git a/crates/shirabe/src/command/show_command.rs b/crates/shirabe/src/command/show_command.rs
index 7a2c452..e99005e 100644
--- a/crates/shirabe/src/command/show_command.rs
+++ b/crates/shirabe/src/command/show_command.rs
@@ -1870,12 +1870,20 @@ impl ShowCommand {
let out = match license {
None => license_id.clone(),
Some(license) => {
- // TODO(phase-b): SpdxLicenses returns PhpMixed; field access (osi/fullname/url)
- // is placeholder until PHP array offsets are wired.
- let _ = &license;
- let fullname = String::new();
- let url = String::new();
- let is_osi = false;
+ // SpdxLicenses::getLicenseByIdentifier returns [0 => fullname, 1 => osiApproved, 2 => url].
+ let list = license.as_list();
+ let fullname = list
+ .and_then(|l| l.get(0))
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string();
+ let is_osi =
+ list.and_then(|l| l.get(1)).and_then(|v| v.as_bool()) == Some(true);
+ let url = list
+ .and_then(|l| l.get(2))
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string();
if is_osi {
format!("{} ({}) (OSI approved) {}", fullname, license_id, url)
} else {
@@ -2116,12 +2124,23 @@ impl ShowCommand {
match license {
None => PhpMixed::String(license_id),
Some(l) => {
- // TODO(phase-b): SpdxLicenses returns PhpMixed; field access placeholder.
- let _ = &l;
+ // PHP shape: ['name' => $license[0], 'osi' => $licenseId, 'url' => $license[2]].
+ // Note 'osi' is the license id string, not the OSI-approved flag.
+ let list = l.as_list();
+ let name = list
+ .and_then(|x| x.get(0))
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string();
+ let url = list
+ .and_then(|x| x.get(2))
+ .and_then(|v| v.as_string())
+ .unwrap_or("")
+ .to_string();
let mut m: IndexMap<String, PhpMixed> = IndexMap::new();
- m.insert("name".to_string(), PhpMixed::String(String::new()));
+ m.insert("name".to_string(), PhpMixed::String(name));
m.insert("osi".to_string(), PhpMixed::String(license_id));
- m.insert("url".to_string(), PhpMixed::String(String::new()));
+ m.insert("url".to_string(), PhpMixed::String(url));
PhpMixed::Array(m.into_iter().map(|(k, v)| (k, Box::new(v))).collect())
}
}
diff --git a/crates/shirabe/src/command/suggests_command.rs b/crates/shirabe/src/command/suggests_command.rs
index 5c021b2..b93f7ec 100644
--- a/crates/shirabe/src/command/suggests_command.rs
+++ b/crates/shirabe/src/command/suggests_command.rs
@@ -75,10 +75,11 @@ impl SuggestsCommand {
)?;
installed_repos.push(locked_repo.into());
} else {
- // TODO(phase-b): Config::get returns PhpMixed; need to coerce to IndexMap<String, PhpMixed>
- let _platform_cfg = composer.get_config().borrow().get("platform");
- let platform_overrides: IndexMap<String, PhpMixed> =
- todo!("extract IndexMap<String, PhpMixed> from PhpMixed config value");
+ let platform_cfg = composer.get_config().borrow().get("platform");
+ let platform_overrides: IndexMap<String, PhpMixed> = platform_cfg
+ .as_array()
+ .map(|m| m.iter().map(|(k, v)| (k.clone(), (**v).clone())).collect())
+ .unwrap_or_default();
installed_repos.push(RepositoryInterfaceHandle::new(PlatformRepository::new(
vec![],
platform_overrides,
diff --git a/crates/shirabe/src/command/update_command.rs b/crates/shirabe/src/command/update_command.rs
index f0dc830..4fe9b6a 100644
--- a/crates/shirabe/src/command/update_command.rs
+++ b/crates/shirabe/src/command/update_command.rs
@@ -655,9 +655,6 @@ impl UpdateCommand {
.into());
}
- // TODO(phase-b): IOInterface::select returns PhpMixed and takes
- // Vec<String> choices; convert IndexMap<String, String> autocompleter values
- // to choices and downcast PhpMixed back to Vec<String>.
let select_result = io.select(
"Select packages: (Select more than one value separated by comma) ".to_string(),
autocompleter_values