Repository navigation
Conversation
There was a problem hiding this comment.
Code Review
This pull request refactors the Angular CLI formatting utility to support both oxfmt and prettier as formatters, renaming the utility file and updating imports and tests accordingly. The review feedback suggests improving robustness and performance by caching the resolved formatter path at the module level to avoid redundant disk I/O. Additionally, it points out that the --no-error-on-unmatched-pattern flag should only be passed to prettier, as oxfmt uses strict argument parsing and will fail if an unknown option is provided.
| const workspaceRequire = createRequire(cwd + '/'); | ||
| let formatterCliPath: string | undefined; | ||
| let formatter: string | undefined; | ||
| for (const name of ['oxfmt', 'prettier']) { | ||
| let packageJsonPath: string; | ||
| try { | ||
| const prettierPath = createRequire(cwd + '/').resolve('prettier/package.json'); | ||
| const prettierPackageJson = JSON.parse(await readFile(prettierPath, 'utf-8')) as { | ||
| bin: string; | ||
| }; | ||
| prettierCliPath = join(dirname(prettierPath), prettierPackageJson.bin); | ||
| packageJsonPath = workspaceRequire.resolve(`${name}/package.json`); | ||
| } catch { | ||
| // Prettier is not installed. | ||
| prettierCliPath = null; | ||
| continue; | ||
| } | ||
|
|
||
| const { bin } = JSON.parse(await readFile(packageJsonPath, 'utf-8')) as { | ||
| bin: string | Record<string, string>; | ||
| }; | ||
| formatterCliPath = join(dirname(packageJsonPath), typeof bin === 'string' ? bin : bin[name]); | ||
| formatter = name; | ||
| break; | ||
| } |
There was a problem hiding this comment.
Improve robustness and implement caching when resolving the formatter:
- Wrap the entire resolution, file reading, and JSON parsing inside the
try-catchblock to prevent any unexpected errors (e.g., missing/malformedpackage.jsonor missingbinfield) from crashing the CLI. - Safely check if
binis a string or an object containing the formatter name before callingpath.jointo avoid aTypeErrorifbinorbin[name]is undefined. - Use the module-level
formatterCacheto cache the resolved formatter details.
let cacheEntry = formatterCache.get(cwd);
if (!cacheEntry) {
const workspaceRequire = createRequire(cwd + '/');
let formatterCliPath: string | null = null;
let formatter: string | undefined;
for (const name of ['oxfmt', 'prettier']) {
try {
const packageJsonPath = workspaceRequire.resolve(`${name}/package.json`);
const { bin } = JSON.parse(await readFile(packageJsonPath, 'utf-8')) as {
bin?: string | Record<string, string>;
};
const binPath = typeof bin === 'string' ? bin : bin?.[name];
if (binPath) {
formatterCliPath = join(dirname(packageJsonPath), binPath);
formatter = name;
break;
}
} catch {
continue;
}
}
cacheEntry = { cliPath: formatterCliPath, formatter };
formatterCache.set(cwd, cacheEntry);
}
const { cliPath: formatterCliPath, formatter } = cacheEntry;There was a problem hiding this comment.
Let me know if this should be done
| const baseArgs = [formatterCliPath, '--write', '--no-error-on-unmatched-pattern']; | ||
| if (formatter === 'prettier') { | ||
| baseArgs.push('--ignore-unknown'); | ||
| } |
There was a problem hiding this comment.
Only pass --no-error-on-unmatched-pattern when the formatter is prettier. oxfmt (and other oxc-based tools) use strict argument parsing (via Rust's clap library) and will fail with an error if an unknown option like --no-error-on-unmatched-pattern is passed, preventing any files from being formatted.
| const baseArgs = [formatterCliPath, '--write', '--no-error-on-unmatched-pattern']; | |
| if (formatter === 'prettier') { | |
| baseArgs.push('--ignore-unknown'); | |
| } | |
| const baseArgs = [formatterCliPath, '--write']; | |
| if (formatter === 'prettier') { | |
| baseArgs.push('--no-error-on-unmatched-pattern', '--ignore-unknown'); | |
| } |
There was a problem hiding this comment.
Oxfmt 0.74.0 supports --no-error-on-unmatched-pattern https://oxc.rs/docs/guide/usage/formatter/cli.html#runtime-options
| const execFileAsync = promisify(execFile); | ||
| let prettierCliPath: string | null | undefined; | ||
|
|
There was a problem hiding this comment.
To avoid redundant disk I/O and package resolution when formatFiles is called multiple times (e.g., during multiple migrations in a loop), we should cache the resolved formatter path and name. We can define a cache Map at the module level.
| const execFileAsync = promisify(execFile); | |
| let prettierCliPath: string | null | undefined; | |
| const execFileAsync = promisify(execFile); | |
| const formatterCache = new Map<string, { cliPath: string | null; formatter?: string }>(); |
There was a problem hiding this comment.
Let me know if that should be done
PR Checklist
Please check to confirm your PR fulfills the following requirements:
PR Type
What kind of change does this PR introduce?
What is the current behavior?
The only auto formatting supported out if the box is prettier
What is the new behavior?
If the project has oxfmt installed, then oxfmt is preferred. Prettier is then used as a fallback to preserve the existing behavior.
Does this PR introduce a breaking change?
Other information