Skip to content

Commit a71da68

Browse files
cvclaude
andcommitted
refactor: Fix additional linter issues (thelper, wastedassign, perfsprint)
Add new linters to golangci-lint: errchkjson, fatcontext, nilerr, nosprintfhostport, canonicalheader, dupword, mirror, perfsprint, predeclared, wastedassign, thelper. Fixes include: - Add t.Helper() to test setup functions (thelper) - Use http.MethodPost/MethodGet constants (usestdlibvars) - Use errors.As for wrapped error checking (errorlint) - Use fmt.Sprintf instead of string concat in errors.New (perfsprint) - Remove custom min() function (predeclared, Go 1.21 builtin) - Fix wastedassign in event generators - Fix nilerr by explicitly ignoring cache load error - Use switch statements instead of if-else chains 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <[email protected]>
1 parent 53d7385 commit a71da68

File tree

17 files changed

+75
-67
lines changed

17 files changed

+75
-67
lines changed

.golangci.yml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,20 +6,31 @@ linters:
66
- bodyclose # HTTP response body closure
77
- durationcheck # Duration multiplication bugs
88
- errorlint # Error wrapping issues
9+
- errchkjson # JSON encoding error checks
10+
- fatcontext # Context misuse in loops
11+
- nilerr # Returns nil even when error set
912
- noctx # Missing context.Context usage
13+
- nosprintfhostport # URL construction bugs
1014

1115
# Security
1216
- gosec # Security issues
1317

1418
# Code quality
19+
- canonicalheader # HTTP header canonicalization
20+
- dupword # Duplicate words (typos)
1521
- goconst # Repeated strings that could be constants
1622
- gocritic # Bugs, performance, style issues
23+
- mirror # bytes/strings usage patterns
1724
- misspell # Typos
25+
- perfsprint # Faster alternatives to fmt.Sprintf
26+
- predeclared # Shadowing predeclared identifiers
1827
- unconvert # Unnecessary type conversions
1928
- usestdlibvars # Use stdlib constants
29+
- wastedassign # Wasted assignments
2030

2131
# Test quality
2232
- testifylint # Testify usage
33+
- thelper # Test helper best practices
2334

2435
settings:
2536
goconst:

internal/api/auth.go

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"context"
66
"encoding/base64"
77
"encoding/json"
8+
"errors"
89
"fmt"
910
"io"
1011
"net/http"
@@ -230,7 +231,7 @@ func (c *Client) GetEncryptionKeys(ctx context.Context) error {
230231
}
231232

232233
if response.Payload == "" {
233-
return fmt.Errorf("payload not found in response")
234+
return errors.New("payload not found in response")
234235
}
235236

236237
decrypted, err := c.decryptCheckVersionPayload(response.Payload)
@@ -280,7 +281,7 @@ func (c *Client) GetUsherEncryptionKey(ctx context.Context) (string, string, err
280281
}
281282

282283
if response.Data.PublicKey == "" {
283-
return "", "", fmt.Errorf("public key not found in response")
284+
return "", "", errors.New("public key not found in response")
284285
}
285286

286287
return response.Data.PublicKey, response.Data.VersionPrefix, nil
@@ -344,17 +345,17 @@ func (c *Client) Login(ctx context.Context) error {
344345
}
345346

346347
if response.Status == "INVALID_CREDENTIAL" {
347-
return fmt.Errorf("invalid email or password")
348+
return errors.New("invalid email or password")
348349
}
349350
if response.Status == "USER_LOCKED" {
350-
return fmt.Errorf("account is locked")
351+
return errors.New("account is locked")
351352
}
352353
if response.Status != "OK" {
353354
return fmt.Errorf("login failed with status: %s", response.Status)
354355
}
355356

356357
if response.Data.AccessToken == "" {
357-
return fmt.Errorf("access token not found in response")
358+
return errors.New("access token not found in response")
358359
}
359360

360361
c.accessToken = response.Data.AccessToken

