lint

command module
v0.0.0-...-423e81f Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 21, 2026 License: MIT Imports: 7 Imported by: 0

README

lint/

Source-level static checks that guard lark-cli conventions golangci-lint cannot express. Each lint domain is a sibling Go package under lint/; the top-level lint/main.go aggregates results and emits a single exit code.

lint/ is its own Go module so its golang.org/x/tools/go/packages dependency does not leak into the shipped lark-cli binary's module graph.

Layout

lint/
├── go.mod              # module github.com/larksuite/cli/lint
├── go.sum
├── main.go             # package main — dispatches to every registered domain
├── lintapi/            # shared types every domain returns
│   └── violation.go    # Violation, Action, ActionReject / ActionLabel / ActionWarning
├── errscontract/       # first domain: typed-error contract guards
    ├── scan.go         # ScanRepoWithOptions(root, opts)  ← public entry
    ├── runner.go
    ├── typecheck.go
    ├── violation.go    # local type aliases to lintapi
    ├── rule_problem_embed.go
    ├── rule_no_registrar.go
    ├── rule_adhoc_subtype.go
    ├── rule_declared_subtype.go
    ├── rule_subtype_classifier.go
    ├── rule_typed_error_completeness.go
    └── *_test.go
├── domaincontract/     # resolver ownership + approved public hostname policy
    ├── scan.go         # ScanRepoWithOptions(root, opts)  ← public entry
    ├── unapproved.go   # Go AST/type-aware hostname extraction
    ├── policy.go       # exact public/fixture allowlist validation
    ├── diff.go         # added-line attribution
    └── *_test.go
└── flagcontract/       # framework ownership for flag aliases
    ├── scan.go         # rejects local name normalizers
    └── scan_test.go

Flag alias contract (flagcontract)

flagcontract keeps exact flag-name synonyms on the shared framework path. It rejects production calls to SetNormalizeFunc outside internal/flagalias. Exact synonyms belong in the canonical common.Flag.Aliases; legacy inputs with a different value grammar or meaning remain real hidden flags and normalize into canonical state inside the business-owned Shortcut.Normalize execution stage.

Endpoint domain contract (domaincontract)

domaincontract contains two complementary Go source guards.

The resolver-ownership guard rejects:

  • string literals containing a resolver-owned host FQDN ({open,accounts,mcp,applink}.{feishu.cn,larksuite.com}), and
  • direct references to the SDK base-URL globals (FeishuBaseUrl / LarkBaseUrl) selected off an import of the SDK root package, which pick a host without going through the resolver. Unrelated identifiers sharing the name are not flagged.

Host literals are permitted only inside the resolver's ResolveEndpoints function body (internal/core/types.go) and in this rule's own host list (lint/domaincontract/scan.go); a helper elsewhere in the resolver file returning a hardcoded host is still rejected. Comments and _test.go files are not scanned. Literals are unquoted before matching (escape sequences cannot hide a host) and match case-insensitively, and dot-imports of the SDK root package are rejected outright (they would hide the globals from this parse-level guard). The forbidden-host list is bound to the resolver source by TestForbiddenHostsMatchResolver, so adding a resolver domain without updating the guard fails the lint module's tests.

The approved-domain guard parses every Git-tracked Go file in full. In CI, unapproved-host findings are limited to values whose expressions intersect an added line; policy validation and unused-entry checks remain repository-wide. It rejects an exact hostname unless it is present in one of:

  • internal/qualitygate/config/allowlists/public-domains.txt, for production and test code; or
  • internal/qualitygate/config/allowlists/fixture-domains.txt, only for *_test.go, the repository-root tests/, and any testdata/ (never skills/).

RFC 2606 example/test names are accepted independently of those lists. This includes the reserved .test, .example, .invalid, and .localhost namespaces and the exact names example.com, example.net, and example.org; they are safe placeholders rather than supported public endpoints.

High-confidence evidence is deliberately limited to static string expressions assigned to host, hostname, or domain semantic names (including common case/plural forms and collections), plus static strings whose entire value is an absolute http, https, ws, or wss URL. It supports Go literals, escapes, compile-time concatenation, constant references, grouped declarations, multi-value assignments, and multiline expressions. Bare domain-shaped strings without hostname semantics are not blocked.

Sequence values are scanned individually. For a hostname-semantic map, a key or value is evidence only when it is the sole hostname-shaped side of that entry; ambiguous string-to-string entries are not guessed. Struct fields use Go type information so known non-network Host / Domain fields do not become hostname evidence merely because an enum or command category contains a dot.

