Description
Component
Forge
Have you ensured that all of these are up to date?
- Foundry
- Foundryup
What version of Foundry are you on?
master
What command(s) is the bug in?
forge fmt
Operating System
Linux
Describe the bug
Imports sorting sometimes adds empty lines in the middle of a group. Repro:
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol"`;
Output:
import { AccessControl } from "@openzeppelin/contracts/access/AccessControl.sol";
import { ERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import { ERC20Burnable } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Burnable.sol";
import { ERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/ERC20Permit.sol";
import { IERC20Permit } from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Permit.sol";
As you can see, there is an empty line before the last element.
I dug a bit into the formatter code and I noticed that the Loc::File
offsets are not updated when re-ordering the SourceUnitPart
s in:
foundry/crates/fmt/src/formatter.rs
Lines 1696 to 1705 in 467aff3
Since the rest of the formatter makes heavy use of those locations, I assumed they are important.
I attempted a dirty fix by re-writing the Loc
start and end offsets according to the new ordering and that seems to fix the bug described above, but my dirty fix does not take into account the spacing between the various SourceUnitPart
s (due to semicolon and line break, or a potential comment on the same line).
// Before sorting
let group_start_loc = import_directives[0].loc();
// After sorting
let mut offset = group_start_loc.start();
for source_unit_part in import_directives.iter_mut() {
let loc = source_unit_part.loc();
let len = loc.end() - loc.start();
match source_unit_part {
SourceUnitPart::ImportDirective(import) => match import {
Import::Plain(_, loc) => {
*loc = loc.with_start(offset).with_end(offset + len);
}
Import::GlobalSymbol(_, _, loc) => {
*loc = loc.with_start(offset).with_end(offset + len);
}
Import::Rename(_, _, loc) => {
*loc = loc.with_start(offset).with_end(offset + len);
}
},
_ => {
unreachable!("import group contains non-import statement")
}
}
offset += len;
}
Maybe someone smarter can create a better fix? Also, I haven't even attempted to update the location of the renames in the Import::Rename
variant, but that should also probably be done?