internal/api/auth_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ func TestClient_GetEncryptionKeys(t *testing.T) {
2828
assert.Equalf(t, "POST", r.Method, "Expected POST method, got %s", r.Method)
2929

3030
// Verify headers
31-
assert.NotEmpty(t, r.Header.Get("app-code"), "Expected app-code header")
31+
assert.NotEmpty(t, r.Header.Get("App-Code"), "Expected app-code header")
3232

3333
// Mock response - encrypt a simple JSON payload
3434
response := map[string]interface{}{

internal/api/client.go

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ func handleAPIResponse(response *APIBaseResponse) (string, error) {
145145
if response.State == "S" {
146146
// Success - return encrypted payload for caller to decrypt
147147
if response.Payload == "" {
148-
return "", fmt.Errorf("payload not found in response")
148+
return "", errors.New("payload not found in response")
149149
}
150150

151151
return response.Payload, nil
@@ -168,10 +168,10 @@ func handleAPIResponse(response *APIBaseResponse) (string, error) {
168168

169169
// Generic error
170170
if response.Message != "" {
171-
return "", NewAPIError(fmt.Sprintf("Request failed: %s", response.Message))
171+
return "", NewAPIError("Request failed: " + response.Message)
172172
}
173173
if response.Error != "" {
174-
return "", NewAPIError(fmt.Sprintf("Request failed: %s", response.Error))
174+
return "", NewAPIError("Request failed: " + response.Error)
175175
}
176176

177177
return "", NewAPIError("Request failed for an unknown reason")

internal/api/client_test.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,11 @@ func TestAPIRequest_Success(t *testing.T) {
1919
// Create a mock server
2020
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
2121
// Verify headers
22-
assert.NotEmpty(t, r.Header.Get("device-id"), "device-id header is missing")
23-
assert.NotEmpty(t, r.Header.Get("app-code"), "app-code header is missing")
24-
assert.Equal(t, UserAgentBaseAPI, r.Header.Get("user-agent"))
25-
assert.NotEmpty(t, r.Header.Get("sign"), "sign header is missing")
26-
assert.NotEmpty(t, r.Header.Get("timestamp"), "timestamp header is missing")
22+
assert.NotEmpty(t, r.Header.Get("Device-Id"), "device-id header is missing")
23+
assert.NotEmpty(t, r.Header.Get("App-Code"), "app-code header is missing")
24+
assert.Equal(t, UserAgentBaseAPI, r.Header.Get("User-Agent"))
25+
assert.NotEmpty(t, r.Header.Get("Sign"), "sign header is missing")
26+
assert.NotEmpty(t, r.Header.Get("Timestamp"), "timestamp header is missing")
2727

2828
// Return success response with encrypted payload
2929
// Encrypt a simple JSON response
@@ -249,7 +249,7 @@ func TestAPIRequest_POST_WithBody(t *testing.T) {
249249
assert.Equalf(t, "POST", r.Method, "Expected POST method, got %s", r.Method)
250250

251251
// Verify sign header is present
252-
assert.NotEmpty(t, r.Header.Get("sign"), "sign header is missing")
252+
assert.NotEmpty(t, r.Header.Get("Sign"), "sign header is missing")
253253

254254
// Return encrypted success response
255255
testResponse := map[string]interface{}{

internal/api/crypto.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import (
88
"crypto/x509"
99
"encoding/base64"
1010
"encoding/hex"
11+
"errors"
1112
"fmt"
1213
"strconv"
1314
"strings"
@@ -50,7 +51,7 @@ func EncryptRSA(data, publicKeyBase64 string) ([]byte, error) {
5051

5152
rsaPubKey, ok := pubKey.(*rsa.PublicKey)
5253
if !ok {
53-
return nil, fmt.Errorf("not an RSA public key")
54+
return nil, errors.New("not an RSA public key")
5455
}
5556

5657
// Encrypt using PKCS1v15

internal/api/types.go

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package api
22

33
import (
44
"encoding/json"
5+
"errors"
56
"fmt"
67
"strings"
78
)
@@ -224,15 +225,15 @@ type RemoteHvacInfo struct {
224225
// GetInternalVIN extracts the internal VIN from the first vehicle in the response
225226
func (r *VecBaseInfosResponse) GetInternalVIN() (string, error) {
226227
if len(r.VecBaseInfos) == 0 {
227-
return "", fmt.Errorf("no vehicles found")
228+
return "", errors.New("no vehicles found")
228229
}
229230
return string(r.VecBaseInfos[0].Vehicle.CvInformation.InternalVIN), nil
230231
}
231232

232233
// GetVehicleInfo extracts vehicle identification info from the response
233234
func (r *VecBaseInfosResponse) GetVehicleInfo() (vin, nickname, modelName, modelYear string, err error) {
234235
if len(r.VecBaseInfos) == 0 {
235-
err = fmt.Errorf("no vehicles found")
236+
err = errors.New("no vehicles found")
236237
return
237238
}
238239
info := r.VecBaseInfos[0]
@@ -247,7 +248,7 @@ func (r *VecBaseInfosResponse) GetVehicleInfo() (vin, nickname, modelName, model
247248
// GetBatteryInfo extracts battery information from the EV status response
248249
func (r *EVVehicleStatusResponse) GetBatteryInfo() (BatteryInfo, error) {
249250
if len(r.ResultData) == 0 {
250-
return BatteryInfo{}, fmt.Errorf("no EV status data available")
251+
return BatteryInfo{}, errors.New("no EV status data available")
251252
}
252253
chargeInfo := r.ResultData[0].PlusBInformation.VehicleInfo.ChargeInfo
253254
return BatteryInfo{
@@ -265,11 +266,11 @@ func (r *EVVehicleStatusResponse) GetBatteryInfo() (BatteryInfo, error) {
265266
// GetHvacInfo extracts HVAC information from the EV status response
266267
func (r *EVVehicleStatusResponse) GetHvacInfo() (HVACInfo, error) {
267268
if len(r.ResultData) == 0 {
268-
return HVACInfo{}, fmt.Errorf("no EV status data available")
269+
return HVACInfo{}, errors.New("no EV status data available")
269270
}
270271
hvacInfo := r.ResultData[0].PlusBInformation.VehicleInfo.RemoteHvacInfo
271272
if hvacInfo == nil {
272-
return HVACInfo{}, fmt.Errorf("no HVAC info available")
273+
return HVACInfo{}, errors.New("no HVAC info available")
273274
}
274275
return HVACInfo{
275276
HVACOn: int(hvacInfo.HVAC) == HVACStatusOn,
@@ -283,15 +284,15 @@ func (r *EVVehicleStatusResponse) GetHvacInfo() (HVACInfo, error) {
283284
// GetOccurrenceDate returns the occurrence date from the first result
284285
func (r *EVVehicleStatusResponse) GetOccurrenceDate() (string, error) {
285286
if len(r.ResultData) == 0 {
286-
return "", fmt.Errorf("no EV status data available")
287+
return "", errors.New("no EV status data available")
287288
}
288289
return r.ResultData[0].OccurrenceDate, nil
289290
}
290291

291292
// GetFuelInfo extracts fuel information from the vehicle status response
292293
func (r *VehicleStatusResponse) GetFuelInfo() (FuelInfo, error) {
293294
if len(r.RemoteInfos) == 0 {
294-
return FuelInfo{}, fmt.Errorf("no vehicle status data available")
295+
return FuelInfo{}, errors.New("no vehicle status data available")
295296
}
296297
fuel := r.RemoteInfos[0].ResidualFuel
297298
return FuelInfo{
@@ -303,7 +304,7 @@ func (r *VehicleStatusResponse) GetFuelInfo() (FuelInfo, error) {
303304
// GetTiresInfo extracts tire pressure information from the vehicle status response
304305
func (r *VehicleStatusResponse) GetTiresInfo() (TireInfo, error) {
305306
if len(r.RemoteInfos) == 0 {
306-
return TireInfo{}, fmt.Errorf("no vehicle status data available")
307+
return TireInfo{}, errors.New("no vehicle status data available")
307308
}
308309
tpms := r.RemoteInfos[0].TPMSInformation
309310
return TireInfo{
@@ -317,7 +318,7 @@ func (r *VehicleStatusResponse) GetTiresInfo() (TireInfo, error) {
317318
// GetLocationInfo extracts location information from the vehicle status response
318319
func (r *VehicleStatusResponse) GetLocationInfo() (LocationInfo, error) {
319320
if len(r.AlertInfos) == 0 {
320-
return LocationInfo{}, fmt.Errorf("no alert info available")
321+
return LocationInfo{}, errors.New("no alert info available")
321322
}
322323
pos := r.AlertInfos[0].PositionInfo
323324
return LocationInfo{
@@ -401,7 +402,7 @@ type HVACInfo struct {
401402
// GetDoorsInfo extracts door lock status from the vehicle status response
402403
func (r *VehicleStatusResponse) GetDoorsInfo() (status DoorStatus, err error) {
403404
if len(r.AlertInfos) == 0 {
404-
err = fmt.Errorf("no alert info available")
405+
err = errors.New("no alert info available")
405406
return
406407
}
407408
door := r.AlertInfos[0].Door
@@ -434,7 +435,7 @@ func (r *VehicleStatusResponse) GetDoorsInfo() (status DoorStatus, err error) {
434435
// GetOdometerInfo extracts odometer reading from the vehicle status response
435436
func (r *VehicleStatusResponse) GetOdometerInfo() (OdometerInfo, error) {
436437
if len(r.RemoteInfos) == 0 {
437-
return OdometerInfo{}, fmt.Errorf("no vehicle status data available")
438+
return OdometerInfo{}, errors.New("no vehicle status data available")
438439
}
439440
return OdometerInfo{
440441
OdometerKm: r.RemoteInfos[0].DriveInformation.OdoDispValue,
@@ -444,7 +445,7 @@ func (r *VehicleStatusResponse) GetOdometerInfo() (OdometerInfo, error) {
444445
// GetWindowsInfo extracts window position information from the vehicle status response
445446
func (r *VehicleStatusResponse) GetWindowsInfo() (WindowStatus, error) {
446447
if len(r.AlertInfos) == 0 {
447-
return WindowStatus{}, fmt.Errorf("no alert info available")
448+
return WindowStatus{}, errors.New("no alert info available")
448449
}
449450
pw := r.AlertInfos[0].Pw
450451
return WindowStatus{
@@ -458,7 +459,7 @@ func (r *VehicleStatusResponse) GetWindowsInfo() (WindowStatus, error) {
458459
// GetHazardInfo extracts hazard lights status from the vehicle status response
459460
func (r *VehicleStatusResponse) GetHazardInfo() (hazardsOn bool, err error) {
460461
if len(r.AlertInfos) == 0 {
461-
err = fmt.Errorf("no alert info available")
462+
err = errors.New("no alert info available")
462463
return
463464
}
464465
hazardsOn = int(r.AlertInfos[0].HazardLamp.HazardSw) == HazardLightsOn

internal/cli/client.go

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,8 @@ func createAPIClient(ctx context.Context) (*api.Client, error) {
2828
return nil, fmt.Errorf("failed to create API client: %w", err)
2929
}
3030

31-
// Try to load cached credentials
32-
cachedCreds, err := cache.Load()
33-
if err != nil {
34-
// If cache load fails, just continue without it
35-
// The client will authenticate normally
36-
return client, nil
37-
}
31+
// Try to load cached credentials (ignore errors - client will authenticate normally)
32+
cachedCreds, _ := cache.Load()
3833

3934
// If we have valid cached credentials, use them
4035
if cachedCreds != nil && cachedCreds.IsValid() {

internal/cli/skill_test.go

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ func TestUninstallSkill(t *testing.T) {
3636
{
3737
name: "removes existing directory",
3838
setupFunc: func(t *testing.T, tempDir string) {
39+
t.Helper()
3940
// Create the directory structure
4041
skillPath := filepath.Join(tempDir, ".claude", "skills", skill.SkillName)
4142
err := os.MkdirAll(skillPath, 0755)
@@ -45,13 +46,13 @@ func TestUninstallSkill(t *testing.T) {
4546
testFile := filepath.Join(skillPath, "test.txt")
4647
err = os.WriteFile(testFile, []byte("test"), 0644)
4748
require.NoError(t, err, "Failed to create test file: %v")
48-
4949
},
5050
wantErr: false,
5151
},
5252
{
5353
name: "handles non-existent directory gracefully",
5454
setupFunc: func(t *testing.T, tempDir string) {
55+
t.Helper()
5556
// Don't create anything - directory doesn't exist
5657
},
5758
wantErr: false,
@@ -218,20 +219,21 @@ func TestSkillUninstallCommand_Execute(t *testing.T) {
218219
{
219220
name: "removes existing installation",
220221
setupFunc: func(t *testing.T, tempDir string) {
222+
t.Helper()
221223
skillPath := filepath.Join(tempDir, ".claude", "skills", skill.SkillName)
222224
err := os.MkdirAll(skillPath, 0755)
223225
require.NoError(t, err, "Failed to create skill directory: %v")
224226

225227
testFile := filepath.Join(skillPath, "test.txt")
226228
err = os.WriteFile(testFile, []byte("test"), 0644)
227229
require.NoError(t, err, "Failed to create test file: %v")
228-
229230
},
230231
expectedOutput: "Skill uninstalled from ",
231232
},
232233
{
233234
name: "handles non-existent installation",
234235
setupFunc: func(t *testing.T, tempDir string) {
236+
t.Helper()
235237
// Don't create anything
236238
},
237239
expectedOutput: "Skill not installed at ",
@@ -395,6 +397,7 @@ func TestCheckSkillVersion(t *testing.T) {
395397
{
396398
name: "returns SkillNotInstalled when skill directory does not exist",
397399
setupFunc: func(t *testing.T, tempDir string) {
400+
t.Helper()
398401
// Don't create anything
399402
},
400403
expectedStatus: SkillNotInstalled,
@@ -403,6 +406,7 @@ func TestCheckSkillVersion(t *testing.T) {
403406
{
404407
name: "returns SkillVersionUnknown when skill exists without version file",
405408
setupFunc: func(t *testing.T, tempDir string) {
409+
t.Helper()
406410
skillPath := filepath.Join(tempDir, ".claude", "skills", skill.SkillName)
407411
err := os.MkdirAll(skillPath, 0755)
408412
require.NoError(t, err, "Failed to create skill directory: %v")
@@ -418,36 +422,37 @@ func TestCheckSkillVersion(t *testing.T) {
418422
{
419423
name: "returns SkillVersionMatch when versions match",
420424
setupFunc: func(t *testing.T, tempDir string) {
425+
t.Helper()
421426
skillPath := filepath.Join(tempDir, ".claude", "skills", skill.SkillName)
422427
err := os.MkdirAll(skillPath, 0755)
423428
require.NoError(t, err, "Failed to create skill directory: %v")
424429

425430
versionPath := filepath.Join(skillPath, ".mcs-version")
426431
err = os.WriteFile(versionPath, []byte(Version), 0644)
427432
require.NoError(t, err, "Failed to create version file: %v")
428-
429433
},
430434
expectedStatus: SkillVersionMatch,
431435
expectedVersion: Version,
432436
},
433437
{
434438
name: "returns SkillVersionMismatch when versions differ",
435439
setupFunc: func(t *testing.T, tempDir string) {
440+
t.Helper()
436441
skillPath := filepath.Join(tempDir, ".claude", "skills", skill.SkillName)
437442
err := os.MkdirAll(skillPath, 0755)
438443
require.NoError(t, err, "Failed to create skill directory: %v")
439444

440445
versionPath := filepath.Join(skillPath, ".mcs-version")
441446
err = os.WriteFile(versionPath, []byte("1.0.0"), 0644)
442447
require.NoError(t, err, "Failed to create version file: %v")
443-
444448
},
445449
expectedStatus: SkillVersionMismatch,
446450
expectedVersion: "1.0.0",
447451
},
448452
{
449453
name: "handles version file with whitespace",
450454
setupFunc: func(t *testing.T, tempDir string) {
455+
t.Helper()
451456
skillPath := filepath.Join(tempDir, ".claude", "skills", skill.SkillName)
452457
err := os.MkdirAll(skillPath, 0755)
453458
require.NoError(t, err, "Failed to create skill directory: %v")
@@ -456,7 +461,6 @@ func TestCheckSkillVersion(t *testing.T) {
456461
// Write version with trailing newline
457462
err = os.WriteFile(versionPath, []byte(Version+"\n"), 0644)
458463
require.NoError(t, err, "Failed to create version file: %v")
459-
460464
},
461465
expectedStatus: SkillVersionMatch,
462466
expectedVersion: Version,

0 commit comments

Comments
 (0)