// Copyright (c) 2026 Lark Technologies Pte. Ltd. // SPDX-License-Identifier: MIT package errscontract import ( "strings" "testing" ) // 4 source-level rules: // (B) typed Error must embed Problem → REJECT // (C) no service-side mergeCodeMeta / registrar → REJECT // (D) Subtype: "ad_hoc_*" literal → LABEL (governance signal) // (E) Subtype value not in declared allowlist → REJECT / LABEL / WARNING func TestCheckProblemEmbed_RejectsMissingProblemEmbed(t *testing.T) { src := `package errs type FrobnicateError struct { Code int Msg string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "FrobnicateError") { t.Errorf("message should name the violating type: %s", v[0].Message) } } func TestCheckProblemEmbed_AcceptsPackageLocalEmbed(t *testing.T) { src := `package errs type Problem struct{} type GoodError struct { Problem Extra string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 0 { t.Errorf("compliant struct should pass, got: %+v", v) } } func TestCheckProblemEmbed_AcceptsImportedEmbed(t *testing.T) { // `errs.Problem` selector form: used by re-export packages. src := `package alias import "github.com/larksuite/cli/errs" type GoodError struct { errs.Problem Extra string } ` v := CheckProblemEmbed("internal/alias/x.go", src) if len(v) != 0 { t.Errorf("imported-embed should pass, got: %+v", v) } } func TestCheckProblemEmbed_RejectsSecurityPolicyErrorWithoutProblem(t *testing.T) { // Production SecurityPolicyError embeds Problem (see errs/types.go); the // previous CheckProblemEmbed whitelist for this type was dead code that would also // mask a future regression where the embed gets dropped. src := `package errs type SecurityPolicyError struct { ChallengeURL string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "SecurityPolicyError") { t.Errorf("message should name the violating type: %s", v[0].Message) } } func TestCheckProblemEmbed_AcceptsSecurityPolicyErrorWithProblem(t *testing.T) { // Mirrors the real errs/types.go declaration — must pass with no violation. src := `package errs type Problem struct{} type SecurityPolicyError struct { Problem ChallengeURL string } ` v := CheckProblemEmbed("errs/types.go", src) if len(v) != 0 { t.Errorf("compliant SecurityPolicyError must pass, got: %+v", v) } } func TestCheckNoRegistrar_RejectsMergeCodeMetaInShortcuts(t *testing.T) { src := `package task func init() { mergeCodeMeta(taskMap, "task") } var taskMap = map[int]any{} ` v := CheckNoRegistrar("shortcuts/task/init.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "mergeCodeMeta") { t.Errorf("message must name the offending call: %s", v[0].Message) } if !strings.Contains(v[0].Suggestion, "internal/errclass/codemeta_") { t.Errorf("suggestion must point to the right location: %s", v[0].Suggestion) } } func TestCheckNoRegistrar_RejectsRegisterServiceMapInInternal(t *testing.T) { src := `package auth import "github.com/larksuite/cli/internal/output" func init() { output.RegisterServiceMap("auth", nil) } ` v := CheckNoRegistrar("internal/auth/init.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "RegisterServiceMap") { t.Errorf("message must name the offending call: %s", v[0].Message) } } func TestCheckNoRegistrar_AllowsInternalErrclass(t *testing.T) { // internal/errclass legitimately owns mergeCodeMeta; rule must not fire here. src := `package errclass func init() { mergeCodeMeta(taskCodeMeta, "task") } var taskCodeMeta = map[int]any{} ` v := CheckNoRegistrar("internal/errclass/codemeta_task.go", src) if len(v) != 0 { t.Errorf("internal/errclass must be exempt, got: %+v", v) } } func TestCheckNoRegistrar_IgnoresTestFiles(t *testing.T) { src := `package task_test func TestFoo(t *testing.T) { mergeCodeMeta(nil, "fixture") } ` v := CheckNoRegistrar("shortcuts/task/init_test.go", src) if len(v) != 0 { t.Errorf("test fixtures must be exempt, got: %+v", v) } } func TestCheckNoRegistrar_IgnoresCmdAndRoot(t *testing.T) { src := `package main func init() { mergeCodeMeta(nil, "x") } ` v := CheckNoRegistrar("cmd/foo/main.go", src) if len(v) != 0 { t.Errorf("cmd/ paths are out of CheckNoRegistrar scope, got: %+v", v) } } func TestCheckAdHocSubtype_EmitsLabel(t *testing.T) { src := `package task func makeErr() any { return struct{ Subtype string }{Subtype: "ad_hoc_task_quota_breach"} } ` v := CheckAdHocSubtype("shortcuts/task/quota.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionLabel { t.Errorf("action = %q, want LABEL (ad_hoc_* is soft governance signal)", v[0].Action) } if !strings.Contains(v[0].Message, "needs-taxonomy-decision") { t.Errorf("message should carry the label prefix so CI can grep it: %s", v[0].Message) } if !strings.Contains(v[0].Suggestion, "1 week") { t.Errorf("suggestion should state the ad_hoc_* promotion window: %s", v[0].Suggestion) } } func TestCheckAdHocSubtype_DetectsCastForm(t *testing.T) { // Subtype field assigned via errs.Subtype("ad_hoc_xxx") cast. src := `package task type problem struct{ Subtype any } var _ = problem{Subtype: Subtype("ad_hoc_new_feature")} func Subtype(s string) string { return s } ` v := CheckAdHocSubtype("shortcuts/task/x.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionLabel { t.Errorf("cast form must also LABEL, got %q", v[0].Action) } } func TestCheckDeclaredSubtype(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, "rate_limit": {}, "invalid_parameters": {}, } cases := []struct { name string src string wantAction Action wantInMsg string }{ { name: "named_const_selector_accepted", src: `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.SubtypeMissingScope} `, wantAction: "", }, { name: "literal_in_allowlist_accepted", src: `package x var _ = struct{ Subtype string }{Subtype: "missing_scope"} `, wantAction: "", }, { name: "undeclared_literal_rejected", src: `package x var _ = struct{ Subtype string }{Subtype: "my_custom_thing"} `, wantAction: ActionReject, wantInMsg: "my_custom_thing", }, { name: "undeclared_via_cast_rejected", src: `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.Subtype("custom_value")} `, wantAction: ActionReject, wantInMsg: "custom_value", }, { name: "ad_hoc_does_not_fire_in_rule_e", src: `package x var _ = struct{ Subtype string }{Subtype: "ad_hoc_thing"} `, // CheckDeclaredSubtype hands ad_hoc_* off to CheckAdHocSubtype — returns no E-class violation. wantAction: "", }, { name: "dynamic_local_var_warns", src: `package x var loc = "x" var _ = struct{ Subtype string }{Subtype: loc} `, wantAction: ActionWarning, wantInMsg: "manual review", }, { name: "dynamic_cast_warns", src: `package x import "github.com/larksuite/cli/errs" func f(raw string) { _ = struct{ Subtype errs.Subtype }{Subtype: errs.Subtype(raw)} } `, wantAction: ActionWarning, wantInMsg: "non-literal", }, } for _, tc := range cases { t.Run(tc.name, func(t *testing.T) { v := CheckDeclaredSubtype("x.go", tc.src, allowlist) if tc.wantAction == "" { if len(v) != 0 { t.Fatalf("expected pass, got %d violations: %+v", len(v), v) } return } if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != tc.wantAction { t.Errorf("action = %q, want %q", v[0].Action, tc.wantAction) } if tc.wantInMsg != "" && !strings.Contains(v[0].Message, tc.wantInMsg) { t.Errorf("message %q lacks expected substring %q", v[0].Message, tc.wantInMsg) } }) } } // TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteral pins that codemeta_task.go and // codemeta.go use positional `{cat, subtype, retryable}` literals inside a // `map[int]CodeMeta{...}` — element [1] is the Subtype slot. The AST walker // must recognise the positional form; otherwise an undeclared subtype cast // here would bypass CheckDeclaredSubtype. func TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteral(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, } src := `package output import "github.com/larksuite/cli/errs" type CodeMeta struct { Category errs.Category Subtype errs.Subtype Retryable bool } var m = map[int]CodeMeta{ 1: {errs.CategoryAPI, errs.Subtype("totally_bogus_undeclared"), false}, } ` v := CheckDeclaredSubtype("internal/output/codemeta_test_fixture.go", src, allowlist) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "totally_bogus_undeclared") { t.Errorf("message should name the violating subtype: %s", v[0].Message) } } // TestCheckDeclaredSubtype_AcceptsPositionalCodeMetaLiteral: same positional form but the // Subtype literal is in the allowlist — no violation should fire. func TestCheckDeclaredSubtype_AcceptsPositionalCodeMetaLiteral(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, } src := `package output import "github.com/larksuite/cli/errs" type CodeMeta struct { Category errs.Category Subtype errs.Subtype Retryable bool } var m = map[int]CodeMeta{ 1: {errs.CategoryAuthorization, errs.SubtypeMissingScope, false}, 2: {errs.CategoryAuthorization, errs.Subtype("missing_scope"), false}, } ` v := CheckDeclaredSubtype("internal/output/codemeta_test_fixture.go", src, allowlist) if len(v) != 0 { t.Errorf("allowlisted subtypes in positional form must pass, got: %+v", v) } } // TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteralInSlice: covers the slice form // `[]CodeMeta{{cat, subtype, retryable}}` so other call-site shapes are also // guarded. func TestCheckDeclaredSubtype_DetectsPositionalCodeMetaLiteralInSlice(t *testing.T) { allowlist := map[string]struct{}{ "missing_scope": {}, } src := `package output import "github.com/larksuite/cli/errs" type CodeMeta struct { Category errs.Category Subtype errs.Subtype Retryable bool } var s = []CodeMeta{ {errs.CategoryAPI, errs.Subtype("undeclared_via_slice"), false}, } ` v := CheckDeclaredSubtype("internal/output/codemeta_test_fixture.go", src, allowlist) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "undeclared_via_slice") { t.Errorf("message should name the violating subtype: %s", v[0].Message) } } // TestCheckDeclaredSubtype_WithNames_RejectsTypoedSelector pins the strengthened CheckDeclaredSubtype: // when a nameset is supplied, selectors like `errs.SubtypeBogus` that satisfy // the "Subtype*" prefix but reference no declared constant must REJECT. The // nil-nameset path preserves the legacy prefix-only acceptance. func TestCheckDeclaredSubtype_WithNames_RejectsTypoedSelector(t *testing.T) { allowlist := map[string]struct{}{"missing_scope": {}} nameset := map[string]struct{}{"SubtypeMissingScope": {}} // Typo'd selector — REJECT under strengthened rule. src := `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.SubtypeBogus} ` v := CheckDeclaredSubtypeWithNames("x.go", src, allowlist, nameset) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "SubtypeBogus") { t.Errorf("message should name the offending selector: %s", v[0].Message) } // Same source, nil nameset → legacy prefix-only path, no violation. v2 := CheckDeclaredSubtypeWithNames("x.go", src, allowlist, nil) if len(v2) != 0 { t.Errorf("nil nameset must preserve legacy prefix acceptance, got: %+v", v2) } } // TestCheckDeclaredSubtype_WithNames_AcceptsDeclaredSelector: declared selector with nameset // supplied must still pass. func TestCheckDeclaredSubtype_WithNames_AcceptsDeclaredSelector(t *testing.T) { allowlist := map[string]struct{}{"missing_scope": {}} nameset := map[string]struct{}{"SubtypeMissingScope": {}} src := `package x import "github.com/larksuite/cli/errs" var _ = struct{ Subtype errs.Subtype }{Subtype: errs.SubtypeMissingScope} ` v := CheckDeclaredSubtypeWithNames("x.go", src, allowlist, nameset) if len(v) != 0 { t.Errorf("declared selector must pass, got: %+v", v) } } // TestCheckDeclaredSubtype_WithNames_RejectsTypoedIdent: in-package identifier form (no errs. // prefix) must also be checked against the nameset. func TestCheckDeclaredSubtype_WithNames_RejectsTypoedIdent(t *testing.T) { allowlist := map[string]struct{}{"missing_scope": {}} nameset := map[string]struct{}{"SubtypeMissingScope": {}} src := `package errs type Subtype string type myErr struct{ Subtype Subtype } var _ = myErr{Subtype: SubtypeNotDeclared} ` v := CheckDeclaredSubtypeWithNames("internal/x.go", src, allowlist, nameset) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "SubtypeNotDeclared") { t.Errorf("message should name the offending identifier: %s", v[0].Message) } } func TestCheckDeclaredSubtype_NilAllowlist_IsNoOp(t *testing.T) { // Caller can disable CheckDeclaredSubtype by passing nil; that should not panic and must // not emit any E-class violation, even on undeclared subtypes. src := `package x var _ = struct{ Subtype string }{Subtype: "anything"} ` v := CheckDeclaredSubtype("x.go", src, nil) if len(v) != 0 { t.Errorf("nil allowlist must disable CheckDeclaredSubtype, got: %+v", v) } } // TestRunAll_OneFileFourViolations exercises the combined entry point: a // synthetic file under shortcuts/ that violates B, C, D, and E together. func TestRunAll_OneFileFourViolations(t *testing.T) { // Path is shortcuts/* so CheckNoRegistrar fires; file declared in errs-like package // header is irrelevant for B (we test B in errs/ files only via path). src := `package task type LooseError struct{} func init() { mergeCodeMeta(nil, "task") } var _ = struct{ Subtype string }{Subtype: "ad_hoc_thing"} var _ = struct{ Subtype string }{Subtype: "bogus"} ` allowlist := map[string]struct{}{ "missing_scope": {}, } v := RunAll("shortcuts/task/all_bad.go", src, allowlist) byRule := map[string]int{} byAction := map[Action]int{} for _, vv := range v { byRule[vv.Rule]++ byAction[vv.Action]++ } // CheckProblemEmbed is path-scoped to errs/, so it does NOT fire on shortcuts/. if byRule["problem_embed"] != 0 { t.Errorf("CheckProblemEmbed should not fire outside errs/, got %d", byRule["problem_embed"]) } if byRule["no_registrar"] != 1 { t.Errorf("CheckNoRegistrar count = %d, want 1", byRule["no_registrar"]) } if byRule["adhoc_subtype"] != 1 { t.Errorf("CheckAdHocSubtype count = %d, want 1", byRule["adhoc_subtype"]) } if byRule["declared_subtype"] != 1 { t.Errorf("CheckDeclaredSubtype count = %d, want 1", byRule["declared_subtype"]) } if byAction[ActionReject] != 2 { t.Errorf("REJECT count = %d, want 2 (Rules C+E)", byAction[ActionReject]) } if byAction[ActionLabel] != 1 { t.Errorf("LABEL count = %d, want 1 (CheckAdHocSubtype)", byAction[ActionLabel]) } } func TestRunAll_ErrsPathRunsRuleB(t *testing.T) { src := `package errs type NoEmbedError struct { Code int } ` v := RunAll("errs/types.go", src, nil) if len(v) != 1 || v[0].Rule != "problem_embed" { t.Fatalf("expected one CheckProblemEmbed violation, got %+v", v) } } // TestCheckProblemEmbed_SkipsUnexportedErrorType pins that CheckProblemEmbed only // enforces the Problem embed on EXPORTED *Error types — unexported helper // types that happen to end in "Error" are not part of the public taxonomy // and would create false-positive REJECT violations. func TestCheckProblemEmbed_SkipsUnexportedErrorType(t *testing.T) { src := `package internal type myInternalError struct { Code int Msg string } ` v := CheckProblemEmbed("internal/foo/internal.go", src) if len(v) != 0 { t.Errorf("expected 0 violations for unexported helper, got %d: %+v", len(v), v) } } // TestCheckNoRegistrar_CatchesMiddleAffix pins that the registrar matcher // catches RegisterServiceMap even when it has affixes on both sides — the // older prefix-or-suffix-only check would have missed FooRegisterServiceMapBar. func TestCheckNoRegistrar_CatchesMiddleAffix(t *testing.T) { src := `package auth func init() { FooRegisterServiceMapBar("auth", nil) } func FooRegisterServiceMapBar(name string, _ interface{}) {} ` v := CheckNoRegistrar("internal/auth/init.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for middle-affix registrar, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "FooRegisterServiceMapBar") { t.Errorf("message must name the offending call: %s", v[0].Message) } } // (F) direct legacy output.ExitError / output.ErrDetail literals on migrated // paths → REJECT; output.ErrBare(...) calls and non-migrated paths pass. func TestCheckNoLegacyEnvelopeLiteral_RejectsExitErrorLiteralOnDrivePath(t *testing.T) { src := `package drive import "github.com/larksuite/cli/internal/output" func boom() error { return &output.ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "ExitError") { t.Errorf("message should name the legacy type: %s", v[0].Message) } } func TestCheckNoLegacyEnvelopeLiteral_RejectsExitErrorLiteralOnMigratedShortcutPaths(t *testing.T) { for _, path := range []string{ "shortcuts/markdown/markdown_fetch.go", "shortcuts/okr/okr_image_upload.go", "shortcuts/task/task_update.go", "shortcuts/whiteboard/whiteboard_update.go", } { t.Run(path, func(t *testing.T) { src := `package migrated import "github.com/larksuite/cli/internal/output" func boom() error { return &output.ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral(path, src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "ExitError") { t.Errorf("message should name the legacy type: %s", v[0].Message) } }) } } func TestCheckNoLegacyEnvelopeLiteral_RejectsErrDetailLiteralOnDrivePath(t *testing.T) { src := `package drive import "github.com/larksuite/cli/internal/output" func boom() *output.ErrDetail { return &output.ErrDetail{Code: 7} } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export_common.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "ErrDetail") { t.Errorf("message should name the legacy type: %s", v[0].Message) } } func TestCheckNoLegacyEnvelopeLiteral_AllowsErrBareCallOnDrivePath(t *testing.T) { // output.ErrBare(...) is a CallExpr, not a CompositeLit — must NOT fire. src := `package drive import "github.com/larksuite/cli/internal/output" func boom() error { return output.ErrBare(output.ExitAPI) } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export.go", src) if len(v) != 0 { t.Errorf("ErrBare call should pass, got: %+v", v) } } func TestCheckNoLegacyEnvelopeLiteral_FiresOnAnyPath(t *testing.T) { // The guard is now repo-wide: any .go path that re-introduces the legacy // literal is flagged, regardless of domain. for _, path := range []string{ "shortcuts/im/im_send.go", "shortcuts/some_new_domain/foo.go", "internal/auth/login.go", "cmd/config/bind.go", } { t.Run(path, func(t *testing.T) { src := `package other import "github.com/larksuite/cli/internal/output" func boom() error { return &output.ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral(path, src) if len(v) != 1 { t.Fatalf("expected 1 violation on %s, got %d: %+v", path, len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } }) } } func TestCheckNoLegacyEnvelopeLiteral_SkipsTestFiles(t *testing.T) { src := `package drive import "github.com/larksuite/cli/internal/output" func boom() error { return &output.ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export_test.go", src) if len(v) != 0 { t.Errorf("_test.go file should be skipped, got: %+v", v) } } // TestCheckNoLegacyEnvelopeLiteral_RejectsAliasedImport pins that an aliased // import of internal/output cannot bypass the rule: the qualifier is resolved // from the import declaration, not matched against the literal string "output". func TestCheckNoLegacyEnvelopeLiteral_RejectsAliasedImport(t *testing.T) { src := `package drive import legacy "github.com/larksuite/cli/internal/output" func boom() error { return &legacy.ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for aliased import, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "ExitError") { t.Errorf("message should name the legacy type: %s", v[0].Message) } } // TestCheckNoLegacyEnvelopeLiteral_NormalImportStillRejected guards against a // regression where resolving by import path accidentally drops the default // (non-aliased) `output` case. func TestCheckNoLegacyEnvelopeLiteral_NormalImportStillRejected(t *testing.T) { src := `package drive import "github.com/larksuite/cli/internal/output" func boom() error { return &output.ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for default import, got %d: %+v", len(v), v) } } // TestCheckNoLegacyEnvelopeLiteral_ErrBareAliasedStillAllowed: output.ErrBare is // a CallExpr, not a composite literal — even under an alias it must not fire. func TestCheckNoLegacyEnvelopeLiteral_ErrBareAliasedStillAllowed(t *testing.T) { src := `package drive import legacy "github.com/larksuite/cli/internal/output" func boom() error { return legacy.ErrBare(legacy.ExitAPI) } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export.go", src) if len(v) != 0 { t.Errorf("ErrBare call should pass, got: %+v", v) } } // TestCheckNoLegacyEnvelopeLiteral_RejectsDotImport: a dot-import surfaces // ExitError / ErrDetail as bare unqualified idents; the rule must still catch // the composite literal. func TestCheckNoLegacyEnvelopeLiteral_RejectsDotImport(t *testing.T) { src := `package drive import . "github.com/larksuite/cli/internal/output" func boom() error { return &ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for dot-import, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "ExitError") { t.Errorf("message should name the legacy type: %s", v[0].Message) } } // TestCheckNoLegacyEnvelopeLiteral_UnrelatedSelectorPasses: a same-named // selector on an unrelated package (not the legacy output import path) must not // trigger a false positive. func TestCheckNoLegacyEnvelopeLiteral_UnrelatedSelectorPasses(t *testing.T) { src := `package drive import "example.com/other/output" func boom() error { return &output.ExitError{Code: 1} } ` v := CheckNoLegacyEnvelopeLiteral("shortcuts/drive/drive_export.go", src) if len(v) != 0 { t.Errorf("unrelated package selector must not fire, got: %+v", v) } } func TestCheckNoLegacyRuntimeAPICall_RejectsCallAPIOnDrivePath(t *testing.T) { src := `package drive import "github.com/larksuite/cli/shortcuts/common" func boom(runtime *common.RuntimeContext) error { _, err := runtime.CallAPI("POST", "/x", nil, nil) return err } ` v := CheckNoLegacyRuntimeAPICall("shortcuts/drive/drive_create_folder.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "CallAPI") { t.Errorf("message should name the legacy method: %s", v[0].Message) } } func TestCheckNoLegacyRuntimeAPICall_RejectsCallAPIOnTaskPath(t *testing.T) { src := `package task import "github.com/larksuite/cli/shortcuts/common" func boom(runtime *common.RuntimeContext) error { _, err := runtime.CallAPI("POST", "/x", nil, nil) return err } ` v := CheckNoLegacyRuntimeAPICall("shortcuts/task/task_update.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "CallAPI") { t.Errorf("message should name the legacy method: %s", v[0].Message) } } func TestCheckNoLegacyRuntimeAPICall_RejectsDoAPIJSONWithLogIDOnDrivePath(t *testing.T) { src := `package drive import "github.com/larksuite/cli/shortcuts/common" func boom(runtime *common.RuntimeContext) error { _, err := runtime.DoAPIJSONWithLogID("POST", "/x", nil, nil) return err } ` v := CheckNoLegacyRuntimeAPICall("shortcuts/drive/drive_export.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if !strings.Contains(v[0].Message, "DoAPIJSONWithLogID") { t.Errorf("message should name the legacy method: %s", v[0].Message) } } func TestCheckNoLegacyRuntimeAPICall_AllowsTypedWrapperCall(t *testing.T) { // driveCallAPI is an unqualified call (*ast.Ident), not a selector — must NOT fire. src := `package drive func boom(runtime *common.RuntimeContext) error { _, err := driveCallAPI(runtime, "POST", "/x", nil, nil) return err } ` v := CheckNoLegacyRuntimeAPICall("shortcuts/drive/drive_create_folder.go", src) if len(v) != 0 { t.Errorf("typed wrapper call must not fire, got: %+v", v) } } func TestCheckNoLegacyRuntimeAPICall_AllowsRawAPIAndDoAPI(t *testing.T) { // RawAPI / DoAPI return the raw response for the caller to classify and do // not emit a legacy envelope — they are not banned. src := `package drive func boom(runtime *common.RuntimeContext) error { _, _ = runtime.RawAPI("POST", "/x", nil, nil) _, err := runtime.DoAPI(nil) return err } ` v := CheckNoLegacyRuntimeAPICall("shortcuts/drive/drive_api.go", src) if len(v) != 0 { t.Errorf("RawAPI / DoAPI must not fire, got: %+v", v) } } func TestCheckNoLegacyRuntimeAPICall_FiresOnAnyCommonImportingPath(t *testing.T) { // The guard is now repo-wide: any path importing shortcuts/common that // re-introduces a legacy runtime call is flagged, regardless of domain. for _, path := range []string{ "shortcuts/im/im_send.go", "shortcuts/some_new_domain/sample.go", "internal/cmdutil/helper.go", } { t.Run(path, func(t *testing.T) { src := `package contact import "github.com/larksuite/cli/shortcuts/common" func boom(runtime *common.RuntimeContext) error { _, err := runtime.CallAPI("POST", "/x", nil, nil) return err } ` v := CheckNoLegacyRuntimeAPICall(path, src) if len(v) != 1 { t.Fatalf("expected 1 violation on %s, got %d: %+v", path, len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } }) } } func TestCheckNoLegacyRuntimeAPICall_SkipsFilesWithoutCommonImport(t *testing.T) { // The import gate stays: without a shortcuts/common import, a same-named // CallAPI method on another receiver is not the legacy RuntimeContext helper. src := `package contact func boom(runtime *common.RuntimeContext) error { _, err := runtime.CallAPI("POST", "/x", nil, nil) return err } ` v := CheckNoLegacyRuntimeAPICall("shortcuts/some_new_domain/sample.go", src) if len(v) != 0 { t.Errorf("file without shortcuts/common import must not fire, got: %+v", v) } } func TestCheckNoLegacyRuntimeAPICall_SkipsTestFiles(t *testing.T) { src := `package drive func boom(runtime *common.RuntimeContext) error { _, err := runtime.CallAPI("POST", "/x", nil, nil) return err } ` v := CheckNoLegacyRuntimeAPICall("shortcuts/drive/drive_create_folder_test.go", src) if len(v) != 0 { t.Errorf("test files must be skipped, got: %+v", v) } } func TestCheckNoLegacyCommonHelperCall_RejectsLegacyHelpersOnMigratedPath(t *testing.T) { helpers := []string{ "FlagErrorf", "MutuallyExclusive", "AtLeastOne", "ExactlyOne", "ValidatePageSize", "ValidateChatID", "ValidateUserID", "ValidateSafePath", "RejectDangerousChars", "WrapInputStatError", "WrapSaveErrorByCategory", "ResolveOpenIDs", "HandleApiResult", } paths := []string{ "shortcuts/doc/docs_fetch_v2.go", "shortcuts/drive/drive_search.go", "shortcuts/im/im_messages_send.go", "shortcuts/mail/mail_send.go", "shortcuts/markdown/markdown_fetch.go", "shortcuts/okr/okr_progress_create.go", "shortcuts/sheets/helpers.go", "shortcuts/slides/slides_create.go", "shortcuts/task/task_update.go", "shortcuts/whiteboard/whiteboard_query.go", "shortcuts/wiki/wiki_node_get.go", } for _, path := range paths { for _, helper := range helpers { t.Run(path+"_"+helper, func(t *testing.T) { src := `package migrated import "github.com/larksuite/cli/shortcuts/common" func boom() { common.` + helper + `() } ` v := CheckNoLegacyCommonHelperCall(path, src) if len(v) != 1 { t.Fatalf("expected 1 violation for %s on %s, got %d: %+v", helper, path, len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Message, "common."+helper) { t.Errorf("message should name helper %s: %s", helper, v[0].Message) } }) } } } func TestCheckNoLegacyCommonHelperCall_RejectsDangerousCharsOnCalendarPath(t *testing.T) { src := `package calendar import "github.com/larksuite/cli/shortcuts/common" func boom() { common.RejectDangerousChars("--summary", "x") } ` v := CheckNoLegacyCommonHelperCall("shortcuts/calendar/calendar_create.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation, got %d: %+v", len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } if !strings.Contains(v[0].Suggestion, "common.RejectDangerousCharsTyped") { t.Errorf("suggestion should name typed replacement, got: %s", v[0].Suggestion) } } func TestCheckNoLegacyCommonHelperCall_CoversDocPathWithAliasAndFunctionValue(t *testing.T) { src := `package migrated import c "github.com/larksuite/cli/shortcuts/common" func boom() { f := c.FlagErrorf _ = f c.WrapInputStatError(nil) } ` v := CheckNoLegacyCommonHelperCall("shortcuts/doc/docs_fetch_v2.go", src) if len(v) != 2 { t.Fatalf("expected 2 violations for aliased/function-value legacy helpers on doc path, got %d: %+v", len(v), v) } } func TestCheckNoLegacyCommonHelperCall_CoversSheetsPathWithAliasAndFunctionValue(t *testing.T) { src := `package migrated import c "github.com/larksuite/cli/shortcuts/common" func boom() { f := c.FlagErrorf _ = f c.WrapInputStatError(nil) } ` v := CheckNoLegacyCommonHelperCall("shortcuts/sheets/helpers.go", src) if len(v) != 2 { t.Fatalf("expected 2 violations for aliased/function-value legacy helpers on sheets path, got %d: %+v", len(v), v) } } func TestCheckNoLegacyCommonHelperCall_CoversSlidesPathWithAliasAndFunctionValue(t *testing.T) { src := `package migrated import c "github.com/larksuite/cli/shortcuts/common" func boom() { f := c.FlagErrorf _ = f c.WrapInputStatError(nil) } ` v := CheckNoLegacyCommonHelperCall("shortcuts/slides/slides_create.go", src) if len(v) != 2 { t.Fatalf("expected 2 violations for aliased/function-value legacy helpers on slides path, got %d: %+v", len(v), v) } } func TestCheckNoLegacyCommonHelperCall_CoversMarkdownPathWithAliasAndFunctionValue(t *testing.T) { src := `package migrated import c "github.com/larksuite/cli/shortcuts/common" func boom() { f := c.FlagErrorf _ = f c.WrapInputStatError(nil) } ` v := CheckNoLegacyCommonHelperCall("shortcuts/markdown/markdown_fetch.go", src) if len(v) != 2 { t.Fatalf("expected 2 violations for aliased/function-value legacy helpers on markdown path, got %d: %+v", len(v), v) } } func TestCheckNoLegacyCommonHelperCall_CoversWikiPathWithAliasAndFunctionValue(t *testing.T) { src := `package migrated import c "github.com/larksuite/cli/shortcuts/common" func boom() { f := c.FlagErrorf _ = f c.WrapInputStatError(nil) } ` v := CheckNoLegacyCommonHelperCall("shortcuts/wiki/wiki_node_get.go", src) if len(v) != 2 { t.Fatalf("expected 2 violations for aliased/function-value legacy helpers on wiki path, got %d: %+v", len(v), v) } } func TestCheckNoLegacyCommonHelperCall_FiresOnAnyPath(t *testing.T) { // The guard is now repo-wide: re-introducing a legacy common helper is // flagged regardless of domain. for _, path := range []string{ "shortcuts/im/im_send.go", "shortcuts/some_new_domain/sample.go", "internal/cmdutil/helper.go", } { t.Run(path, func(t *testing.T) { src := `package contact import "github.com/larksuite/cli/shortcuts/common" func boom() { common.FlagErrorf("relapse") } ` v := CheckNoLegacyCommonHelperCall(path, src) if len(v) != 1 { t.Fatalf("expected 1 violation on %s, got %d: %+v", path, len(v), v) } if v[0].Action != ActionReject { t.Errorf("action = %q, want REJECT", v[0].Action) } }) } } func TestCheckNoLegacyCommonHelperCall_RejectsReintroducedUploadAndCallAPIHelpers(t *testing.T) { // The three relapse-guard entries added when the legacy bodies were deleted: // re-introducing a same-named helper must be rejected with a typed pointer. cases := []struct { helper string wantInSugg string }{ {"UploadDriveMediaAll", "common.UploadDriveMediaAllTyped"}, {"UploadDriveMediaMultipart", "common.UploadDriveMediaMultipartTyped"}, {"CallAPI", "runtime.CallAPITyped"}, } for _, tc := range cases { t.Run(tc.helper, func(t *testing.T) { src := `package drive import "github.com/larksuite/cli/shortcuts/common" func boom() { common.` + tc.helper + `() } ` v := CheckNoLegacyCommonHelperCall("shortcuts/drive/drive_upload.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for %s, got %d: %+v", tc.helper, len(v), v) } if !strings.Contains(v[0].Suggestion, tc.wantInSugg) { t.Errorf("suggestion should name typed replacement %q, got: %s", tc.wantInSugg, v[0].Suggestion) } }) } } func TestCheckNoLegacyCommonHelperCall_AllowsTypedHelpersOnMigratedPath(t *testing.T) { src := `package drive import "github.com/larksuite/cli/shortcuts/common" func boom() { common.ValidationErrorf("typed") common.MutuallyExclusiveTyped(nil, "a", "b") common.ValidateChatIDTyped("--chat-ids", "oc_abc") common.ResolveOpenIDsTyped("--user-ids", nil, nil) common.WrapSaveErrorTyped(nil) } ` v := CheckNoLegacyCommonHelperCall("shortcuts/drive/drive_search.go", src) if len(v) != 0 { t.Errorf("typed helpers must pass, got: %+v", v) } } func TestCheckNoLegacyCommonHelperCall_RejectsAliasedImport(t *testing.T) { src := `package drive import c "github.com/larksuite/cli/shortcuts/common" func boom() { c.FlagErrorf("legacy") } ` v := CheckNoLegacyCommonHelperCall("shortcuts/drive/drive_search.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for aliased common import, got %d: %+v", len(v), v) } } func TestCheckNoLegacyCommonHelperCall_RejectsDotImport(t *testing.T) { src := `package drive import . "github.com/larksuite/cli/shortcuts/common" func boom() { FlagErrorf("legacy") } ` v := CheckNoLegacyCommonHelperCall("shortcuts/drive/drive_search.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for dot-imported common, got %d: %+v", len(v), v) } } func TestCheckNoLegacyCommonHelperCall_RejectsFunctionValueReference(t *testing.T) { src := `package drive import "github.com/larksuite/cli/shortcuts/common" func boom() error { f := common.FlagErrorf return f("legacy") } ` v := CheckNoLegacyCommonHelperCall("shortcuts/drive/drive_search.go", src) if len(v) != 1 { t.Fatalf("expected 1 violation for function-value reference, got %d: %+v", len(v), v) } } func TestCheckNoLegacyRuntimeAPICall_SkipsNonCommonReceiver(t *testing.T) { // The event domain's APIClient interface has a same-named CallAPI method // whose implementation classifies into typed errs.* errors; without the // shortcuts/common import the call cannot be the legacy RuntimeContext // helper and must not fire. src := `package vc import "github.com/larksuite/cli/internal/event" func boom(rt event.APIClient) error { _, err := rt.CallAPI(nil, "POST", "/x", nil) return err } ` v := CheckNoLegacyRuntimeAPICall("events/vc/preconsume.go", src) if len(v) != 0 { t.Errorf("non-common CallAPI receiver must not fire, got: %+v", v) } }