Allowlist matching is lowercase and exact: there are no wildcard, suffix, DNS, or public-suffix exceptions. Entries must be sorted and unique, use ASCII hostnames, and have a current in-scope use. See internal/qualitygate/config/README.md for admission and approval rules.

This is not a general outbound-URL or cross-language data-flow analyzer. It does not inspect non-Go assets or dynamically constructed values.

To add or change a resolver-owned Feishu/Lark endpoint, edit the resolver rather than hardcoding the host elsewhere.

Running

# PR-scoped scan from the repo root (one level above lint/)
go run -C lint . --changed-from <base-revision> ..

# Full inventory (also reports historical unapproved hostnames)
go run -C lint . ..

-C lint switches Go's working directory to lint/; the .. argument is the repo root to scan (relative to lint/).

CI: .github/workflows/ci.yml step Run source-contract lint guards (lintcheck).

Exit codes follow lint/main.go:

Code Meaning
0 no REJECT diagnostics (LABEL / WARNING are advisory)
1 one or more REJECT diagnostics
2 a domain's ScanRepo returned an error

Adding a new lint domain

  1. Create a sibling package: lint/<domain>/. Pick a name that reads like a category, not a list of rules (errscontract/ covers many error-contract rules; flagnaming/ would cover many flag-related rules).

  2. Inside the new package, expose one public entry:

    package <domain>
    
    import "github.com/larksuite/cli/lint/lintapi"
    
    type ScanOptions struct {
        ChangedFrom string
    }
    
    // ScanRepoWithOptions walks root and returns every violation produced
    // by this domain's checks. Domains MUST return []lintapi.Violation so
    // the top-level dispatcher can aggregate uniformly.
    func ScanRepoWithOptions(root string, opts ScanOptions) ([]lintapi.Violation, error) { ... }
    
  3. Per-rule files are named rule_<name>.go with sibling rule_<name>_test.go. Each rule function returns []lintapi.Violation. runner.go (or scan.go) composes the rules.

  4. Register the domain in lint/main.go:

    var scanners = []scanner{
        {name: "errscontract", fn: errscontract.ScanRepoWithOptions},
        {name: "<domain>", fn: func(root string, opts errscontract.ScanOptions) ([]lintapi.Violation, error) {
            return <domain>.ScanRepoWithOptions(root, <domain>.ScanOptions{
                ChangedFrom: opts.ChangedFrom,
            })
        }},
    }
    
  5. Verify locally:

    go test  -C lint ./...      # all domains' tests
    go run   -C lint . ..       # full scan against the repo
    
  6. Document the rules. If they enforce a contract that already has a spec (e.g. errs/ERROR_CONTRACT.md), add the lint entry to that contract's "CI guards" table. Otherwise create a short spec alongside the package.

Rule severity conventions (lintapi.Action)

Action Effect When to use
ActionReject exit 1, fails CI a contract violation that must be fixed before merge
ActionLabel stderr only; CI can grep for [needs-taxonomy-decision] and label the PR governance signal that asks a human to choose (e.g. ad_hoc_* subtype needs a taxonomy decision)
ActionWarning stderr only advisory hint surfaced to reviewers (typed scope unavailable, fallback to AST-only, etc.) — never gates merges

Only ActionReject contributes to a nonzero exit code; ActionLabel and ActionWarning are reviewer signal only.

Documentation

Overview

Command lintcheck runs repository source-contract guards that golangci-lint cannot express directly. It currently covers typed-error contracts and the resolver-owned endpoint and approved-domain contracts.

lintcheck lives in its own Go module under lint/ so its build-time dependency on golang.org/x/tools/go/packages does not leak into the shipped lark-cli binary's module graph.

Usage (from repo root):

go run -C lint . .                # scan the lark-cli repo
go run -C lint . /path/to/repo    # scan another path

Exit codes:

0  no REJECT violations (LABEL and WARNING diagnostics are advisory)
1  one or more REJECT violations

WARNING and LABEL diagnostics are still printed so a CI workflow can grep for the prefixes — LABEL emits `[needs-taxonomy-decision]` for an auto-labeler — but neither severity fails CI. Only REJECT does.

Directories

Path Synopsis
Package domaincontract guards resolver ownership and rejects newly introduced static Go hostnames that are not covered by the repository domain policy.
Package domaincontract guards resolver ownership and rejects newly introduced static Go hostnames that are not covered by the repository domain policy.
Package flagcontract keeps flag aliases on the shared framework path.
Package flagcontract keeps flag aliases on the shared framework path.
Package lintapi defines the shared types every lint domain returns from its scan entry point.
Package lintapi defines the shared types every lint domain returns from its scan entry point.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL