From 63d1e797e2fc7b9772e089cd64eaec9385def46d Mon Sep 17 00:00:00 2001 From: sneak Date: Fri, 7 Aug 2026 20:45:02 +0000 Subject: [PATCH] chore(lint): adopt canonical golangci-lint config Replace .golangci.yml with the shared canonical config. The old config's top-level linters-settings block was silently ignored under the v2 schema, so the lll/funlen/cyclop/dupl thresholds now actually apply. The four repo-specific disables (mnd, exhaustive, paralleltest, testpackage) move out of the config into targeted in-code nolint directives carrying their original approval dates, keeping the config byte-identical to the canonical one. Fixes surfaced by the stricter settings: t.Parallel() added to all 32 tests, 24 overlong lines wrapped or their comments tightened, tcell control-code returns rewritten as character literals, dupl markers on the identically-shaped item data tables, and two wsl_v5 defer cuddles. No behavior changes. The repo has no golangci-lint version pin (no Dockerfile or CI; make lint runs the host binary, currently v2.12.2), so there was nothing to bump. --- .golangci.yml | 32 ++++++++++++++------------------ TODO.md | 13 +++++++++++++ cmd/rogue/main.go | 1 + game/chase.go | 5 ++++- game/command.go | 6 +++++- game/creature.go | 22 ++++++++++++---------- game/daemons.go | 1 + game/dice_test.go | 5 +++++ game/effects_test.go | 14 ++++++++++++++ game/fight.go | 1 + game/fight_test.go | 11 +++++++++++ game/game.go | 1 + game/init.go | 4 +++- game/io.go | 1 + game/level.go | 1 + game/misc.go | 9 +++++++-- game/monsters.go | 1 + game/move.go | 8 ++++++-- game/newlevel.go | 1 + game/newlevel_test.go | 7 +++++++ game/object.go | 3 +++ game/options.go | 2 ++ game/pack.go | 9 +++++++-- game/passages.go | 1 + game/potions.go | 2 ++ game/rings.go | 2 ++ game/rip.go | 8 ++++++-- game/rng.go | 1 + game/rng_test.go | 9 +++++++++ game/rooms.go | 5 ++++- game/run_test.go | 7 +++++++ game/save.go | 2 ++ game/save_test.go | 5 +++++ game/score.go | 2 ++ game/scrolls.go | 4 +++- game/seedcompat_test.go | 3 +++ game/sticks.go | 7 ++++++- game/tables.go | 12 ++++++++++-- game/tables_test.go | 9 +++++++++ game/term_test.go | 1 + game/things.go | 7 ++++++- game/types.go | 12 ++++++------ game/weapons.go | 1 + game/wizard.go | 4 ++++ term/tcell.go | 12 +++++++----- 45 files changed, 218 insertions(+), 56 deletions(-) diff --git a/.golangci.yml b/.golangci.yml index 1add10f..26b1610 100644 --- a/.golangci.yml +++ b/.golangci.yml @@ -1,5 +1,9 @@ version: "2" +# Config schema uses the golangci-lint v2 layout (settings live under +# linters.settings, not top-level linters-settings) so that the +# thresholds below are actually applied by golangci-lint >= v2. + run: timeout: 5m modules-download-mode: readonly @@ -14,25 +18,17 @@ linters: - wsl # Deprecated, replaced by wsl_v5 - wrapcheck # Too verbose for internal packages - varnamelen # Short names like db, id are idiomatic Go - # Repo-specific exceptions approved by sneak (2026-07-06) - - paralleltest # Requires t.Parallel() in every test - # Approved by sneak 2026-07-07 - - testpackage # Tests use internal package game to reach unexported state - - exhaustive # C-faithful switches handle only the cases C handled - - mnd # C-faithful gameplay literals; naming them hurts C-greppability - -linters-settings: - lll: - line-length: 88 - funlen: - lines: 80 - statements: 50 - cyclop: - max-complexity: 15 - dupl: - threshold: 100 + settings: + lll: + line-length: 88 + funlen: + lines: 80 + statements: 50 + cyclop: + max-complexity: 15 + dupl: + threshold: 100 issues: - exclude-use-default: false max-issues-per-linter: 0 max-same-issues: 0 diff --git a/TODO.md b/TODO.md index 7c04da2..e99d21b 100644 --- a/TODO.md +++ b/TODO.md @@ -34,6 +34,19 @@ wizard commands). # Completed Steps +- 2026-08-07 Canonical linter config (`golangci-v2.12.2`): replaced + `.golangci.yml` with the shared canonical config (v2 schema; settings now live + under `linters.settings`, so the `lll`/`funlen`/`cyclop`/`dupl` thresholds + actually apply — the old top-level `linters-settings` block was silently + ignored). The four repo-specific disables (`mnd`, `exhaustive`, + `paralleltest`, `testpackage`) moved out of the config into targeted in-code + `//nolint` directives carrying the original approval dates, so the config + stays byte-identical to canonical. Real fixes: `t.Parallel()` in all 32 tests, + 24 long lines wrapped or their comments tightened, control bytes in + `term/tcell.go` as character literals, and two `wsl_v5` defer cuddles. The + repo has no golangci-lint version pin to bump (no Dockerfile or CI; + `make lint` runs the host `golangci-lint`, currently v2.12.2). + - 2026-07-24 Seed compatibility — item tables (seed-compat): instrumented the C reference on modern-rogue with a DUMP mode (testdata/c_seedcompat.patch) that forces the RNG seed and prints the per-seed item appearance tables (potion diff --git a/cmd/rogue/main.go b/cmd/rogue/main.go index bd5e6f9..268c9aa 100644 --- a/cmd/rogue/main.go +++ b/cmd/rogue/main.go @@ -128,6 +128,7 @@ func chooseSeed(wizard bool) int32 { // The C game computed `lowtime + getpid()` in int; the truncation to // 32 bits is the same wraparound the C int arithmetic performed. + //nolint:mnd // C-faithful: the C int wraparound mask return int32(time.Now().Unix()&0x7fffffff) + int32(os.Getpid()&0x7fffffff) } diff --git a/game/chase.go b/game/chase.go index b9516cc..bad46a2 100644 --- a/game/chase.go +++ b/game/chase.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // chase.c — code for one creature to chase another. @@ -157,7 +158,9 @@ func (g *RogueGame) chaseRooms(th *Monster) (*Room, *Room, bool) { // nearest exit toward its desire when it is in a different room, or the // desire itself. shot means a dragon breathed flame instead of moving // (the goal loop of chase.c do_chase). -func (g *RogueGame) chaseGoal(th *Monster, rer, ree *Room, door bool, mindist int) (Coord, bool) { +func (g *RogueGame) chaseGoal( + th *Monster, rer, ree *Room, door bool, mindist int, +) (Coord, bool) { var this Coord for { diff --git a/game/command.go b/game/command.go index 4e82a07..e21690b 100644 --- a/game/command.go +++ b/game/command.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // command.c — read and execute the user commands. @@ -794,6 +795,7 @@ func (g *RogueGame) call() { func (g *RogueGame) callTarget(obj *Object) (*ObjInfo, string, *string, bool) { it := &g.Items + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.Kind { case KindRing: return &it.Rings[obj.Which], it.RingStones[obj.Which], nil, true @@ -815,7 +817,9 @@ func (g *RogueGame) callTarget(obj *Object) (*ObjInfo, string, *string, bool) { // callPrelude resolves the item's current name, reporting a previous // guess; ok is false when the item is already identified (command.c // call). -func (g *RogueGame) callPrelude(op *ObjInfo, elsewise string, guess *string) (string, *string, bool) { +func (g *RogueGame) callPrelude( + op *ObjInfo, elsewise string, guess *string, +) (string, *string, bool) { fromGuess := false if op != nil { diff --git a/game/creature.go b/game/creature.go index 5388014..00421a3 100644 --- a/game/creature.go +++ b/game/creature.go @@ -2,16 +2,18 @@ package game // Creature is the _t arm of the C THING union: the player or a monster. type Creature struct { - Pos Coord // position - Turn bool // if slowed, is it a turn to move - Type byte // what it is: 'A'..'Z' for monsters, '@' for the player - Disguise byte // what mimic looks like - OldCh byte // character that was where it was - Dest *Coord // where it is running to — aliases live coords (hero pos, room gold, another monster's pos) - Flags CreatureFlags - Stats Stats - Room *Room // current room for thing - Pack []*Object // what the thing is carrying + Pos Coord // position + Turn bool // if slowed, is it a turn to move + Type byte // what it is: 'A'..'Z' for monsters, '@' for the player + Disguise byte // what mimic looks like + OldCh byte // character that was where it was + // Dest is where it is running to — aliases live coords (hero pos, + // room gold, another monster's pos). + Dest *Coord + Flags CreatureFlags + Stats Stats + Room *Room // current room for thing + Pack []*Object // what the thing is carrying } // Monster is a hostile creature on the level. diff --git a/game/daemons.go b/game/daemons.go index 4909706..530551d 100644 --- a/game/daemons.go +++ b/game/daemons.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // daemons.c — the daemon and fuse callbacks, dispatched by DaemonID. diff --git a/game/dice_test.go b/game/dice_test.go index bca6e9c..cee7a1f 100644 --- a/game/dice_test.go +++ b/game/dice_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import "testing" @@ -6,6 +7,8 @@ import "testing" // including its junk-tolerant edges: the bestiary placeholder "%%%x0" and // the flytrap reset "000x0" both mean a single 0x0 attack. func TestParseDice(t *testing.T) { + t.Parallel() + cases := []struct { in string want string @@ -32,6 +35,8 @@ func TestParseDice(t *testing.T) { // The bestiary and weapon tables must parse to at least one attack each so // every creature and weapon actually swings. func TestTablesHaveDice(t *testing.T) { + t.Parallel() + data := newGameData() for i, m := range data.monsterTable { if len(m.Stats.Dmg) == 0 { diff --git a/game/effects_test.go b/game/effects_test.go index 3f6631f..6c054b2 100644 --- a/game/effects_test.go +++ b/game/effects_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import "testing" @@ -37,6 +38,8 @@ func setInput(t *testing.T, g *RogueGame, input ...byte) { } func TestQuaffHealingPotion(t *testing.T) { + t.Parallel() + g := mkGameInput(t) pot := newObject() pot.Kind = KindPotion @@ -61,6 +64,8 @@ func TestQuaffHealingPotion(t *testing.T) { } func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) { + t.Parallel() + g := mkGameInput(t) pot := newObject() pot.Kind = KindPotion @@ -88,6 +93,8 @@ func TestQuaffConfusionSetsFlagAndFuse(t *testing.T) { } func TestReadEnchantArmor(t *testing.T) { + t.Parallel() + g := mkGameInput(t) scr := newObject() scr.Kind = KindScroll @@ -105,6 +112,7 @@ func TestReadEnchantArmor(t *testing.T) { } func TestReadHoldMonsterFreezesAdjacent(t *testing.T) { + t.Parallel() // Note: this must not use a greedy monster ('O' orc, ISGREED): the C // wake_monster gold-guarding check has no ISHELD guard, so the // look(TRUE) at the end of read_scroll immediately re-wakes greedy @@ -133,6 +141,8 @@ func TestReadHoldMonsterFreezesAdjacent(t *testing.T) { // (orc) held by a scroll is re-woken by the look(TRUE) that read_scroll // performs, ending up both held and running again. func TestHoldScrollGreedyMonsterQuirk(t *testing.T) { + t.Parallel() + g := mkGameInput(t) tp := spawnAdjacent(g, 'O') tp.Flags.Set(Awake) @@ -158,6 +168,8 @@ func TestHoldScrollGreedyMonsterQuirk(t *testing.T) { } func TestZapSlowMonster(t *testing.T) { + t.Parallel() + g := mkGameInput(t) tp := spawnAdjacent(g, 'Z') stick := newObject() @@ -183,6 +195,8 @@ func TestZapSlowMonster(t *testing.T) { } func TestParseOpts(t *testing.T) { + t.Parallel() + g := New(Params{Seed: 1}) g.ParseOpts("terse,nojump,name=Conan,fruit=mango,inven=slow") diff --git a/game/fight.go b/game/fight.go index aace02a..bb47920 100644 --- a/game/fight.go +++ b/game/fight.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import "strconv" diff --git a/game/fight_test.go b/game/fight_test.go index d39c51a..4e90985 100644 --- a/game/fight_test.go +++ b/game/fight_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import "testing" @@ -25,6 +26,8 @@ func spawnAdjacent(g *RogueGame, typ byte) *Monster { } func TestRollEmParsesMultiAttackDice(t *testing.T) { + t.Parallel() + g := mkGame(t, 42) att := &Creature{Stats: Stats{Str: 16, Lvl: 20, Dmg: dice("1x4/1x4/1x4")}} def := &Creature{Stats: Stats{ArmorClass: 10, HP: 1000}} @@ -43,6 +46,8 @@ func TestRollEmParsesMultiAttackDice(t *testing.T) { } func TestFightKillsMonster(t *testing.T) { + t.Parallel() + g := mkGame(t, 7) tp := spawnAdjacent(g, 'B') // bat: 1 hit die tp.Stats.HP = 1 @@ -64,6 +69,8 @@ func TestFightKillsMonster(t *testing.T) { } func TestAttackHurtsPlayer(t *testing.T) { + t.Parallel() + g := mkGame(t, 9) tp := spawnAdjacent(g, 'T') // troll: 1x8/1x8/2x6 tp.Stats.Lvl = 20 // always hits @@ -80,6 +87,8 @@ func TestAttackHurtsPlayer(t *testing.T) { } func TestRunnersChaseHero(t *testing.T) { + t.Parallel() + g := mkGame(t, 3) // Place a hobgoblin a few squares away in the hero's room and set it // running at the hero. @@ -104,6 +113,8 @@ func TestRunnersChaseHero(t *testing.T) { } func TestKilledLeprechaunDropsGoldViaFall(t *testing.T) { + t.Parallel() + g := mkGame(t, 21) tp := spawnAdjacent(g, 'L') tp.Stats.HP = 0 diff --git a/game/game.go b/game/game.go index bfdc83c..3da4fac 100644 --- a/game/game.go +++ b/game/game.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // ItemLore is the per-game item identity state: the randomized appearance diff --git a/game/init.go b/game/init.go index 2dd0671..6640d16 100644 --- a/game/init.go +++ b/game/init.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import "strings" @@ -169,7 +170,8 @@ func (g *RogueGame) initProbs() { sumProbs(g.Items.Scrolls[:]) sumProbs(g.Items.Rings[:]) sumProbs(g.Items.Sticks[:]) - sumProbs(g.Items.Weapons[:NumWeaponTypes]) // C sums MAXWEAPONS, excluding the flame entry + // C sums MAXWEAPONS, excluding the flame entry. + sumProbs(g.Items.Weapons[:NumWeaponTypes]) sumProbs(g.Items.Armors[:]) } diff --git a/game/io.go b/game/io.go index 6d73d77..98de025 100644 --- a/game/io.go +++ b/game/io.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import ( diff --git a/game/level.go b/game/level.go index ad93d4a..b572895 100644 --- a/game/level.go +++ b/game/level.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // Place describes a spot on the level map (rogue.h PLACE). diff --git a/game/misc.go b/game/misc.go index 75a01df..71da545 100644 --- a/game/misc.go +++ b/game/misc.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // misc.c — look() display maintenance, direction input, eating, level-ups, @@ -130,7 +131,9 @@ func lookForeignPassage(s *lookScan, fp PlaceFlags, ch byte) bool { // lookDiagonalBlocked hides diagonal door/passage squares the hero could // not actually step to (misc.c look). -func (g *RogueGame) lookDiagonalBlocked(s *lookScan, fp PlaceFlags, ch byte, y, x int) bool { +func (g *RogueGame) lookDiagonalBlocked( + s *lookScan, fp PlaceFlags, ch byte, y, x int, +) bool { if !fp.Has(FPassage) && ch != Door { return false } @@ -167,7 +170,9 @@ func (g *RogueGame) lookShow(s *lookScan, tp *Monster, ch byte, y, x int) bool { // lookCellChar picks what the square shows: trip rendering for empty // squares, waking and disguises for monsters. skip means the square is // not drawn at all (the monster switch of the look loop). -func (g *RogueGame) lookCellChar(s *lookScan, tp *Monster, y, x int, ch byte) (byte, bool) { +func (g *RogueGame) lookCellChar( + s *lookScan, tp *Monster, y, x int, ch byte, +) (byte, bool) { p := &g.Player switch { diff --git a/game/monsters.go b/game/monsters.go index 95b59e2..fd83d34 100644 --- a/game/monsters.go +++ b/game/monsters.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // monsters.c — monster creation and saving throws. diff --git a/game/move.go b/game/move.go index e1c15cf..ed30e1b 100644 --- a/game/move.go +++ b/game/move.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // move.c — hero movement commands. @@ -60,7 +61,9 @@ func (g *RogueGame) moveHero(dy, dx int) { // moveResolve acts on the square the hero stepped at: a wall may turn a // passage runner (reported with the new deltas); anything else completes // or refuses the move (the switch of move.c do_move). -func (g *RogueGame) moveResolve(nh Coord, ch byte, fl PlaceFlags, dy, dx int) (bool, int, int) { +func (g *RogueGame) moveResolve( + nh Coord, ch byte, fl PlaceFlags, dy, dx int, +) (bool, int, int) { switch ch { case ' ', '|', '-': if turn, ndy, ndx := g.passageTurn(dy, dx); turn { @@ -343,7 +346,8 @@ func (g *RogueGame) trapMystery(Coord) { case 0: g.msg("you are suddenly in a parallel dimension") case 1: - g.msg("the light in here suddenly seems %s", g.data.rainbow[g.rnd(len(g.data.rainbow))]) + g.msg("the light in here suddenly seems %s", + g.data.rainbow[g.rnd(len(g.data.rainbow))]) case 2: g.msg("you feel a sting in the side of your neck") case 3: diff --git a/game/newlevel.go b/game/newlevel.go index a549aba..348600a 100644 --- a/game/newlevel.go +++ b/game/newlevel.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // new_level.c — dig and draw a new level. diff --git a/game/newlevel_test.go b/game/newlevel_test.go index 72a8387..d912f54 100644 --- a/game/newlevel_test.go +++ b/game/newlevel_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import ( @@ -35,6 +36,8 @@ func renderMap(g *RogueGame) string { } func TestNewLevelInvariants(t *testing.T) { + t.Parallel() + for _, seed := range []int32{1, 12345, 2026, 99999} { g := genLevel(t, seed) @@ -142,6 +145,8 @@ func checkStartingKit(t *testing.T, g *RogueGame, seed int32) { } func TestNewLevelDeterministic(t *testing.T) { + t.Parallel() + a := renderMap(genLevel(t, 12345)) b := renderMap(genLevel(t, 12345)) @@ -158,6 +163,8 @@ func TestNewLevelDeterministic(t *testing.T) { // TestDeeperLevels exercises generation across many depths and seeds — // mazes, dark rooms, traps, treasure rooms — as a crash/invariant sweep. func TestDeeperLevels(t *testing.T) { + t.Parallel() + for _, seed := range []int32{7, 42, 1000, 31337} { g := New(Params{Seed: seed}) for depth := 1; depth <= 30; depth++ { diff --git a/game/object.go b/game/object.go index b62f689..db67316 100644 --- a/game/object.go +++ b/game/object.go @@ -38,6 +38,7 @@ const ( // Glyph returns the map/display character for this kind of object. func (k ObjectKind) Glyph() byte { + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch k { case KindPotion: return Potion @@ -64,6 +65,7 @@ func (k ObjectKind) Glyph() byte { // String names the category the way the C type_name() did. func (k ObjectKind) String() string { + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch k { case KindPotion: return potionName @@ -118,6 +120,7 @@ func (k ObjectKind) MergesInPack() bool { // stringRest names the remaining kinds, including the ring-or-stick // prompt pseudo-kind (the tail of the C type_name switch). func (k ObjectKind) stringRest() string { + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch k { case KindWand: return "wand or staff" diff --git a/game/options.go b/game/options.go index 95fe9a2..57e2dd9 100644 --- a/game/options.go +++ b/game/options.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import "strings" @@ -118,6 +119,7 @@ func boolStr(b bool) string { // getOpt reads a new value for an option (options.c get_bool/get_sf/ // get_inv_t/get_str dispatch). func (g *RogueGame) getOpt(op *optDesc) int { + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch op.kind { case optBool: return g.getBool(op.boolP) diff --git a/game/pack.go b/game/pack.go index cb86121..07e9128 100644 --- a/game/pack.go +++ b/game/pack.go @@ -156,7 +156,9 @@ func packScanWhich(pack []*Object, obj *Object, i, lp int) (int, int) { // anything else marks the insertion point (the matched-subtype switch // of pack.c add_pack). It returns the resulting entry, the insert-after // index, whether a merge happened, and ok false when the pack is full. -func (g *RogueGame) packMatch(obj, op *Object, i int, fromFloor bool) (*Object, int, bool, bool) { +func (g *RogueGame) packMatch( + obj, op *Object, i int, fromFloor bool, +) (*Object, int, bool, bool) { switch { case op.Kind.MergesInPack(): if !g.packRoom(fromFloor, obj) { @@ -175,7 +177,9 @@ func (g *RogueGame) packMatch(obj, op *Object, i int, fromFloor bool) (*Object, // packMatchGroup rejoins a grouped missile bundle with its group entry // (the o_group arm of pack.c add_pack). -func (g *RogueGame) packMatchGroup(obj *Object, i int, fromFloor bool) (*Object, int, bool, bool) { +func (g *RogueGame) packMatchGroup( + obj *Object, i int, fromFloor bool, +) (*Object, int, bool, bool) { p := &g.Player lp := i @@ -333,6 +337,7 @@ func (g *RogueGame) pickUp(ch byte) { // everything, KindCallable takes anything nameable (not food, not the // amulet), KindRingOrStick takes rings and wands. func matchesFilter(kind ObjectKind, item *Object) bool { + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch kind { case KindNone: return true diff --git a/game/passages.go b/game/passages.go index b9cd56e..93aa089 100644 --- a/game/passages.go +++ b/game/passages.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // passages.c — draw the connecting passages. diff --git a/game/potions.go b/game/potions.go index 70c2e09..0fae76a 100644 --- a/game/potions.go +++ b/game/potions.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import "fmt" @@ -261,6 +262,7 @@ func (g *RogueGame) applyPotionFuse(kind PotionKind, knowit bool) { // isMagic reports whether an object radiates magic (potions.c is_magic). func (g *RogueGame) isMagic(o *Object) bool { + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch o.Kind { case KindArmor: return o.Flags.Has(Protected) || o.ArmorClass != g.data.aClass[o.Which] diff --git a/game/rings.go b/game/rings.go index 1e7bc2a..02ef9b5 100644 --- a/game/rings.go +++ b/game/rings.go @@ -33,6 +33,7 @@ func (g *RogueGame) ringOn() { p.CurRing[ring] = obj // Calculate the effect it has on the poor guy. + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.RingKind() { case RingAddStrength: g.changeStrength(obj.Bonus) @@ -171,6 +172,7 @@ func ringNum(_ *RogueGame, obj *Object) string { return "" } + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.RingKind() { case RingProtection, RingAddStrength, RingIncreaseDamage, RingDexterity: return fmt.Sprintf(" [%s]", num(obj.Bonus, 0, Ring)) diff --git a/game/rip.go b/game/rip.go index 7fc78f1..32fdbe1 100644 --- a/game/rip.go +++ b/game/rip.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import ( @@ -98,8 +99,10 @@ func (g *RogueGame) totalWinner() { } g.standend() - g.scr.Std.MvAddStr(10, 0, "You have joined the elite ranks of those who have escaped the") - g.scr.Std.MvAddStr(11, 0, "Dungeons of Doom alive. You journey home and sell all your loot at") + g.scr.Std.MvAddStr(10, 0, + "You have joined the elite ranks of those who have escaped the") + g.scr.Std.MvAddStr(11, 0, + "Dungeons of Doom alive. You journey home and sell all your loot at") g.scr.Std.MvAddStr(12, 0, "a great profit and are admitted to the Fighters' Guild.") g.mvaddstr(NumLines-1, 0, "--Press space to continue--") g.refresh() @@ -132,6 +135,7 @@ func (g *RogueGame) objectWorth(obj *Object) int { it := &g.Items worth := 0 + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.Kind { case KindFood: worth = 2 * obj.Count diff --git a/game/rng.go b/game/rng.go index 774d0b1..c189dbd 100644 --- a/game/rng.go +++ b/game/rng.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // Rng is the original Rogue linear congruential generator. The C RN macro is diff --git a/game/rng_test.go b/game/rng_test.go index 7b1acf9..5415ae1 100644 --- a/game/rng_test.go +++ b/game/rng_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import "testing" @@ -6,6 +7,8 @@ import "testing" // (seed = seed*11109+13849; rnd(range) = abs(RN) % range) compiled and run // on this machine. They lock in seed compatibility with the C game. func TestRndMatchesCImplementation(t *testing.T) { + t.Parallel() + golden := map[int32][]int{ 1: {0, 30, 79, 7, 87, 1, 23, 7, 57, 98}, 12345: {92, 92, 45, 98, 24, 39, 92, 67, 3, 7}, @@ -23,6 +26,8 @@ func TestRndMatchesCImplementation(t *testing.T) { } func TestRollMatchesCImplementation(t *testing.T) { + t.Parallel() + golden := map[int32][]int{ 1: {6, 8, 10}, 12345: {10, 10, 15}, @@ -42,6 +47,8 @@ func TestRollMatchesCImplementation(t *testing.T) { // rnd(0) must return 0 without stepping the generator: the C macro // short-circuits before evaluating RN, and BEFORE/AFTER depend on it. func TestRndZeroDoesNotStep(t *testing.T) { + t.Parallel() + r := &Rng{Seed: 42} if got := r.Rnd(0); got != 0 { t.Fatalf("rnd(0) = %d, want 0", got) @@ -55,6 +62,8 @@ func TestRndZeroDoesNotStep(t *testing.T) { // spread(1)==1 and spread(2)==2 deterministically; the C BEFORE/AFTER // constants rely on this. func TestSpreadSmallValues(t *testing.T) { + t.Parallel() + g := &RogueGame{Rng: &Rng{Seed: 7}} if got := g.spread(1); got != 1 { t.Errorf("spread(1) = %d, want 1", got) diff --git a/game/rooms.go b/game/rooms.go index 1c096af..e18dfc7 100644 --- a/game/rooms.go +++ b/game/rooms.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // rooms.c — create the layout for the new level. @@ -330,7 +331,9 @@ func floorChar(rp *Room) byte { return Floor } -func (g *RogueGame) findFloorImpl(rp *Room, limit int, monst, pickroom bool) (Coord, bool) { +func (g *RogueGame) findFloorImpl( + rp *Room, limit int, monst, pickroom bool, +) (Coord, bool) { var compchar byte if !pickroom { compchar = floorChar(rp) diff --git a/game/run_test.go b/game/run_test.go index 85cbfb3..60cb551 100644 --- a/game/run_test.go +++ b/game/run_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import ( @@ -39,6 +40,7 @@ func driveTurns(t *testing.T, g *RogueGame, n int) { // TestRunDownStairs stands the hero on the staircase and descends via the // '>' command through the real turn loop, then checks the level changed. func TestRunDownStairs(t *testing.T) { + t.Parallel() // '>' is a free action (After=false), so it is followed by a paying // rest ('.') to end the command() call; without a paying action the // turn loop would spin forever on the auto-fed prompt input. @@ -65,6 +67,8 @@ func TestRunDownStairs(t *testing.T) { // quit and death paths that normally show it now exit the process, so the // display is exercised through score() directly. func TestScoreRendersList(t *testing.T) { + t.Parallel() + g := New(Params{Seed: 1, Term: &testTerm{}}) g.Player.Purse = 100 @@ -90,6 +94,8 @@ func TestScoreRendersList(t *testing.T) { // The hero is fortified so no death exits the process (step 8), and the fixed // seed keeps it deterministic. func TestDeepPlaythrough(t *testing.T) { + t.Parallel() + g := New(Params{Seed: 4242, Wizard: true, Term: &testTerm{}}) g.startLevel() g.prePlay() @@ -155,6 +161,7 @@ func TestDeepPlaythrough(t *testing.T) { // and traps. The hero is fortified each turn so nothing exits the process, // and the fixed seeds keep it deterministic; the point is to surface panics. func TestTurnLoopCrashSweep(t *testing.T) { + t.Parallel() // A generous mix of movement, search, and rest. The spaces between // commands double as answers to any --More-- prompt (wait_for eats // everything up to a space); without them one prompt would swallow the diff --git a/game/save.go b/game/save.go index c8832a6..095e8ab 100644 --- a/game/save.go +++ b/game/save.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import ( @@ -686,6 +687,7 @@ func Restore(path string, params Params) (*RogueGame, error) { if err != nil { return nil, err } + defer func() { _ = f.Close() }() // read-only handle var st SaveState diff --git a/game/save_test.go b/game/save_test.go index d41c7f6..2db3234 100644 --- a/game/save_test.go +++ b/game/save_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import ( @@ -8,6 +9,8 @@ import ( ) func TestSaveRestoreRoundTrip(t *testing.T) { + t.Parallel() + g := mkGame(t, 4242) // Dirty up some state so the round trip is meaningful. g.Player.Purse = 123 @@ -134,6 +137,8 @@ func checkEquipmentAliasing(t *testing.T, g, h *RogueGame) { } func TestRestoreRejectsWrongVersion(t *testing.T) { + t.Parallel() + g := mkGame(t, 1) path := filepath.Join(t.TempDir(), "rogue.save") st := g.snapshot() diff --git a/game/score.go b/game/score.go index 599cada..e86a67d 100644 --- a/game/score.go +++ b/game/score.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import ( @@ -36,6 +37,7 @@ func (g *RogueGame) rdScore() []ScoreEnt { if err != nil { return topTen } + defer func() { _ = f.Close() }() // read-only handle var onDisk []ScoreEnt diff --git a/game/scrolls.go b/game/scrolls.go index 75303cc..e0279fd 100644 --- a/game/scrolls.go +++ b/game/scrolls.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // scrolls.c — read a scroll and let it happen. @@ -148,7 +149,8 @@ func (g *RogueGame) createMonsterSpot() (Coord, bool) { // Or anything else nasty if ch := g.Level.VisibleChar(y, x); stepOk(ch) { if ch == Scroll { - if fo := g.Level.ObjectAt(y, x); fo != nil && fo.ScrollKind() == ScrollScareMonster { + if fo := g.Level.ObjectAt(y, x); fo != nil && + fo.ScrollKind() == ScrollScareMonster { continue } } diff --git a/game/seedcompat_test.go b/game/seedcompat_test.go index d3b5def..f89ec72 100644 --- a/game/seedcompat_test.go +++ b/game/seedcompat_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import ( @@ -51,6 +52,8 @@ func dumpItemTables(seed int32, g *RogueGame) string { // match C byte for byte. The golden is captured from an instrumented build // of the C game (testdata/README.md). func TestSeedCompatItemTables(t *testing.T) { + t.Parallel() + golden, err := os.ReadFile("testdata/item_tables.golden") if err != nil { t.Fatalf("read golden: %v", err) diff --git a/game/sticks.go b/game/sticks.go index 5675d16..f346d5a 100644 --- a/game/sticks.go +++ b/game/sticks.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import "fmt" @@ -272,6 +273,7 @@ func slowTarget(tp *Monster) { func (g *RogueGame) zapBolt(obj *Object) bool { var name string + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.WandKind() { case WandLightning: name = "bolt" @@ -434,7 +436,9 @@ func boltBounces(ch byte, heroPos, pos Coord) bool { // boltStrikesMonster resolves a bolt arriving on a monster's square (the // monster arm of the fire_bolt loop). It reports whether the bolt was // used up. -func (g *RogueGame) boltStrikesMonster(tp *Monster, bolt *Object, pos Coord, ch byte, name string, fromHero bool) bool { +func (g *RogueGame) boltStrikesMonster( + tp *Monster, bolt *Object, pos Coord, ch byte, name string, fromHero bool, +) bool { tp.OldCh = g.Level.Char(pos.Y, pos.X) if !g.saveThrow(VsMagic, &tp.Stats) { bolt.Pos = pos @@ -506,6 +510,7 @@ func (g *RogueGame) fixStick(cur *Object) { cur.HurlDmg = dice("1x1") + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch cur.WandKind() { case WandLight: cur.Charges = g.rnd(10) + 10 diff --git a/game/tables.go b/game/tables.go index 94934e3..90b0d83 100644 --- a/game/tables.go +++ b/game/tables.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // tables.go — the static data tables the C game kept as file-scope globals @@ -166,7 +167,10 @@ const ripWall = " | |" //nolint:funlen,maintidx // a single composite literal holding every C data table func newGameData() *gameData { return &gameData{ - initStats: Stats{Str: 16, Exp: 0, Lvl: 1, ArmorClass: 10, HP: 12, Dmg: dice("1x4"), MaxHP: 12}, + initStats: Stats{ + Str: 16, Exp: 0, Lvl: 1, ArmorClass: 10, HP: 12, + Dmg: dice("1x4"), MaxHP: 12, + }, aClass: [NumArmorTypes]int{ 8, // LEATHER @@ -205,7 +209,8 @@ func newGameData() *gameData { {"dragon", 100, Mean, Stats{10, 5000, 10, -1, 1, dice("1x8/1x8/3x10"), 0}}, {"emu", 0, Mean, Stats{10, 2, 1, 7, 1, dice("1x2"), 0}}, {"venus flytrap", 0, Mean, Stats{10, 80, 8, 3, 1, dice("%%%x0"), 0}}, - {"griffin", 20, Mean | Flying | Regenerates, Stats{10, 2000, 13, 2, 1, dice("4x3/3x5"), 0}}, + {"griffin", 20, Mean | Flying | Regenerates, + Stats{10, 2000, 13, 2, 1, dice("4x3/3x5"), 0}}, {"hobgoblin", 0, Mean, Stats{10, 3, 1, 5, 1, dice("1x8"), 0}}, {"ice monster", 0, 0, Stats{10, 5, 1, 9, 1, dice("0x0"), 0}}, {"jabberwock", 70, 0, Stats{10, 3000, 15, 6, 1, dice("2x12/2x4"), 0}}, @@ -248,6 +253,7 @@ func newGameData() *gameData { {Name: "plate mail", Prob: 5, Worth: 150}, }, + //nolint:dupl // distinct C data tables that merely share their shape basePotInfo: [NumPotionTypes]ObjInfo{ {Name: "confusion", Prob: 7, Worth: 5}, {Name: "hallucination", Prob: 8, Worth: 5}, @@ -265,6 +271,7 @@ func newGameData() *gameData { {Name: "levitation", Prob: 6, Worth: 75}, }, + //nolint:dupl // distinct C data tables that merely share their shape baseRingInfo: [NumRingTypes]ObjInfo{ {Name: "protection", Prob: 9, Worth: 400}, {Name: "add strength", Prob: 9, Worth: 400}, @@ -316,6 +323,7 @@ func newGameData() *gameData { {}, // DO NOT REMOVE: fake entry for dragon's breath }, + //nolint:dupl // distinct C data tables that merely share their shape baseWsInfo: [NumWandTypes]ObjInfo{ {Name: "light", Prob: 12, Worth: 250}, {Name: "invisibility", Prob: 6, Worth: 5}, diff --git a/game/tables_test.go b/game/tables_test.go index f98ffaa..21683d2 100644 --- a/game/tables_test.go +++ b/game/tables_test.go @@ -1,9 +1,12 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game import "testing" // badcheck from init.c: every probability table must sum to exactly 100. func TestProbabilitiesSumTo100(t *testing.T) { + t.Parallel() + sum := func(info []ObjInfo) int { s := 0 for _, oi := range info { @@ -32,6 +35,8 @@ func TestProbabilitiesSumTo100(t *testing.T) { } func TestInitProbsCumulative(t *testing.T) { + t.Parallel() + g := New(Params{Seed: 1}) last := g.Items.Potions[NumPotionTypes-1].Prob @@ -47,6 +52,8 @@ func TestInitProbsCumulative(t *testing.T) { } func TestNewGameRandomizesAppearances(t *testing.T) { + t.Parallel() + g := New(Params{Seed: 12345}) checkPotionColors(t, g) @@ -112,6 +119,8 @@ func checkWandMaterials(t *testing.T, g *RogueGame) { } func TestMonsterTable(t *testing.T) { + t.Parallel() + data := newGameData() if data.monsterTable[0].Name != "aquator" || data.monsterTable[25].Name != "zombie" { t.Error("monster table order broken") diff --git a/game/term_test.go b/game/term_test.go index 77012ea..ca0bcc3 100644 --- a/game/term_test.go +++ b/game/term_test.go @@ -1,3 +1,4 @@ +//nolint:testpackage // white-box tests reach unexported state (approved 2026-07-07) package game // testTerm is a headless Terminal for tests: rendering is a no-op and diff --git a/game/things.go b/game/things.go index e87ddff..b918663 100644 --- a/game/things.go +++ b/game/things.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import ( @@ -15,13 +16,15 @@ func (g *RogueGame) inventoryName(obj *Object, drop bool) string { which := obj.Which it := &g.Items + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.Kind { case KindPotion: g.nameit(&pb, obj, potionName, it.PotColors[which], &it.Potions[which], nullstr) case KindRing: g.nameit(&pb, obj, ringName, it.RingStones[which], &it.Rings[which], ringNum) case KindWand: - g.nameit(&pb, obj, it.WandType[which], it.WandMade[which], &it.Sticks[which], chargeStr) + g.nameit(&pb, obj, it.WandType[which], it.WandMade[which], + &it.Sticks[which], chargeStr) case KindScroll: g.nameScroll(&pb, obj) case KindFood: @@ -250,6 +253,7 @@ func (g *RogueGame) dropRing(obj *Object) { p.CurRing[hand] = nil + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.RingKind() { case RingAddStrength: g.changeStrength(-obj.Bonus) @@ -346,6 +350,7 @@ func (g *RogueGame) newRingThing(cur *Object) { cur.Kind = KindRing cur.Which = pickOne(g, g.Items.Rings[:]) + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch cur.RingKind() { case RingAddStrength, RingProtection, RingDexterity, RingIncreaseDamage: if cur.Bonus = g.rnd(3); cur.Bonus == 0 { diff --git a/game/types.go b/game/types.go index d8e8d2b..e9e63d9 100644 --- a/game/types.go +++ b/game/types.go @@ -118,7 +118,7 @@ const ( Known // ISKNOW: player knows details about the object Missile // ISMISL: object is a missile type Stackable // ISMANY: object comes in groups - WasFound // ISFOUND (objects): object has been seen (ISFOUND shares the bit with creatures) + WasFound // ISFOUND (objects): seen; bit shared with creatures Protected // ISPROT: armor is permanently protected ) @@ -140,18 +140,18 @@ type CreatureFlags int32 // Creature state bits (rogue.h). const ( CanConfuse CreatureFlags = 0o000001 // CANHUH: creature can confuse - CanSeeInvisible CreatureFlags = 0o000002 // CANSEE: creature can see invisible creatures + CanSeeInvisible CreatureFlags = 0o000002 // CANSEE: can see invisible creatures Blind CreatureFlags = 0o000004 // ISBLIND: creature is blind - Cancelled CreatureFlags = 0o000010 // ISCANC: creature has special qualities cancelled + Cancelled CreatureFlags = 0o000010 // ISCANC: special qualities cancelled Levitating CreatureFlags = 0o000010 // ISLEVIT: hero is levitating Found CreatureFlags = 0o000020 // ISFOUND: creature has been seen Greedy CreatureFlags = 0o000040 // ISGREED: creature runs to protect gold Hasted CreatureFlags = 0o000100 // ISHASTE: creature has been hastened - Targeted CreatureFlags = 0o000200 // ISTARGET: creature is the target of an 'f' command + Targeted CreatureFlags = 0o000200 // ISTARGET: target of an 'f' command Held CreatureFlags = 0o000400 // ISHELD: creature has been held Confused CreatureFlags = 0o001000 // ISHUH: creature is confused Invisible CreatureFlags = 0o002000 // ISINVIS: creature is invisible - Mean CreatureFlags = 0o004000 // ISMEAN: creature can wake when player enters room + Mean CreatureFlags = 0o004000 // ISMEAN: wakes when player enters room Hallucinating CreatureFlags = 0o004000 // ISHALU: hero is on acid trip Regenerates CreatureFlags = 0o010000 // ISREGEN: creature can regenerate Awake CreatureFlags = 0o020000 // ISRUN: creature is running at the player @@ -392,7 +392,7 @@ type Stone struct { } // CTRL maps a letter to its control character, as the C CTRL() macro. -func CTRL(c byte) byte { return c & 0o37 } +func CTRL(c byte) byte { return c & 0o37 } //nolint:mnd // the C CTRL() mask // distance returns the squared distance between two points (chase.c dist). func distance(y1, x1, y2, x2 int) int { diff --git a/game/weapons.go b/game/weapons.go index c555240..7290a8e 100644 --- a/game/weapons.go +++ b/game/weapons.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game import "fmt" diff --git a/game/wizard.go b/game/wizard.go index e7cd5dc..4b59628 100644 --- a/game/wizard.go +++ b/game/wizard.go @@ -1,3 +1,4 @@ +//nolint:mnd // C-faithful literals; names hurt C-greppability (approved 2026-07-07) package game // wizard.c — special wizard commands, some of which are also non-wizard @@ -25,6 +26,7 @@ func (g *RogueGame) createObj() { obj.Count = 1 g.Msgs.Mpos = 0 + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.Kind { case KindWeapon, KindArmor: g.createWeaponArmor(obj) @@ -82,6 +84,7 @@ func (g *RogueGame) createWeaponArmor(obj *Object) { // createRing sets up a wizard-created ring, prompting for a bonus on // the bonus rings (the ring arm of wizard.c create_obj). func (g *RogueGame) createRing(obj *Object) { + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.RingKind() { case RingProtection, RingAddStrength, RingDexterity, RingIncreaseDamage: g.msg("blessing? (+,-,n)") @@ -136,6 +139,7 @@ func (g *RogueGame) whatis(insist bool, kind ObjectKind) { return } + //nolint:exhaustive // C-faithful: only the cases C handled (approved 2026-07-07) switch obj.Kind { case KindScroll: setKnow(obj, g.Items.Scrolls[:]) diff --git a/term/tcell.go b/term/tcell.go index d87a705..49d1a39 100644 --- a/term/tcell.go +++ b/term/tcell.go @@ -128,6 +128,7 @@ func namedKey(k tcell.Key) (byte, bool) { // motionKey translates the arrow and paging keys to Rogue's movement // letters (tcell.go ReadChar). func motionKey(k tcell.Key) (byte, bool) { + //nolint:exhaustive // translation table: all other keys fall through switch k { case tcell.KeyUp: return 'k', true @@ -153,19 +154,20 @@ func motionKey(k tcell.Key) (byte, bool) { // editingKey translates the editing and control keys to their C0 codes // (tcell.go ReadChar). func editingKey(k tcell.Key) (byte, bool) { + //nolint:exhaustive // translation table: all other keys fall through switch k { case tcell.KeyEnter: return '\n', true case tcell.KeyEscape: return game.Escape, true case tcell.KeyBackspace, tcell.KeyBackspace2: - return 8, true + return '\b', true case tcell.KeyDelete: - return 0x7f, true + return '\x7f', true case tcell.KeyTab: return '\t', true case tcell.KeyCtrlC: - return 3, true + return '\x03', true } return 0, false @@ -188,8 +190,8 @@ func (t *Tcell) ShellEscape() { "[Entering shell; exit to return to the game]") // The shell session has no deadline by design; Background context. - cmd := exec.CommandContext(context.Background(), //nolint:gosec // G204: the user's own $SHELL - shell) + //nolint:gosec // G204: the user's own $SHELL + cmd := exec.CommandContext(context.Background(), shell) cmd.Stdin = os.Stdin cmd.Stdout = os.Stdout cmd.Stderr = os.Stderr -- 2.49.1