| 35 | } |
| 36 | |
| 37 | func ScanRepoWithOptions(root string, opts ScanOptions) ([]Violation, error) { |
| 38 | allowlist, nameset, err := LoadSubtypeAllowlists(filepath.Join(root, "errs")) |
| 39 | if err != nil { |
| 40 | // "Subtype allowlist file missing" → skip CheckDeclaredSubtype; CheckAdHocSubtype still |
| 41 | // catches ad_hoc_*. Any other error (permission, malformed source) |
| 42 | // must propagate — otherwise a real taxonomy regression silently |
| 43 | // disables CheckDeclaredSubtype in CI. |
| 44 | if !os.IsNotExist(err) { |
| 45 | return nil, fmt.Errorf("load subtype allowlists: %w", err) |
| 46 | } |
| 47 | allowlist = nil |
| 48 | nameset = nil |
| 49 | } |
| 50 | commandErrorAllow, commandErrorAllowDiags, err := LoadLegacyCommandErrorAllowlistWithDiagnostics(root) |
| 51 | if err != nil { |
| 52 | return nil, fmt.Errorf("load legacy command error allowlist: %w", err) |
| 53 | } |
| 54 | changedFiles, err := changedFilesFrom(root, opts.ChangedFrom) |
| 55 | if err != nil { |
| 56 | return nil, err |
| 57 | } |
| 58 | commandErrorOptions := CommandErrorOptions{ |
| 59 | Allow: commandErrorAllow, |
| 60 | ChangedFiles: changedFiles, |
| 61 | ChangedOnly: opts.ChangedFrom != "", |
| 62 | } |
| 63 | |
| 64 | var all []Violation |
| 65 | all = append(all, commandErrorAllowDiags...) |
| 66 | observedCommandErrorAllowlist := map[fileLine]bool{} |
| 67 | |
| 68 | // CheckProblemEmbed: errs/ contract parity (types ↔ predicates ↔ tests ↔ docs). |
| 69 | if contractViols, err := CheckErrsContract(root); err == nil { |
| 70 | all = append(all, contractViols...) |
| 71 | } else if !os.IsNotExist(err) { |
| 72 | return nil, fmt.Errorf("rule B: %w", err) |
| 73 | } |
| 74 | |
| 75 | // CheckDeclaredSubtype typed resolution: load the workspace's type info once so we |
| 76 | // can verify Subtype selectors resolve into the canonical errs package. |
| 77 | // A loader failure or empty result falls back to the AST-only pass — |
| 78 | // the unit-test API path that ScanRepo shares with |
| 79 | // CheckDeclaredSubtypeWithNames already enforces nameset matching. |
| 80 | // When the fallback is taken on a workspace that LOOKS like a Go repo |
| 81 | // (has a go.mod), we emit a single advisory diagnostic so reviewers |
| 82 | // know CheckDeclaredSubtype ran in a less-strict mode this run. ActionWarning is |
| 83 | // print-only per Action semantics; it does not fail CI. |
| 84 | typedScope, typedErr := LoadTypedScope(root) |
| 85 | if typedErr != nil { |
| 86 | typedScope = nil |
| 87 | } |
| 88 | if !typedScope.Enabled() && hasGoMod(root) { |
| 89 | all = append(all, Violation{ |
| 90 | Rule: "declared_subtype", |
| 91 | Action: ActionWarning, |
| 92 | File: "lint", |
| 93 | Line: 0, |
| 94 | Message: "CheckDeclaredSubtype typed resolution unavailable; falling back to AST name matching. " + |