diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 6ec5b00e..2dec643f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,6 +25,23 @@ jobs: - name: Run tests with race detector run: go test -race -v ./... + gofmt: + name: gofmt + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 + with: + go-version-file: .go-version + - name: Check gofmt + run: | + unformatted=$(gofmt -l .) + if [ -n "$unformatted" ]; then + echo "The following files are not gofmt'd:" + echo "$unformatted" + exit 1 + fi + test-against-bash: name: Test against Bash (Docker) runs-on: ubuntu-latest diff --git a/AGENTS.md b/AGENTS.md index d34973f2..c6e5ca7d 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,14 @@ The shell is supported on Linux, Windows and macOS. - `README.md` and `SHELL_FEATURES.md` must be kept up to date with the implementation. +## Code Style + +- **All Go files must be formatted with `gofmt` before committing.** Run `gofmt -w .` from the repo root and verify with `gofmt -l .` (no output means clean). CI enforces this and will fail if any file is not properly formatted. + +## Pull Requests + +- **Always open pull requests in draft mode.** Use `gh pr create --draft` (or the GitHub UI's "Draft pull request" option). Only mark a PR ready for review once all CI checks pass and the work is complete. + ## CRITICAL: Bug Fixes and Bash Compatibility - **ALWAYS prioritise fixing the shell implementation to match bash behaviour over changing tests to match the current (incorrect) shell output.** Never "fix" a failing test by updating its expected output to match broken shell behaviour — fix the shell instead. diff --git a/interp/builtins/printf/printf.go b/interp/builtins/printf/printf.go index c7538cee..54b6cb76 100644 --- a/interp/builtins/printf/printf.go +++ b/interp/builtins/printf/printf.go @@ -1049,7 +1049,6 @@ func parseFloatArg(s string) (floatArg, error) { return floatArg{f: val}, nil } - // processBEscapes handles backslash escapes for %b (like echo -e). // Returns the processed string, whether \c was seen (stop all output), // and any warning messages to emit to stderr. diff --git a/interp/builtins/strings_cmd/strings.go b/interp/builtins/strings_cmd/strings.go index bdba91f8..b884e59a 100644 --- a/interp/builtins/strings_cmd/strings.go +++ b/interp/builtins/strings_cmd/strings.go @@ -91,7 +91,7 @@ const ( // radixFlagVal implements pflag.Value for the -t / --radix flag. // Validation happens in Set so pflag reports errors during parsing, which also -// correctly rejects empty values (e.g. --radix= or -t ''). +// correctly rejects empty values (e.g. --radix= or -t ”). type radixFlagVal struct{ target *radixFormat } func (r *radixFlagVal) String() string { diff --git a/interp/builtins/tail/tail.go b/interp/builtins/tail/tail.go index b7f227f6..35d231e3 100644 --- a/interp/builtins/tail/tail.go +++ b/interp/builtins/tail/tail.go @@ -120,15 +120,15 @@ type countMode struct { // returns a bound handler whose flag variables are captured by closure. The // framework calls Parse and passes positional arguments to the handler. func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc { - help := fs.BoolP("help", "h", false, "print usage and exit") + help := fs.BoolP("help", "h", false, "print usage and exit") zeroTerminated := fs.BoolP("zero-terminated", "z", false, "use NUL as line delimiter") // quietFlag, silentFlag, and verboseFlag share a sequence counter so that // after parsing we can tell which appeared last on the command line and // apply last-flag-wins semantics (e.g. "-q -v" should show headers). var headerSeq int - quietFlag := newHeaderFlag(&headerSeq) - silentFlag := newHeaderFlag(&headerSeq) + quietFlag := newHeaderFlag(&headerSeq) + silentFlag := newHeaderFlag(&headerSeq) verboseFlag := newHeaderFlag(&headerSeq) fs.VarP(quietFlag, "quiet", "q", "never print file name headers") fs.Var(silentFlag, "silent", "alias for --quiet") @@ -162,10 +162,10 @@ func registerFlags(fs *builtins.FlagSet) builtins.HandlerFunc { // Bytes mode wins if -c/--bytes was parsed after -n/--lines. useBytesMode := bytesFlag.pos > linesFlag.pos - countStr := linesFlag.val + countStr := linesFlag.val modeLabel := "lines" if useBytesMode { - countStr = bytesFlag.val + countStr = bytesFlag.val modeLabel = "bytes" } diff --git a/interp/builtins/wc/wc.go b/interp/builtins/wc/wc.go index 1b71418b..7fbf50ee 100644 --- a/interp/builtins/wc/wc.go +++ b/interp/builtins/wc/wc.go @@ -259,8 +259,8 @@ func countReader(ctx context.Context, r io.Reader) (counts, error) { } c.chars += int64(utf8.RuneCount(chunk)) // carryN bytes are subtracted here and will be re-added via - // n += carryN at the top of the next iteration. - c.bytes -= int64(carryN) + // n += carryN at the top of the next iteration. + c.bytes -= int64(carryN) for i := 0; i < len(chunk); { r, size := utf8.DecodeRune(chunk[i:]) @@ -353,25 +353,25 @@ func runeWidth(r rune) int { // codepoints per UAX #11, matching the ranges used by wcwidth(3). var eastAsianWide = &unicode.RangeTable{ R16: []unicode.Range16{ - {0x1100, 0x115F, 1}, // Hangul Jamo initials - {0x2329, 0x232A, 1}, // CJK angle brackets - {0x2E80, 0x303E, 1}, // CJK Radicals Supplement .. CJK Symbols - {0x3040, 0x33BF, 1}, // Hiragana .. CJK Compatibility - {0x33C0, 0x33FF, 1}, // CJK Compatibility (cont.) - {0x3400, 0x4DBF, 1}, // CJK Unified Ideographs Extension A - {0x4E00, 0xA4CF, 1}, // CJK Unified Ideographs .. Yi - {0xAC00, 0xD7A3, 1}, // Hangul Syllables - {0xF900, 0xFAFF, 1}, // CJK Compatibility Ideographs - {0xFE10, 0xFE19, 1}, // Vertical Forms - {0xFE30, 0xFE6F, 1}, // CJK Compatibility Forms + Small Form Variants - {0xFF01, 0xFF60, 1}, // Fullwidth Forms - {0xFFE0, 0xFFE6, 1}, // Fullwidth Signs + {0x1100, 0x115F, 1}, // Hangul Jamo initials + {0x2329, 0x232A, 1}, // CJK angle brackets + {0x2E80, 0x303E, 1}, // CJK Radicals Supplement .. CJK Symbols + {0x3040, 0x33BF, 1}, // Hiragana .. CJK Compatibility + {0x33C0, 0x33FF, 1}, // CJK Compatibility (cont.) + {0x3400, 0x4DBF, 1}, // CJK Unified Ideographs Extension A + {0x4E00, 0xA4CF, 1}, // CJK Unified Ideographs .. Yi + {0xAC00, 0xD7A3, 1}, // Hangul Syllables + {0xF900, 0xFAFF, 1}, // CJK Compatibility Ideographs + {0xFE10, 0xFE19, 1}, // Vertical Forms + {0xFE30, 0xFE6F, 1}, // CJK Compatibility Forms + Small Form Variants + {0xFF01, 0xFF60, 1}, // Fullwidth Forms + {0xFFE0, 0xFFE6, 1}, // Fullwidth Signs }, R32: []unicode.Range32{ - {0x1F300, 0x1F64F, 1}, // Misc Symbols/Pictographs + Emoticons - {0x1F900, 0x1F9FF, 1}, // Supplemental Symbols and Pictographs - {0x20000, 0x2FFFD, 1}, // CJK Extension B..F - {0x30000, 0x3FFFD, 1}, // CJK Extension G+ + {0x1F300, 0x1F64F, 1}, // Misc Symbols/Pictographs + Emoticons + {0x1F900, 0x1F9FF, 1}, // Supplemental Symbols and Pictographs + {0x20000, 0x2FFFD, 1}, // CJK Extension B..F + {0x30000, 0x3FFFD, 1}, // CJK Extension G+ }, } diff --git a/interp/builtins/wc/wc_test.go b/interp/builtins/wc/wc_test.go index 4707b0dd..dd2e3d20 100644 --- a/interp/builtins/wc/wc_test.go +++ b/interp/builtins/wc/wc_test.go @@ -451,4 +451,3 @@ func TestWcChunkBoundaryMultibyte(t *testing.T) { // max line length: 32766 + 2 (emoji display width) = 32768 assert.Equal(t, "32768 32768 file.txt\n", stdout) } - diff --git a/interp/register_builtins.go b/interp/register_builtins.go index a86488a6..1a360bbc 100644 --- a/interp/register_builtins.go +++ b/interp/register_builtins.go @@ -11,8 +11,8 @@ import ( "github.com/DataDog/rshell/interp/builtins" breakcmd "github.com/DataDog/rshell/interp/builtins/break" "github.com/DataDog/rshell/interp/builtins/cat" - "github.com/DataDog/rshell/interp/builtins/cut" continuecmd "github.com/DataDog/rshell/interp/builtins/continue" + "github.com/DataDog/rshell/interp/builtins/cut" "github.com/DataDog/rshell/interp/builtins/echo" "github.com/DataDog/rshell/interp/builtins/exit" falsecmd "github.com/DataDog/rshell/interp/builtins/false" diff --git a/tests/compliance_test.go b/tests/compliance_test.go index 037a0008..e2e62683 100644 --- a/tests/compliance_test.go +++ b/tests/compliance_test.go @@ -156,11 +156,11 @@ func TestComplianceLicense3rdPartyFormat(t *testing.T) { // Known SPDX identifiers (extend as needed). knownSPDX := map[string]bool{ "MIT": true, - "Apache-2.0": true, - "BSD-2-Clause": true, - "BSD-3-Clause": true, - "ISC": true, - "MPL-2.0": true, + "Apache-2.0": true, + "BSD-2-Clause": true, + "BSD-3-Clause": true, + "ISC": true, + "MPL-2.0": true, "MIT AND Apache-2.0": true, } diff --git a/tests/scenarios_test.go b/tests/scenarios_test.go index e3240c32..58652141 100644 --- a/tests/scenarios_test.go +++ b/tests/scenarios_test.go @@ -31,7 +31,7 @@ const dockerBashImage = "debian:bookworm-slim" // scenario represents a single test scenario. type scenario struct { Description string `yaml:"description"` - SkipAssertAgainstBash bool `yaml:"skip_assert_against_bash"` // true = skip bash comparison + SkipAssertAgainstBash bool `yaml:"skip_assert_against_bash"` // true = skip bash comparison Setup setup `yaml:"setup"` Input input `yaml:"input"` Expect expected `yaml:"expect"`