Skip to content

feat(lint): prohibit var shadowing#172

Merged
water-sucks merged 2 commits intonix-community:mainfrom
water-sucks:add-strict-var-shadowing-linter-rule
Feb 10, 2026
Merged

feat(lint): prohibit var shadowing#172
water-sucks merged 2 commits intonix-community:mainfrom
water-sucks:add-strict-var-shadowing-linter-rule

Conversation

@water-sucks
Copy link
Copy Markdown
Collaborator

@water-sucks water-sucks commented Feb 10, 2026

Lax variable shadowing rules have caused bugs in the past, especially in #169 and elsewhere.

This takes a more restrictive approach: variable shadowing is now completely disallowed in most situations. It does make short-hand variable assignments a little harder to write in cases with common variable names like err, but this is a worthwhile tradeoff IMO.

Summary by CodeRabbit

  • Refactor

    • Reorganized code structure throughout multiple modules to improve consistency and maintainability.
    • Enhanced internal type definitions and improved variable declaration patterns for better code clarity.
  • Chores

    • Integrated additional code linting tools to maintain higher code quality standards across the project.

@water-sucks
Copy link
Copy Markdown
Collaborator Author

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 10, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Feb 10, 2026

📝 Walkthrough

Walkthrough

This pull request applies extensive refactoring across 15 files to prevent variable shadowing by converting short variable declarations (:=) to assignments (=) using pre-declared variables. Additionally, public struct types in cmd/features/features.go are renamed (features → AvailableFeatures, complilationOptions → CompliationOptions), and the shadow linter is enabled in .golangci.yml.

Changes

Cohort / File(s) Summary
Linter Configuration & Type Renames
.golangci.yml, cmd/features/features.go
Added shadow linter to .golangci.yml; renamed features struct to AvailableFeatures and complilationOptions to CompliationOptions in cmd/features/features.go with field updates.
Activation & Generation Refactoring
cmd/activate/run.go, cmd/activate/user.go, cmd/generation/rollback/rollback.go, cmd/generation/switch/switch.go
Replaced short variable declarations with pre-declared variable assignments in error handling, confirmation input, and specialisation retrieval paths to eliminate shadowing.
Setup & Installation Refactoring
cmd/apply/apply.go, cmd/init/run.go, cmd/install/install.go
Converted short variable declarations to separate declarations with assignments for error handling, configuration paths, and specialisation retrieval; added early returns to prevent file overwrites when --force is not set.
Utility & Option Refactoring
cmd/option/completion.go, cmd/option/option.go, internal/utils/utils.go
Split short variable declarations into separate variable declarations followed by assignments for cache-building, option evaluation, and file-info retrieval.
System & Shell Refactoring
cmd/enter/run.go, internal/system/ssh.go
Converted short variable declarations to pre-declared variables and assignments in file descriptor handling and SSH authentication paths; expanded error handling and variable scoping.
Generation Utilities
internal/generation/generation.go
Replaced short variable declarations with separate declarations and assignments for JSON unmarshalling and file read fallback error handling.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 11.11% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(lint): prohibit var shadowing' directly and clearly describes the main objective of the changeset: adding a linter rule to prevent variable shadowing throughout the codebase.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.5.0)

level=error msg="[linters_context] typechecking error: pattern ./...: directory prefix . does not contain main module or its selected dependencies"


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
cmd/enter/run.go (1)

30-33: ⚠️ Potential issue | 🟠 Major

Variable shadowing: err on line 31 shadows the err from the if-init on line 30.

Change the assignment on line 31 from := to = to use the outer error variable instead of creating a shadow:

Fix
 	if _, err := os.Stat(nixosMarker); err != nil {
-		err := fmt.Errorf("%v is not a valid NixOS system", opts.RootLocation)
+		err = fmt.Errorf("%v is not a valid NixOS system", opts.RootLocation)
 		log.Error(err)
 		return err
 	}
cmd/install/install.go (1)

550-552: ⚠️ Potential issue | 🟠 Major

Fix remaining variable shadowing issues: Lines 550 and 564 redeclare err instead of reassigning it.

Line 550 declares a new err variable that shadows the outer err declared at line 540. Line 564 does the same. Use assignment (=) instead of declaration (:=) to reuse the outer variable:

Line 550 fix
-		if err := installBootloader(s, mountpoint); err != nil {
+		if err = installBootloader(s, mountpoint); err != nil {
 			return err
 		}
Line 564 fix
-			err := setRootPassword(s, mountpoint)
+			err = setRootPassword(s, mountpoint)
cmd/activate/user.go (1)

79-83: ⚠️ Potential issue | 🟡 Minor

err on line 82 shadows the outer err declared on line 42.

The err := on line 82 creates a new variable that shadows the outer err from the function scope. While this doesn't cause a functional issue (the block immediately returns), it should be fixed for code clarity and to satisfy the Go shadowing linter.

Proposed fix
	if status == "timeout" || status == "failed" || status == "dependency" {
-		err := fmt.Errorf("restarting nixos-activation.service failed with status %s", status)
+		err = fmt.Errorf("restarting nixos-activation.service failed with status %s", status)
		log.Error(err)
		return err
	}
internal/system/ssh.go (1)

345-353: ⚠️ Potential issue | 🔴 Critical

Data race: goroutine writes to the shared err variable concurrently.

Line 348 assigns to the outer err (from Run on line 270) inside a goroutine that runs concurrently with session.Run(fullCmd) on line 355, which also assigns to err. Previously, := would have scoped a local err to the goroutine; now both paths race on the same variable.

Proposed fix — use a local variable inside the goroutine
 	go func() {
 		for sig := range sigCh {
 			if s := osSignalToSSHSignal(sig); s != "" {
-				if err = session.Signal(s); err != nil {
+				if sigErr := session.Signal(s); sigErr != nil {
-					log.Warnf("failed to forward signal '%v': %v", s, err)
+					log.Warnf("failed to forward signal '%v': %v", s, sigErr)
 				}
 			}
 		}
 	}()
cmd/option/option.go (1)

187-195: ⚠️ Potential issue | 🟠 Major

Use a local err variable in the evaluator closure to prevent data race when called concurrently.

The closure captures and mutates the outer optionMain scope's err variable. Since optnix's TUI can invoke EvaluatorFunc from multiple goroutines, concurrent writes to the shared err create a data race. The fix is straightforward:

Proposed fix
 var evaluator option.EvaluatorFunc = func(optionName string) (string, error) {
-	var value *string
-	value, err = nixosConfig.EvalAttribute(optionName)
+	value, err := nixosConfig.EvalAttribute(optionName)
 	realValue := ""
 	if value != nil {
 		realValue = *value
 	}
 	return realValue, err
 }
🤖 Fix all issues with AI agents
In `@cmd/features/features.go`:
- Around line 45-48: Rename the exported struct CompliationOptions to the
correctly spelled CompilationOptions and update every reference/usage to that
type (including any variable declarations, struct literals, function
parameters/returns and instantiations that use CompliationOptions) so the API
stays consistent; keep the struct fields (NixpkgsVersion, Flake) and their tags
unchanged, and ensure exported references (e.g., any places constructing the
struct or accepting it) are updated to use CompilationOptions.
🧹 Nitpick comments (3)
internal/system/ssh.go (2)

59-66: Variable name collision: current used for both *user.User and os.Getenv("USER").

Line 59 declares current as *user.User, but line 62 uses current as a string from os.Getenv. These are in different scopes (the else if starts a new block), so it compiles, but the shadowing of the variable name within this narrow block is confusing — ironic given the PR's purpose.


102-108: Closure captures outer err — verify this is safe with SSH auth retries.

With :==, the password callback now mutates NewSSHSystem's err. If the SSH library calls this callback multiple times (e.g., on authentication retry), intermediate errors written to the outer err could leak into unrelated code paths. In practice, ssh.Dial on line 114 overwrites err, so this appears safe, but the coupling is fragile.

Safer alternative — keep a local err in the closure
 passwordCallback := ssh.PasswordCallback(func() (string, error) {
-	var bytePassword []byte
-	bytePassword, err = promptForPassword(username, address)
+	bytePassword, err := promptForPassword(username, address)
 	if err != nil {
 		return "", err
 	}
cmd/apply/apply.go (1)

633-643: Assigning to outer err inside defer — safe now, but fragile if named returns are ever introduced.

Currently err is not a named return value, so mutating it in the defer has no effect on the returned error. However, if applyMain is ever refactored to use a named err return, this assignment would silently overwrite the original return error on the rollback path. Consider using a locally-scoped variable (e.g., rollbackErr) to make intent explicit and avoid that future pitfall.

Suggested change
-			if err = activation.SetNixProfileGeneration(
+			if rollbackErr := activation.SetNixProfileGeneration(
 				targetHost,
 				opts.ProfileName,
 				previousGenNumber, &activation.SetNixProfileGenerationOptions{
 					RootCommand:    cfg.RootCommand,
 					UseRootCommand: activationUseRoot,
 				},
-			); err != nil {
+			); rollbackErr != nil {
-				log.Errorf("failed to rollback system profile: %v", err)
+				log.Errorf("failed to rollback system profile: %v", rollbackErr)
 				log.Info("make sure to rollback the system manually before deleting anything!")
 			}

@water-sucks water-sucks force-pushed the add-strict-var-shadowing-linter-rule branch from 44aef7c to 2d9135d Compare February 10, 2026 03:18
@water-sucks water-sucks force-pushed the add-strict-var-shadowing-linter-rule branch from 2d9135d to 5c79564 Compare February 10, 2026 03:19
@water-sucks water-sucks enabled auto-merge (rebase) February 10, 2026 03:19
@water-sucks water-sucks merged commit 7be930c into nix-community:main Feb 10, 2026
2 checks passed
@water-sucks water-sucks deleted the add-strict-var-shadowing-linter-rule branch February 10, 2026 18:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant