diff --git a/test/lib/LibCodeGen.commentPrefix.t.sol b/test/lib/LibCodeGen.commentPrefix.t.sol new file mode 100644 index 0000000..73580df --- /dev/null +++ b/test/lib/LibCodeGen.commentPrefix.t.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {LibCodeGen} from "src/lib/LibCodeGen.sol"; +import {LibCodeGenSlow} from "./LibCodeGenSlow.sol"; + +/// @title LibCodeGenCommentPrefixTest +/// @notice `commentPrefix` is the one rule every constant declaration shares: +/// a blank line always, and a comment line only when there is a comment. The +/// library states it as a choice between two whole prefix literals, so these +/// assert it against a reference that assembles the prefix a line at a time, +/// and pin that reference's own two cases to literals so agreement between the +/// two is not the only thing holding either. +contract LibCodeGenCommentPrefixTest is Test { + /// No comment is one blank line and nothing else. Two consecutive newlines + /// here would be a blank line `forge fmt` collapses, which makes a + /// regenerated file fail the consumer's `forge fmt --check`. + function testCommentPrefixSlowEmptyComment() external pure { + assertEq(LibCodeGenSlow.commentPrefixSlow(""), "\n"); + } + + /// A comment is a blank line, then the comment on a line of its own. The + /// comment is emitted as given, so the caller owns whether it carries + /// `///` and where it breaks. + function testCommentPrefixSlowWithComment() external pure { + assertEq(LibCodeGenSlow.commentPrefixSlow("/// @dev Some comment."), "\n/// @dev Some comment.\n"); + } + + /// Whatever the comment, the library emits the lines the reference + /// assembles. Fuzzed over the comment because emptiness is the only thing + /// the rule branches on and every other comment has to come back untouched. + function testCommentPrefixMatchesSlow(string memory comment) external pure { + assertEq(LibCodeGen.commentPrefix(comment), LibCodeGenSlow.commentPrefixSlow(comment)); + } +} diff --git a/test/lib/LibCodeGenSlow.sol b/test/lib/LibCodeGenSlow.sol index bd11101..d097e5d 100644 --- a/test/lib/LibCodeGenSlow.sol +++ b/test/lib/LibCodeGenSlow.sol @@ -34,11 +34,10 @@ string constant SLOW_TAIL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnop /// than imported, so a change to a literal in `LibCodeGen` shows up as a /// disagreement instead of moving both sides at once. /// -/// The empty comment case is the one place the two sides state the same rule -/// rather than deriving it independently: a declaration is preceded by one -/// blank line, and by a comment line only when there is a comment. The exact -/// text of both cases is pinned separately by literal assertions, so this -/// reference is not the only thing holding it. +/// The lines that precede a declaration are assembled here from the rule they +/// follow: one blank line always, and a comment line only when there is a +/// comment. The exact text of both cases is pinned separately by literal +/// assertions, so this reference is not the only thing holding it. library LibCodeGenSlow { /// `vm.toString` on a `bytes` always prefixes `0x`, which a `hex"..."` /// literal must not carry. Dropped by copying the tail one byte at a time so @@ -63,13 +62,31 @@ library LibCodeGenSlow { return oneLine; } + /// The lines that precede a declaration, listed out and then joined one + /// newline terminated line at a time: a blank line always, and a comment + /// line only when there is a comment. `LibCodeGen.commentPrefix` instead + /// chooses between two whole prefix literals, so the two agree only when + /// both the number of lines and the text of each one is right. + function commentPrefixSlow(string memory comment) internal pure returns (string memory) { + string[] memory lines = new string[](bytes(comment).length == 0 ? 1 : 2); + lines[0] = ""; + if (lines.length == 2) { + lines[1] = comment; + } + string memory prefix = ""; + for (uint256 i = 0; i < lines.length; i++) { + prefix = string.concat(prefix, lines[i], "\n"); + } + return prefix; + } + function bytesConstantStringSlow(Vm vm, string memory comment, string memory name, bytes memory data) internal pure returns (string memory) { return string.concat( - bytes(comment).length == 0 ? "\n" : string.concat("\n", comment, "\n"), + commentPrefixSlow(comment), joinSlow(string.concat("bytes constant ", name, " ="), string.concat("hex\"", hexOfSlow(vm, data), "\";")), "\n" ); @@ -81,7 +98,7 @@ library LibCodeGenSlow { returns (string memory) { return string.concat( - bytes(comment).length == 0 ? "\n" : string.concat("\n", comment, "\n"), + commentPrefixSlow(comment), joinSlow(string.concat("uint8 constant ", name, " ="), string.concat(vm.toString(uint256(data)), ";")), "\n" ); @@ -93,7 +110,7 @@ library LibCodeGenSlow { returns (string memory) { return string.concat( - bytes(comment).length == 0 ? "\n" : string.concat("\n", comment, "\n"), + commentPrefixSlow(comment), joinSlow( string.concat("bytes32 constant ", name, " ="), string.concat("bytes32(", vm.toString(data), ");") ), @@ -107,7 +124,7 @@ library LibCodeGenSlow { returns (string memory) { return string.concat( - bytes(comment).length == 0 ? "\n" : string.concat("\n", comment, "\n"), + commentPrefixSlow(comment), joinSlow( string.concat("address constant ", name, " ="), string.concat("address(", vm.toString(data), ");") ),