Conversation
6d49dbd to
d692118
Compare
There was a problem hiding this comment.
Pull request overview
This PR implements the --init flag for oxfmt, allowing users to initialize a default configuration file (.oxfmtrc.jsonc) in their project. The implementation follows a similar pattern to oxlint's --init functionality, including automatic detection and inclusion of JSON schema references when available.
Key Changes
- Added
--initcommand-line flag that creates.oxfmtrc.jsoncwith default configuration - New
ConfigFileInitSucceededandConfigFileInitFailedresult variants to handle initialization outcomes - Automatic
$schemareference injection whennode_modules/oxfmt/configuration_schema.jsonexists
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
| apps/oxfmt/src/cli/command.rs | Adds the init boolean flag to BasicOptions struct with hide_usage attribute |
| apps/oxfmt/src/cli/result.rs | Adds two new enum variants for config initialization success/failure states and maps them to appropriate exit codes |
| apps/oxfmt/src/cli/format.rs | Implements handle_init method to create default config file, includes early exit logic in run method, and adds comprehensive tests for both schema and non-schema scenarios |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| let config_path = cwd.join(Self::DEFAULT_OXFMTRC); | ||
| match fs::write(&config_path, configuration) { | ||
| Ok(()) => { | ||
| let _ = writeln!(stdout, "Configuration file created"); |
There was a problem hiding this comment.
The success message should include the filename that was created for better user feedback. For example: "Configuration file created at .oxfmtrc.jsonc". This is consistent with oxlint's implementation and provides clearer information to the user.
| let _ = writeln!(stdout, "Configuration file created"); | |
| let _ = writeln!(stdout, "Configuration file created at {}", config_path.display()); |
| #[test] | ||
| fn test_init_config() { | ||
| let temp_dir = env::temp_dir().join("oxfmt_init_test"); | ||
| fs::create_dir_all(&temp_dir).unwrap(); | ||
|
|
||
| let config_path = temp_dir.join(FormatRunner::DEFAULT_OXFMTRC); | ||
| let _ = fs::remove_file(&config_path); | ||
|
|
||
| let mut stdout = Vec::new(); | ||
| let mut stderr = Vec::new(); | ||
| let result = FormatRunner::handle_init(&temp_dir, &mut stdout, &mut stderr); | ||
|
|
||
| assert!(matches!(result, CliRunResult::ConfigFileInitSucceeded)); | ||
| assert!(config_path.exists()); | ||
|
|
||
| // Verify valid JSON | ||
| let content = fs::read_to_string(&config_path).unwrap(); | ||
| let _: serde_json::Value = serde_json::from_str(&content).unwrap(); | ||
|
|
||
| // Cleanup | ||
| fs::remove_file(&config_path).unwrap(); | ||
| fs::remove_dir(&temp_dir).unwrap(); | ||
| } |
There was a problem hiding this comment.
Missing test coverage for the case when a configuration file already exists. Consider adding a test that verifies the behavior when --init is run and .oxfmtrc.jsonc already exists (e.g., whether it overwrites, fails, or prompts).
| Self::InvalidOptionConfig | Self::FormatMismatch | Self::ConfigFileInitFailed => { | ||
| ExitCode::from(1) | ||
| } | ||
| Self::NoFilesFound | Self::FormatFailed => ExitCode::from(2), |
There was a problem hiding this comment.
ConfigFileInitFailed is categorized under "Fatal error" in the comment but returns exit code 1 (warning error) in the implementation. This is inconsistent. If it's truly a fatal error, it should return exit code 2 like other fatal errors (NoFilesFound, FormatFailed). Otherwise, the comment should categorize it as a warning error.
| Self::InvalidOptionConfig | Self::FormatMismatch | Self::ConfigFileInitFailed => { | |
| ExitCode::from(1) | |
| } | |
| Self::NoFilesFound | Self::FormatFailed => ExitCode::from(2), | |
| Self::InvalidOptionConfig | Self::FormatMismatch => { | |
| ExitCode::from(1) | |
| } | |
| Self::NoFilesFound | Self::FormatFailed | Self::ConfigFileInitFailed => ExitCode::from(2), |
| config_string | ||
| }; | ||
|
|
||
| // Write to .oxfmtrc.json |
There was a problem hiding this comment.
The comment says "Write to .oxfmtrc.json" but the actual filename constant is .oxfmtrc.jsonc (with 'c' suffix). The comment should be updated to match the actual filename being used.
| // Write to .oxfmtrc.json | |
| // Write to .oxfmtrc.jsonc |
| // Write to .oxfmtrc.json | ||
| let config_path = cwd.join(Self::DEFAULT_OXFMTRC); | ||
| match fs::write(&config_path, configuration) { | ||
| Ok(()) => { | ||
| let _ = writeln!(stdout, "Configuration file created"); | ||
| CliRunResult::ConfigFileInitSucceeded | ||
| } | ||
| Err(e) => { | ||
| let _ = writeln!(stderr, "Failed to create configuration file: {e}"); | ||
| CliRunResult::ConfigFileInitFailed | ||
| } | ||
| } |
There was a problem hiding this comment.
The --init flag should check if the configuration file already exists before writing to prevent silently overwriting existing user configurations. Consider warning the user or failing gracefully if the file exists. Compare with common tools like npm init which prompts for confirmation or uses a --force flag for overwriting.
| let mut config_json: Value = serde_json::from_str(&config_string).unwrap(); | ||
| if let Value::Object(ref mut obj) = config_json { | ||
| let mut json_object = serde_json::Map::new(); | ||
| json_object | ||
| .insert("$schema".to_string(), format!("./{schema_relative_path}").into()); | ||
| json_object.extend(obj.clone()); | ||
| *obj = json_object; | ||
| } | ||
| serde_json::to_string_pretty(&config_json).unwrap() |
There was a problem hiding this comment.
The .unwrap() calls on lines 272 and 280 could panic if JSON serialization/deserialization fails unexpectedly. While unlikely since we just successfully serialized the config, it's safer to handle these errors explicitly and return CliRunResult::ConfigFileInitFailed with an appropriate error message to stderr.
| // Write to .oxfmtrc.json | ||
| let config_path = cwd.join(Self::DEFAULT_OXFMTRC); | ||
| match fs::write(&config_path, configuration) { | ||
| Ok(()) => { | ||
| let _ = writeln!(stdout, "Configuration file created"); | ||
| CliRunResult::ConfigFileInitSucceeded | ||
| } | ||
| Err(e) => { | ||
| let _ = writeln!(stderr, "Failed to create configuration file: {e}"); | ||
| CliRunResult::ConfigFileInitFailed | ||
| } | ||
| } |
There was a problem hiding this comment.
The code does not check if the configuration file already exists before writing to it. This will silently overwrite any existing .oxfmtrc.jsonc file, potentially causing data loss for users who already have a configuration.
// Check if config file already exists
let config_path = cwd.join(Self::DEFAULT_OXFMTRC);
if config_path.exists() {
let _ = writeln!(stderr, "Configuration file already exists at {}", config_path.display());
return CliRunResult::ConfigFileInitFailed;
}
match fs::write(&config_path, configuration) {
// ...
}| // Write to .oxfmtrc.json | |
| let config_path = cwd.join(Self::DEFAULT_OXFMTRC); | |
| match fs::write(&config_path, configuration) { | |
| Ok(()) => { | |
| let _ = writeln!(stdout, "Configuration file created"); | |
| CliRunResult::ConfigFileInitSucceeded | |
| } | |
| Err(e) => { | |
| let _ = writeln!(stderr, "Failed to create configuration file: {e}"); | |
| CliRunResult::ConfigFileInitFailed | |
| } | |
| } | |
| // Write to .oxfmtrc.json | |
| let config_path = cwd.join(Self::DEFAULT_OXFMTRC); | |
| // Check if config file already exists | |
| if config_path.exists() { | |
| let _ = writeln!(stderr, "Configuration file already exists at {}", config_path.display()); | |
| return CliRunResult::ConfigFileInitFailed; | |
| } | |
| match fs::write(&config_path, configuration) { | |
| Ok(()) => { | |
| let _ = writeln!(stdout, "Configuration file created"); | |
| CliRunResult::ConfigFileInitSucceeded | |
| } | |
| Err(e) => { | |
| let _ = writeln!(stderr, "Failed to create configuration file: {e}"); | |
| CliRunResult::ConfigFileInitFailed | |
| } | |
| } |
Spotted by Graphite Agent
Is this helpful? React 👍 or 👎 to let us know.
|
The TODO has been finalized #16622, so I'll close this PR and redo it. |
closes #16622