patterns

package
v1.4.2 Latest Latest
Warning

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

Go to latest
Published: Aug 11, 2026 License: MIT Imports: 25 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AnonInterfaceDegradationRule added in v1.4.2

type AnonInterfaceDegradationRule struct {
	*rules.BaseRule
}

AnonInterfaceDegradationRule detects type assertions on anonymous interfaces followed by silent degradation returns. This pattern often indicates dead delegation code.

Catches patterns like:

if x.(interface{ Method() Type }); ok {
    return x.Method()
}
return zeroValue // Problem: silently degrades

This violates "Fail explicitly, never degrade silently"

func NewAnonInterfaceDegradationRule added in v1.4.2

func NewAnonInterfaceDegradationRule() *AnonInterfaceDegradationRule

NewAnonInterfaceDegradationRule creates the rule

func (*AnonInterfaceDegradationRule) AnalyzeFile added in v1.4.2

AnalyzeFile checks for anonymous interface degradation patterns

type AppendAssignRule

type AppendAssignRule struct {
	*rules.BaseRule
}

AppendAssignRule detects append() calls without assignment

func NewAppendAssignRule

func NewAppendAssignRule() *AppendAssignRule

NewAppendAssignRule creates the rule

func (*AppendAssignRule) AnalyzeFile

func (r *AppendAssignRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for append without assignment

type AuditActorPropagationRule added in v1.4.2

type AuditActorPropagationRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

AuditActorPropagationRule detects loss of human actor and audit source attribution before typed audit sinks.

func NewAuditActorPropagationRule added in v1.4.2

func NewAuditActorPropagationRule() *AuditActorPropagationRule

NewAuditActorPropagationRule creates the package-level SSA rule.

func (*AuditActorPropagationRule) AnalyzeFile added in v1.4.2

AnalyzeFile is a no-op because this rule requires shared package SSA.

func (*AuditActorPropagationRule) AnalyzeGoProject added in v1.4.2

func (r *AuditActorPropagationRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject follows actor and source taint through context-sensitive SSA calls.

func (*AuditActorPropagationRule) Configure added in v1.4.2

func (r *AuditActorPropagationRule) Configure(settings map[string]any) error

Configure replaces the default sink names when sinks is explicitly set. Callers can supplement the defaults by including them in the configured list.

func (*AuditActorPropagationRule) RequiresSSA added in v1.4.2

func (r *AuditActorPropagationRule) RequiresSSA() bool

RequiresSSA reports that AnalyzeGoProject requires built SSA and its program.

type BoolCompareRule

type BoolCompareRule struct {
	*rules.BaseRule
}

BoolCompareRule detects redundant boolean comparisons

func NewBoolCompareRule

func NewBoolCompareRule() *BoolCompareRule

NewBoolCompareRule creates the rule

func (*BoolCompareRule) AnalyzeFile

func (r *BoolCompareRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for redundant boolean comparisons

type ConstructorNilReturnRule added in v1.4.2

type ConstructorNilReturnRule struct {
	*rules.BaseRule
}

ConstructorNilReturnRule detects constructors (New*) without an error result that can return nil:

func NewWalletRepository(userRepo interface{}) WalletRepository {
    repo, ok := userRepo.(*UserRepository)
    if !ok {
        return nil // callers silently receive a nil dependency
    }
    ...
}

Callers rarely nil-check constructor results, so the failure surfaces much later as a panic far from its cause. CLAUDE.md: initialization failures must be explicit — change the signature to (T, error).

Scope is kept narrow for precision: plain functions named New* with a single non-error result. Comma-ok contracts (T, bool), methods on factories, and closures are out of scope.

func NewConstructorNilReturnRule added in v1.4.2

func NewConstructorNilReturnRule() *ConstructorNilReturnRule

NewConstructorNilReturnRule creates the rule

func (*ConstructorNilReturnRule) AnalyzeFile added in v1.4.2

func (r *ConstructorNilReturnRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks Go constructors for silent nil returns

type ConstructorSwallowsNilDepRule added in v1.4.2

type ConstructorSwallowsNilDepRule struct {
	*rules.BaseRule
}

ConstructorSwallowsNilDepRule detects constructors that notice a nil dependency, log it — and build the object anyway:

func NewPermissionManager(repo Repository) *PermissionManager {
    if repo == nil {
        logger.Error("Critical: repo is nil") // and continues!
    }
    return &PermissionManager{repo: repo}
}

This is the "graceful degradation" anti-pattern forbidden by CLAUDE.md: the caller receives a half-alive object and the failure surfaces far from its cause. A nil dependency must abort construction with an error.

Not flagged: returning an error, panicking, or assigning a default to the parameter (options-defaulting), and Debug/Info-level notes.

func NewConstructorSwallowsNilDepRule added in v1.4.2

func NewConstructorSwallowsNilDepRule() *ConstructorSwallowsNilDepRule

NewConstructorSwallowsNilDepRule creates the rule

func (*ConstructorSwallowsNilDepRule) AnalyzeFile added in v1.4.2

AnalyzeFile checks Go constructors for swallowed nil dependencies

type ContextBackgroundRule

type ContextBackgroundRule struct {
	*rules.BaseRule
}

ContextBackgroundRule detects context.Background/TODO usage in functions that receive context

func NewContextBackgroundRule

func NewContextBackgroundRule() *ContextBackgroundRule

NewContextBackgroundRule creates the rule

func (*ContextBackgroundRule) AnalyzeFile

func (r *ContextBackgroundRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for context.Background/TODO misuse

type ContextFirstRule added in v1.4.2

type ContextFirstRule struct {
	*rules.BaseRule
}

ContextFirstRule detects a function that needs a context, does not accept one, and therefore invents its own:

func (s *Service) SyncWallet(address string) error {
    ctx := context.Background()          // the caller's deadline is gone
    return s.repo.Save(ctx, address)
}

The call chain silently stops being cancellable at this function: a request the user aborted keeps running, a shutdown waits for work nobody needs, and a deadline set three frames up applies to nothing below.

The rule looks at what the function does, not at what it is called: a function that hands no context to anything needs none, whatever its name. Functions that already accept a context are the business of context-background, which reports the same misuse from the other side.

Not flagged: package main and the entry points init/TestMain, where the root context of the program has to come from somewhere; contexts created for a goroutine that outlives the call; and contexts whose cancel function is stored rather than deferred, which belong to something started here and stopped elsewhere.

func NewContextFirstRule added in v1.4.2

func NewContextFirstRule() *ContextFirstRule

NewContextFirstRule creates the rule

func (*ContextFirstRule) AnalyzeFile added in v1.4.2

func (r *ContextFirstRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile reports every function that manufactures the context it should have been given.

type DeferInLoopRule

type DeferInLoopRule struct {
	*rules.BaseRule
}

DeferInLoopRule detects defer statements inside loops

func NewDeferInLoopRule

func NewDeferInLoopRule() *DeferInLoopRule

NewDeferInLoopRule creates the rule

func (*DeferInLoopRule) AnalyzeFile

func (r *DeferInLoopRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for defer inside loops

type DeprecatedIoutilRule

type DeprecatedIoutilRule struct {
	*rules.BaseRule
}

DeprecatedIoutilRule detects usage of deprecated io/ioutil package

func NewDeprecatedIoutilRule

func NewDeprecatedIoutilRule() *DeprecatedIoutilRule

NewDeprecatedIoutilRule creates the rule

func (*DeprecatedIoutilRule) AnalyzeFile

func (r *DeprecatedIoutilRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for io/ioutil usage

type DeprecatedNginxHTTP2ListenRule added in v1.4.2

type DeprecatedNginxHTTP2ListenRule struct {
	*rules.BaseRule
}

DeprecatedNginxHTTP2ListenRule detects syntax deprecated by Nginx 1.25.1.

func NewDeprecatedNginxHTTP2ListenRule added in v1.4.2

func NewDeprecatedNginxHTTP2ListenRule() *DeprecatedNginxHTTP2ListenRule

NewDeprecatedNginxHTTP2ListenRule creates the rule.

func (*DeprecatedNginxHTTP2ListenRule) AnalyzeFile added in v1.4.2

AnalyzeFile checks complete Nginx directives and ignores comments.

type DeterministicUUIDRule added in v1.4.2

type DeterministicUUIDRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

DeterministicUUIDRule detects patterns where UUIDs are generated from strings (email, namespace) instead of using real UUIDs from the database. Principle: "ID always comes from DB, never computed"

func NewDeterministicUUIDRule added in v1.4.2

func NewDeterministicUUIDRule() *DeterministicUUIDRule

NewDeterministicUUIDRule creates the rule

func (*DeterministicUUIDRule) AnalyzeFile added in v1.4.2

func (r *DeterministicUUIDRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for deterministic UUID generation patterns

type E2EBlindWaitRule added in v1.4.2

type E2EBlindWaitRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

E2EBlindWaitRule detects browser-test waits that are not tied to the fact under test: `waitForLoadState('networkidle')`, `goto(..., { waitUntil: 'networkidle' })` and `waitForTimeout(N)`.

Такое ожидание врёт в обе стороны. На странице с фоновыми запросами тишина в сети не наступает вовсе, и тест падает по таймауту, ничего не проверив; на странице, которая рисует данные вторым кадром, тишина наступает раньше отрисовки, и тест читает пустую разметку. Слепая пауза `waitForTimeout` добавляет к этому зависимость от скорости машины.

Проверяемый факт всегда конкретен: появился локатор, сменился URL, пришло значение. На него и надо ждать — тогда ожидание и есть проверка.

Третий случай того же семейства — проверка нового URL сразу после действия, которое этот переход запускает. Клиентский роутер уводит со страницы асинхронно, поэтому `expect(page.url()).toContain('/onboarding')` читает ещё старый адрес; ждать надо сам переход через waitForURL.

networkidle внутри try/catch не флагуется: там это мягкая синхронизация с явным запасным путём, а не единственное условие готовности.

func NewE2EBlindWaitRule added in v1.4.2

func NewE2EBlindWaitRule() *E2EBlindWaitRule

NewE2EBlindWaitRule creates the rule.

func (*E2EBlindWaitRule) AnalyzeFile added in v1.4.2

func (r *E2EBlindWaitRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile scans browser tests and their helpers line by line.

type EmptyBlockRule

type EmptyBlockRule struct {
	*rules.BaseRule
}

EmptyBlockRule detects empty if/for/switch blocks

func NewEmptyBlockRule

func NewEmptyBlockRule() *EmptyBlockRule

NewEmptyBlockRule creates the rule

func (*EmptyBlockRule) AnalyzeFile

func (r *EmptyBlockRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for empty blocks

type EmptyStructReturnRule added in v1.4.2

type EmptyStructReturnRule struct {
	*rules.BaseRule
}

EmptyStructReturnRule detects functions that return empty structs with nil error instead of returning explicit error. This violates "Fail explicitly, never degrade silently" Catches: return SafeDecimal{}, nil (in error context) Catches: return Config{} (without error, in error context)

func NewEmptyStructReturnRule added in v1.4.2

func NewEmptyStructReturnRule() *EmptyStructReturnRule

NewEmptyStructReturnRule creates the rule

func (*EmptyStructReturnRule) AnalyzeFile added in v1.4.2

func (r *EmptyStructReturnRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for empty struct returns in error contexts

type ErrorLengthCheckRule added in v1.4.2

type ErrorLengthCheckRule struct {
	*rules.BaseRule
}

ErrorLengthCheckRule detects dangerous patterns where error type is determined by string length This is an anti-pattern because: - Any error message of certain length will be misclassified - Error message length depends on driver version, locale, etc. - Real errors get masked as different error types

func NewErrorLengthCheckRule added in v1.4.2

func NewErrorLengthCheckRule() *ErrorLengthCheckRule

NewErrorLengthCheckRule creates the rule

func (*ErrorLengthCheckRule) AnalyzeFile added in v1.4.2

func (r *ErrorLengthCheckRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for error length check patterns

type ErrorMaskedAsFalseBoolRule added in v1.4.2

type ErrorMaskedAsFalseBoolRule struct {
	*rules.BaseRule
}

ErrorMaskedAsFalseBoolRule detects `if err != nil { return false }` patterns inside non-predicate bool-returning functions.

The existing `error-masking` rule requires the function to return `error`. That misses a class of security-sensitive bugs where a `(...) bool` function internally calls an error-returning API, then conflates "error" with "not allowed":

func ValidateUserPermission(user, perm string) bool {
    permissions, err := c.GetRolePermissions(user)
    if err != nil {
        return false  // ← user gets denied for reasons they can't debug;
                      //   ops can't see that lookup is broken
    }
    ...
}

Pure predicates (IsEnabled, HasRole, CanWrite, ShouldRetry) are exempt — returning false on lookup miss is their whole contract.

Detects:

  • `if err != nil { ... return false ... }` without any logging call
  • Function's return type contains `bool` (any position, not just last)
  • Function name does NOT start with Is/Has/Can/Should

Skips:

  • Test files
  • Pure predicate functions (Is/Has/Can/Should prefix)
  • Blocks that log the error before returning false

func NewErrorMaskedAsFalseBoolRule added in v1.4.2

func NewErrorMaskedAsFalseBoolRule() *ErrorMaskedAsFalseBoolRule

NewErrorMaskedAsFalseBoolRule creates the rule

func (*ErrorMaskedAsFalseBoolRule) AnalyzeFile added in v1.4.2

func (r *ErrorMaskedAsFalseBoolRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile runs the rule.

type ErrorMaskingRule

type ErrorMaskingRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

ErrorMaskingRule detects patterns that mask errors instead of handling them properly This implements CLAUDE.md principle: "Fail explicitly, never degrade silently"

func NewErrorMaskingRule

func NewErrorMaskingRule() *ErrorMaskingRule

NewErrorMaskingRule creates the rule

func (*ErrorMaskingRule) AnalyzeFile

func (r *ErrorMaskingRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for error masking patterns

type ErrorStringCompareRule added in v1.3.0

type ErrorStringCompareRule struct {
	*rules.BaseRule
}

ErrorStringCompareRule detects error comparisons using string matching

func NewErrorStringCompareRule added in v1.3.0

func NewErrorStringCompareRule() *ErrorStringCompareRule

NewErrorStringCompareRule creates the rule

func (*ErrorStringCompareRule) AnalyzeFile added in v1.3.0

func (r *ErrorStringCompareRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for error string comparisons

type ErrorStringRule

type ErrorStringRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

ErrorStringRule detects error strings that don't follow Go conventions

func NewErrorStringRule

func NewErrorStringRule() *ErrorStringRule

NewErrorStringRule creates the rule

func (*ErrorStringRule) AnalyzeFile

func (r *ErrorStringRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks error string formatting

type ErrorWrapRule added in v1.3.0

type ErrorWrapRule struct {
	*rules.BaseRule
}

ErrorWrapRule detects errors returned without context wrapping

func NewErrorWrapRule added in v1.3.0

func NewErrorWrapRule() *ErrorWrapRule

NewErrorWrapRule creates the rule

func (*ErrorWrapRule) AnalyzeFile added in v1.3.0

func (r *ErrorWrapRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for unwrapped error returns

type FallbackReturnRule added in v1.4.2

type FallbackReturnRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

FallbackReturnRule detects fallback patterns that silently degrade instead of failing explicitly This implements CLAUDE.md principle: "Fail explicitly, never degrade silently" Catches: return testProvider, return mockService on errors Excludes: functions with "OrDefault" in name, parse* functions, singleton getters, middleware defensive code

func NewFallbackReturnRule added in v1.4.2

func NewFallbackReturnRule() *FallbackReturnRule

NewFallbackReturnRule creates the rule

func (*FallbackReturnRule) AnalyzeFile added in v1.4.2

func (r *FallbackReturnRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for fallback return patterns

type FinancialConstantsRule added in v1.4.2

type FinancialConstantsRule struct {
	*rules.BaseRule
}

FinancialConstantsRule detects hardcoded financial constants that should be in config Examples: fees, commissions, rates, percentages in financial context

func NewFinancialConstantsRule added in v1.4.2

func NewFinancialConstantsRule() *FinancialConstantsRule

NewFinancialConstantsRule creates the rule

func (*FinancialConstantsRule) AnalyzeFile added in v1.4.2

func (r *FinancialConstantsRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for hardcoded financial constants

type FinancialDecimalFloatRule added in v1.4.2

type FinancialDecimalFloatRule struct {
	*rules.BaseRule
}

FinancialDecimalFloatRule detects discarded exactness from financial decimal conversions.

func NewFinancialDecimalFloatRule added in v1.4.2

func NewFinancialDecimalFloatRule() *FinancialDecimalFloatRule

NewFinancialDecimalFloatRule creates the rule.

func (*FinancialDecimalFloatRule) AnalyzeFile added in v1.4.2

func (r *FinancialDecimalFloatRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks two-result Float64 assignments on shopspring decimal values.

type FinancialFPRoundingRule added in v1.4.2

type FinancialFPRoundingRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

FinancialFPRoundingRule detects unsafe floor/ceil/trunc rounding of money values multiplied by 100 (or by an explicit percentage). Pattern `Math.floor(value * 100) / 100` looks like "round down to cent" but JavaScript IEEE-754 makes `5055.19 * 100 = 505518.99999999994`, which floors to `505518` and yields `5055.18` — silently losing one cent.

The same shimmer hits `Math.floor(money * pct) / 100` for percentage buttons (e.g. 100% of max → 5055.18 instead of 5055.19).

Safe alternatives:

  • `Math.round(value * 100) / 100` — half-even, no shimmer for cent grid
  • `Math.floor(value * 100 + 1e-9) / 100` — explicit epsilon
  • `value.toFixed(2)` when half-up rounding is acceptable
  • In Go: use `decimal.Decimal` arithmetic, never float64

func NewFinancialFPRoundingRule added in v1.4.2

func NewFinancialFPRoundingRule() *FinancialFPRoundingRule

NewFinancialFPRoundingRule creates the rule

func (*FinancialFPRoundingRule) AnalyzeFile added in v1.4.2

func (r *FinancialFPRoundingRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for floating-point rounding of money

type FinancialJSONFloatRule added in v1.4.2

type FinancialJSONFloatRule struct {
	*rules.BaseRule
}

FinancialJSONFloatRule detects precision-losing floats in monetary JSON contracts.

func NewFinancialJSONFloatRule added in v1.4.2

func NewFinancialJSONFloatRule() *FinancialJSONFloatRule

NewFinancialJSONFloatRule creates the rule.

func (*FinancialJSONFloatRule) AnalyzeFile added in v1.4.2

func (r *FinancialJSONFloatRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks JSON DTO structs while preserving financial context through anonymous fields.

type FinancialRoundedDeltaRule added in v1.4.2

type FinancialRoundedDeltaRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

FinancialRoundedDeltaRule detects financial deltas derived by subtracting parsed cumulative money fields. Financial deltas should be calculated in the canonical backend/domain layer from full-precision decimals and exposed as a first-class field, not reconstructed from rounded API/display values.

func NewFinancialRoundedDeltaRule added in v1.4.2

func NewFinancialRoundedDeltaRule() *FinancialRoundedDeltaRule

NewFinancialRoundedDeltaRule creates the rule

func (*FinancialRoundedDeltaRule) AnalyzeFile added in v1.4.2

func (r *FinancialRoundedDeltaRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for deltas computed from parsed cumulative money fields

type FrontendEnvFallbackRule added in v1.4.2

type FrontendEnvFallbackRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

FrontendEnvFallbackRule detects frontend public-env patterns that silently degrade at build/runtime instead of failing explicitly.

func NewFrontendEnvFallbackRule added in v1.4.2

func NewFrontendEnvFallbackRule() *FrontendEnvFallbackRule

NewFrontendEnvFallbackRule creates the rule

func (*FrontendEnvFallbackRule) AnalyzeFile added in v1.4.2

func (r *FrontendEnvFallbackRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for placeholder and fallback public environment configuration

type FrontendMoneyArithmeticRule added in v1.4.2

type FrontendMoneyArithmeticRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

FrontendMoneyArithmeticRule detects client-side arithmetic over money values in TS/JS:

investedAmount += parseFloat(inv.amount)
pending.reduce((sum, w) => sum + parseFloat(w.amount || '0'), 0)

Financial aggregates must be computed on the backend (single canonical calculation logic); parseFloat over decimal strings silently loses precision, and duplicated client math diverges from the server.

Not flagged: pure formatting (formatAmount(parseFloat(x))), comparisons, non-money numerics, tests.

func NewFrontendMoneyArithmeticRule added in v1.4.2

func NewFrontendMoneyArithmeticRule() *FrontendMoneyArithmeticRule

NewFrontendMoneyArithmeticRule creates the rule

func (*FrontendMoneyArithmeticRule) AnalyzeFile added in v1.4.2

func (r *FrontendMoneyArithmeticRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks TS/JS lines for money arithmetic

func (*FrontendMoneyArithmeticRule) Configure added in v1.4.2

func (r *FrontendMoneyArithmeticRule) Configure(settings map[string]any) error

Configure reads the optional money_fields setting ("a|b|c" fragments).

type FrontendSilentCatchRule added in v1.4.2

type FrontendSilentCatchRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

FrontendSilentCatchRule detects frontend catch blocks that only log errors. UI code must either surface the failure to the user or rethrow it to a caller that can do so.

func NewFrontendSilentCatchRule added in v1.4.2

func NewFrontendSilentCatchRule() *FrontendSilentCatchRule

NewFrontendSilentCatchRule creates the rule

func (*FrontendSilentCatchRule) AnalyzeFile added in v1.4.2

func (r *FrontendSilentCatchRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for catch blocks that log without user-visible handling

type GoModernRule added in v1.4.0

type GoModernRule struct {
	*rules.BaseRule
}

GoModernRule detects patterns that could use modern Go features

func NewGoModernRule added in v1.4.0

func NewGoModernRule() *GoModernRule

NewGoModernRule creates the rule

func (*GoModernRule) AnalyzeFile added in v1.4.0

func (r *GoModernRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for outdated patterns

type HTTPBodyCloseRule

type HTTPBodyCloseRule struct {
	*rules.BaseRule
}

HTTPBodyCloseRule detects HTTP response body not being closed

func NewHTTPBodyCloseRule

func NewHTTPBodyCloseRule() *HTTPBodyCloseRule

NewHTTPBodyCloseRule creates the rule

func (*HTTPBodyCloseRule) AnalyzeFile

func (r *HTTPBodyCloseRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for unclosed HTTP response bodies

type IdempotencyCheckThenCreateRule added in v1.4.2

type IdempotencyCheckThenCreateRule struct {
	*rules.BaseRule
}

IdempotencyCheckThenCreateRule detects non-atomic idempotency checks followed by a create on the same repository.

func NewIdempotencyCheckThenCreateRule added in v1.4.2

func NewIdempotencyCheckThenCreateRule() *IdempotencyCheckThenCreateRule

NewIdempotencyCheckThenCreateRule creates the rule.

func (*IdempotencyCheckThenCreateRule) AnalyzeFile added in v1.4.2

AnalyzeFile checks each production function independently.

type IgnoredErrorRule

type IgnoredErrorRule struct {
	*rules.BaseRule
}

IgnoredErrorRule detects error values thrown away with the blank identifier.

Whether a returned value is an error is decided by its type, not by the name of the function returning it. The name-based version of this rule matched a fixed list of verbs (Read, Parse, Query, Marshal…) and therefore stayed silent on every domain method — the shape `items, _ = repo.List(ctx)` reads as "no data" downstream while the query may well have failed (REF-462).

Closing a resource stays exempt: `_ = conn.Close()` and the print family cannot report anything useful to the caller, and writing the blank there is the documented way to say "checked and deliberately dropped".

func NewIgnoredErrorRule

func NewIgnoredErrorRule() *IgnoredErrorRule

NewIgnoredErrorRule creates a new ignored error detector

func (*IgnoredErrorRule) AnalyzeFile

func (r *IgnoredErrorRule) AnalyzeFile(_ *core.FileContext) []*core.Violation

AnalyzeFile is unused: the rule works on the typed project.

func (*IgnoredErrorRule) AnalyzeGoProject added in v1.4.2

func (r *IgnoredErrorRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject walks assignments and reports blanks that swallow an error value.

func (*IgnoredErrorRule) RequiresSSA added in v1.4.2

func (r *IgnoredErrorRule) RequiresSSA() bool

RequiresSSA reports that plain type information is enough.

type LegacyCommentMarkerRule added in v1.4.2

type LegacyCommentMarkerRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

LegacyCommentMarkerRule detects inline comments that document a runtime legacy code path — "Legacy mode", "Legacy compatibility", "legacy SSE auth", "(legacy)" — as distinct from identifier names (covered by legacy-identifier) and godoc-level Deprecated comments (covered by deprecated-comment).

Why inline: composite_router.go:831 has `// 2. Legacy mode: separate admin/ user path handling` — not a symbol name, not a godoc. The comment itself admits a runtime legacy branch exists, which CLAUDE.md's "No legacy, only current code" forbids.

Detects in Go and TypeScript/TSX files:

  • `// Legacy mode`, `// Legacy compatibility`, `// Legacy:`
  • `// legacy foo`, `// (legacy)`, `// SMTP_* (legacy)`
  • multiline /* Legacy ... */ / /** Legacy ... */ prefix

Skips:

  • Test files and generated code
  • Comments that quote CLAUDE.md policy (contain "CLAUDE.md", "No legacy", "policy", "запрет", "запрещ") — self-references to the rule itself
  • //nolint:legacy-comment-marker on the line
  • The legacy-identifier rule's own file (which prints "Legacy" as a string literal for diagnostic messages — it's the rule implementation, not a legacy code path)

func NewLegacyCommentMarkerRule added in v1.4.2

func NewLegacyCommentMarkerRule() *LegacyCommentMarkerRule

NewLegacyCommentMarkerRule creates the rule

func (*LegacyCommentMarkerRule) AnalyzeFile added in v1.4.2

func (r *LegacyCommentMarkerRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile scans line-by-line for legacy comments in Go and TS files.

type LegacyIdentifierRule added in v1.4.2

type LegacyIdentifierRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

LegacyIdentifierRule detects identifiers (func/method/type/const/var) whose name contains "Legacy" / "legacy_". Comments are covered by the separate `deprecated-comment` rule; this one exists because renaming a symbol to include "Legacy" is a common way to ship a parallel implementation that never actually gets removed — the mirror of what CLAUDE.md forbids under "No legacy, only current code".

Detects:

  • func (Foo) RegisterLegacyRoutes(...) — method with Legacy in name
  • func buildLegacyPayload(...) — function with Legacy in name
  • type LegacyUser struct{} — type with Legacy prefix/suffix
  • var/const LegacyTimeout = ... — value identifier

Skips:

  • Test files
  • Generated files (*.gen.go, *_gen.go, /generated/)
  • //nolint:legacy-identifier opt-outs on the declaration line

func NewLegacyIdentifierRule added in v1.4.2

func NewLegacyIdentifierRule() *LegacyIdentifierRule

NewLegacyIdentifierRule creates the rule

func (*LegacyIdentifierRule) AnalyzeFile added in v1.4.2

func (r *LegacyIdentifierRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for Legacy identifiers

type LogAndReturnZeroRule added in v1.4.2

type LogAndReturnZeroRule struct {
	*rules.BaseRule
}

LogAndReturnZeroRule detects functions without an error result that log at Error/Warn level and immediately return a zero value:

func (m *TokenManager) getJWTIssuer() string {
    if m.config == nil {
        m.logger.Error("Configuration not available")
        return "" // empty issuer breaks validation much later
    }
    ...
}

The log acknowledges a failure, but the caller receives a sentinel ("" / 0 / nil) indistinguishable from a valid value. CLAUDE.md: a function that can fail must return (T, error).

Not flagged: Info/Debug logs, `return false` (see error-masked-as-false-bool), computed recovery values, and HTTP handlers (they report the failure via ResponseWriter).

func NewLogAndReturnZeroRule added in v1.4.2

func NewLogAndReturnZeroRule() *LogAndReturnZeroRule

NewLogAndReturnZeroRule creates the rule

func (*LogAndReturnZeroRule) AnalyzeFile added in v1.4.2

func (r *LogAndReturnZeroRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks Go functions for the log-then-zero pattern

type MagicNumberRule

type MagicNumberRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

MagicNumberRule detects hardcoded numbers that should be named constants

func NewMagicNumberRule

func NewMagicNumberRule() *MagicNumberRule

NewMagicNumberRule creates the rule

func (*MagicNumberRule) AnalyzeFile

func (r *MagicNumberRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for magic numbers

func (*MagicNumberRule) Configure

func (r *MagicNumberRule) Configure(settings map[string]any) error

Configure allows setting rule options

type MainReturnAfterErrorRule added in v1.4.2

type MainReturnAfterErrorRule struct {
	*rules.BaseRule
}

MainReturnAfterErrorRule detects func main returning normally from an error branch:

func main() {
    if err := run(); err != nil {
        log.Printf("Error: %v", err)
        return // the process exits 0 — scripts and CI see success
    }
}

A bare return in main ends the process with exit code 0, so every caller — shell scripts, CI, cron — treats the failed run as successful. The honest endings are os.Exit(1), log.Fatal or panic; those forms are not flagged.

Родилось из ревью projectD 2026-08 (№27): шесть cmd-утилит логировали ошибку и выходили из main обычным return, отчитываясь кодом 0.

func NewMainReturnAfterErrorRule added in v1.4.2

func NewMainReturnAfterErrorRule() *MainReturnAfterErrorRule

NewMainReturnAfterErrorRule creates the rule

func (*MainReturnAfterErrorRule) AnalyzeFile added in v1.4.2

func (r *MainReturnAfterErrorRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks func main of package main for returns from error branches

type MapIterationOrderRule added in v1.4.2

type MapIterationOrderRule struct {
	*rules.BaseRule
}

MapIterationOrderRule detects values whose order comes from walking a map and then leaves the function:

for area := range detected {
    areas = append(areas, area)   // order is random
}
return areas

Go randomizes map iteration deliberately, so the same input produces a different order on every run. Once such a slice or string reaches a message, a report or a caller, the output stops being reproducible: golden tests flap, CI diffs show phantom changes, and findings swap places between runs.

Not flagged: order-independent aggregation (sums, counters), collecting into another map, values that never leave the function, and slices sorted before they are used.

func NewMapIterationOrderRule added in v1.4.2

func NewMapIterationOrderRule() *MapIterationOrderRule

NewMapIterationOrderRule creates the rule

func (*MapIterationOrderRule) AnalyzeFile added in v1.4.2

func (r *MapIterationOrderRule) AnalyzeFile(_ *core.FileContext) []*core.Violation

AnalyzeFile is a no-op because this rule needs the package's type information to tell a map range from a slice range.

func (*MapIterationOrderRule) AnalyzeGoProject added in v1.4.2

func (r *MapIterationOrderRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject inspects every function of the loaded packages.

func (*MapIterationOrderRule) RequiresSSA added in v1.4.2

func (r *MapIterationOrderRule) RequiresSSA() bool

RequiresSSA reports that typed syntax is enough for this rule.

type MaskedErrorOrConditionRule added in v1.4.2

type MaskedErrorOrConditionRule struct {
	*rules.BaseRule
}

MaskedErrorOrConditionRule detects branches that conflate a real error with a legitimate "no data" case via ||, then swallow the error:

if err != nil || latest == nil {
    return SafeDecimal{}, nil   // DB failure masked as valid zero value
}

The caller cannot distinguish a storage failure from an honest zero. CLAUDE.md: "Fail explicitly, never degrade silently".

Functions WITHOUT an error result are covered too, and there any return from such a branch masks the failure — the error cannot even be handed back:

func (s *S) dayYield(id string) Decimal {
    share, err := s.share(id)
    if err != nil || share.LessThanOrEqual(zero) {
        return s.cumulative        // DB failure silently yields "no growth"
    }

Not flagged: branches that propagate/wrap the error, branches that handle the error in a nested if, branches that panic, &&-narrowing (errors.Is style), and branches without a return.

For functions WITHOUT an error result the log IS the only error channel, so a branch that mentions the error variable anywhere (logs it, hands it to a collector) or calls an Error/Warn/Fatal-level logger is treated as handled. Functions taking http.ResponseWriter are skipped entirely: an HTTP handler reports failures through the response, not through return values. Functions WITH an error result get no such exemption — logging and then returning nil error still hides the failure from the caller.

func NewMaskedErrorOrConditionRule added in v1.4.2

func NewMaskedErrorOrConditionRule() *MaskedErrorOrConditionRule

NewMaskedErrorOrConditionRule creates the rule

func (*MaskedErrorOrConditionRule) AnalyzeFile added in v1.4.2

func (r *MaskedErrorOrConditionRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks Go functions for the masking pattern

type MigrationDuplicateVersionRule added in v1.4.2

type MigrationDuplicateVersionRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

MigrationDuplicateVersionRule detects two different migrations sharing one version number in the same directory:

000029_client_number.up.sql
000029_support_conversations.up.sql   // same version, different migration!

Version-keyed migrators (maps, golang-migrate) silently keep only one of them — a fresh database ends up missing the other migration's schema. Every migration must own a unique version number, and the migrator itself should fail on duplicates (defense-in-depth: this rule catches the problem at lint time).

func NewMigrationDuplicateVersionRule added in v1.4.2

func NewMigrationDuplicateVersionRule() *MigrationDuplicateVersionRule

NewMigrationDuplicateVersionRule creates the rule

func (*MigrationDuplicateVersionRule) AnalyzeFile added in v1.4.2

AnalyzeFile registers migration files and reports duplicate versions

func (*MigrationDuplicateVersionRule) ResetState added in v1.4.2

func (r *MigrationDuplicateVersionRule) ResetState()

ResetState clears the migrations seen so far, so that a project root never inherits versions registered while analyzing a previous root.

type MockIdentifierRule added in v1.4.2

type MockIdentifierRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

MockIdentifierRule detects identifiers (func/method/type/const/var) whose name contains a Mock/Fake/Stub/Dummy segment in non-test code. Such a name in production either mislabels a real code path (a "mock" quote that is in fact the live path for one execution mode — readers skip it as test-only and it silently drifts from the implementation it mirrors) or marks test scaffolding that leaked out of _test.go files. Deliberate simulation surfaces (a config-gated sandbox mode) stay, renamed to say what they do or opted out with //nolint:mock-identifier and a reason.

Detects:

  • func (a *Admin) createMockQuote(...) — method with Mock in name
  • func buildFakePayload(...) — function with Fake in name
  • type StubNotifier struct{} — type with Stub prefix
  • var dummy_response = ... — snake_case value identifier

Skips:

  • Test files
  • Generated files (*.gen.go, *_gen.go, /generated/)
  • //nolint:mock-identifier opt-outs on the declaration line

func NewMockIdentifierRule added in v1.4.2

func NewMockIdentifierRule() *MockIdentifierRule

NewMockIdentifierRule creates the rule

func (*MockIdentifierRule) AnalyzeFile added in v1.4.2

func (r *MockIdentifierRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for Mock/Fake/Stub/Dummy identifiers

type MultiWriteNoTransactionRule added in v1.4.2

type MultiWriteNoTransactionRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

MultiWriteNoTransactionRule detects a function that changes persistent state more than once without a transaction around the changes.

The failure is not hypothetical and it is not loud. Each write succeeds on its own, so nothing in the logs says "half of this operation happened". The record simply disagrees with itself from then on, and the disagreement is found later, by a human, in money.

Real case (ProjectA, 2026-07-31). Completing a withdrawal wrote four rows in sequence: the transaction hash onto the request, a posting into `transactions`, the request's status to `completed`, and finally the ledger entries in `fund_transfers`. The last step was allowed to fail — the code even carried a comment saying rollback was impossible at that point. A failure there left the withdrawal marked completed, with a posting under it, and no ledger entry: the money was recorded as gone and unaccounted for at the same time. The four writes are now one transaction, and the regression test asserts that a failing last step leaves the request in its previous status.

What is reported: two or more calls, with distinct method names, that mutate a store (a receiver whose type name reads as a repository, store or DAO) and that can both run in the same pass through the function. What is not reported:

  • Calls in mutually exclusive arms of the same if or switch — only one of them runs.
  • A retry of the same call: the same method name twice is one write attempted twice.
  • Writes inside a goroutine body: that work outlives the function and cannot share its transaction. Project spawners that hide the `go` inside a helper, and telemetry that must survive precisely when the business operation fails, are listed in independent_calls.
  • Writes already inside a transaction runner's callback, and functions that are only ever reached from inside one — the wrapper does not have to sit in the same function as the writes, and requiring that would push every helper back into one long method.

Deliberately separate steps do exist: crediting a deposit and auto-investing it are two operations, and rolling back the credit because the investment failed would be worse than leaving them apart. Such a pair is exempted by naming the runner in transaction_functions only if it truly runs under one, so the honest way to silence this rule for a deliberate split is a comment on the function and a rule exclusion, not a fake transaction.

func NewMultiWriteNoTransactionRule added in v1.4.2

func NewMultiWriteNoTransactionRule() *MultiWriteNoTransactionRule

NewMultiWriteNoTransactionRule creates the rule.

func (*MultiWriteNoTransactionRule) AnalyzeFile added in v1.4.2

AnalyzeFile is a no-op: whether a helper already runs inside a transaction is decided by its callers, which live in other files.

func (*MultiWriteNoTransactionRule) AnalyzeGoProject added in v1.4.2

func (r *MultiWriteNoTransactionRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject collects the functions that run under a transaction, then reports the rest.

func (*MultiWriteNoTransactionRule) Configure added in v1.4.2

func (r *MultiWriteNoTransactionRule) Configure(settings map[string]any) error

Configure accepts overrides for what counts as a store and as a transaction runner.

func (*MultiWriteNoTransactionRule) RequiresSSA added in v1.4.2

func (r *MultiWriteNoTransactionRule) RequiresSSA() bool

RequiresSSA reports that typed packages are enough.

type MutexLockRule

type MutexLockRule struct {
	*rules.BaseRule
}

MutexLockRule detects mutex Lock() without corresponding Unlock()

func NewMutexLockRule

func NewMutexLockRule() *MutexLockRule

NewMutexLockRule creates the rule

func (*MutexLockRule) AnalyzeFile

func (r *MutexLockRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for mutex lock without defer unlock

type NilDIRule added in v1.4.2

type NilDIRule struct {
	*rules.BaseRule
}

NilDIRule detects nil arguments passed to constructor functions (New*) which often indicates missing dependency injection configuration. Focuses on high-risk parameters: logger, service, repo, storage, handler.

func NewNilDIRule added in v1.4.2

func NewNilDIRule() *NilDIRule

NewNilDIRule creates the rule

func (*NilDIRule) AnalyzeFile added in v1.4.2

func (r *NilDIRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for nil arguments in constructor calls

type NilSliceRule

type NilSliceRule struct {
	*rules.BaseRule
}

NilSliceRule detects nil slice comparisons and returns

func NewNilSliceRule

func NewNilSliceRule() *NilSliceRule

NewNilSliceRule creates the rule

func (*NilSliceRule) AnalyzeFile

func (r *NilSliceRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for nil slice comparisons

type NonAtomicStatusHistoryRule added in v1.4.2

type NonAtomicStatusHistoryRule struct {
	*rules.BaseRule
}

NonAtomicStatusHistoryRule detects status mutations followed by a separate history write on the same repository in one function.

func NewNonAtomicStatusHistoryRule added in v1.4.2

func NewNonAtomicStatusHistoryRule() *NonAtomicStatusHistoryRule

NewNonAtomicStatusHistoryRule creates the rule.

func (*NonAtomicStatusHistoryRule) AnalyzeFile added in v1.4.2

func (r *NonAtomicStatusHistoryRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks each production Go function independently.

type NonCanonicalLoggerRule added in v1.4.2

type NonCanonicalLoggerRule struct {
	*rules.BaseRule
}

NonCanonicalLoggerRule detects usage of non-canonical logging in production code.

Rationale: projects that standardize on a single logger (slog, zerolog, or a project-local canonical_logger) need every production code path to route diagnostics through that logger so that formatting, sampling and destinations stay consistent. Ad-hoc calls to log.Printf, fmt.Print* or parallel logger libraries (zap, logrus) bypass that pipeline.

Detects:

  • Calls to log.Printf/Println/Print/Fatal/Panic and their formatted variants
  • fmt.Print/Println/Printf used as diagnostic output (not as error construction)
  • Imports of known parallel logger libraries (zap, logrus, glog, zerolog) in projects where they are not the canonical choice

Skips:

  • Test files (*_test.go, /tests/, /testdata/)
  • cmd/**/main.go (CLI entry points can use bare fmt/log)
  • Files explicitly configured as exceptions in .glint.yaml

func NewNonCanonicalLoggerRule added in v1.4.2

func NewNonCanonicalLoggerRule() *NonCanonicalLoggerRule

NewNonCanonicalLoggerRule creates the rule

func (*NonCanonicalLoggerRule) AnalyzeFile added in v1.4.2

func (r *NonCanonicalLoggerRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for non-canonical logger usage

type NullableObjectCallRule added in v1.4.2

type NullableObjectCallRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

NullableObjectCallRule detects Object.* calls on nested values that may be null/undefined in API responses. These calls throw at runtime when the target is not an object.

func NewNullableObjectCallRule added in v1.4.2

func NewNullableObjectCallRule() *NullableObjectCallRule

NewNullableObjectCallRule creates the rule

func (*NullableObjectCallRule) AnalyzeFile added in v1.4.2

func (r *NullableObjectCallRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for Object.* calls on possibly nullable values

type OrphanedInterfaceRule added in v1.4.2

type OrphanedInterfaceRule struct {
	*rules.BaseRule
}

OrphanedInterfaceRule detects interfaces with no implementations or usages These are "dead code" interfaces that can be safely removed

func NewOrphanedInterfaceRule added in v1.4.2

func NewOrphanedInterfaceRule() *OrphanedInterfaceRule

NewOrphanedInterfaceRule creates the rule

func (*OrphanedInterfaceRule) AnalyzeFile added in v1.4.2

func (r *OrphanedInterfaceRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for orphaned interfaces

type PaginationBoundaryTruncationRule added in v1.4.2

type PaginationBoundaryTruncationRule struct {
	*rules.BaseRule
}

PaginationBoundaryTruncationRule detects silent page caps on time-bounded history walks.

func NewPaginationBoundaryTruncationRule added in v1.4.2

func NewPaginationBoundaryTruncationRule() *PaginationBoundaryTruncationRule

NewPaginationBoundaryTruncationRule creates the rule.

func (*PaginationBoundaryTruncationRule) AnalyzeFile added in v1.4.2

AnalyzeFile finds loops with both a temporal completion boundary and a silent page-cap break.

type ProviderCommandBeforeIntentPersistRule added in v1.4.2

type ProviderCommandBeforeIntentPersistRule struct {
	*rules.BaseRule
}

ProviderCommandBeforeIntentPersistRule detects financial provider commands executed before their durable request or intent is recorded.

func NewProviderCommandBeforeIntentPersistRule added in v1.4.2

func NewProviderCommandBeforeIntentPersistRule() *ProviderCommandBeforeIntentPersistRule

NewProviderCommandBeforeIntentPersistRule creates the rule.

func (*ProviderCommandBeforeIntentPersistRule) AnalyzeFile added in v1.4.2

AnalyzeFile checks command and persistence ordering within each function.

type ProviderCommandRetryRule added in v1.4.2

type ProviderCommandRetryRule struct {
	*rules.BaseRule
}

ProviderCommandRetryRule detects automatic retries of destructive provider commands.

func NewProviderCommandRetryRule added in v1.4.2

func NewProviderCommandRetryRule() *ProviderCommandRetryRule

NewProviderCommandRetryRule creates the rule.

func (*ProviderCommandRetryRule) AnalyzeFile added in v1.4.2

func (r *ProviderCommandRetryRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks same-file helper signatures, retry callbacks, and loops.

type QuadraticLoopRule added in v1.4.2

type QuadraticLoopRule struct {
	*rules.BaseRule
}

QuadraticLoopRule detects work that grows with the square of the input:

for i := range windows {          // every window…
    for j := range windows {      // …compared with every other one

for strings.Contains(line, "  ") {         // scan the whole string…
    line = strings.ReplaceAll(line, "  ", " ")  // …after each replacement

Both shapes are correct and stay fast on the examples they were written against; they turn into minutes once the input grows. Both cost glint itself dearly: the nested window comparison made a 900-file project take over two minutes, and the rescanning replace was the hot spot of line normalization.

Not flagged: loops over two different collections (O(n*m) is what the code asks for), an inner loop over a field of the outer element, and a body that does nothing but count.

func NewQuadraticLoopRule added in v1.4.2

func NewQuadraticLoopRule() *QuadraticLoopRule

NewQuadraticLoopRule creates the rule

func (*QuadraticLoopRule) AnalyzeFile added in v1.4.2

func (r *QuadraticLoopRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile looks for the two quadratic shapes in one file.

type QueryInLoopRule added in v1.4.2

type QueryInLoopRule struct {
	*rules.BaseRule
}

QueryInLoopRule detects repository/DB method calls inside loops (N+1 query anti-pattern). Это правило родилось из REF-312: per-итерационные SQL-вызовы (config_timeline.Resolve, GetNetFundTransfersToVault) давали ~9000 запросов и 9.5s на список клиентов.

func NewQueryInLoopRule added in v1.4.2

func NewQueryInLoopRule() *QueryInLoopRule

NewQueryInLoopRule creates the rule.

func (*QueryInLoopRule) AnalyzeFile added in v1.4.2

func (r *QueryInLoopRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile walks loops and flags data-access calls directly inside them.

type RangeValPointerRule

type RangeValPointerRule struct {
	*rules.BaseRule
}

RangeValPointerRule detects taking address of range loop variable

func NewRangeValPointerRule

func NewRangeValPointerRule() *RangeValPointerRule

NewRangeValPointerRule creates the rule

func (*RangeValPointerRule) AnalyzeFile

func (r *RangeValPointerRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for pointer to range variable

type ReactRemountKeyRule added in v1.4.2

type ReactRemountKeyRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

ReactRemountKeyRule detects a JSX key built from a value that a controlled input inside the same element edits. React treats a changed key as a new element: every keystroke unmounts the subtree, the fresh input mounts empty of focus, and the user can type exactly one character at a time.

Real case (projectB, 2026-08-05): a wallet row used key={`${wallet.walletAddress}-${index}`} while its <input value={wallet.walletAddress} onChange=.../> edited that very address.

Precision over recall: the rule fires only when the key expression and the input's value= provably reference the same dotted path (x.field) and the input has an onChange/onInput handler in the same tag.

func NewReactRemountKeyRule added in v1.4.2

func NewReactRemountKeyRule() *ReactRemountKeyRule

NewReactRemountKeyRule creates the rule.

func (*ReactRemountKeyRule) AnalyzeFile added in v1.4.2

func (r *ReactRemountKeyRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks TSX/JSX sources.

type RedundantCompatibilityRule added in v1.4.2

type RedundantCompatibilityRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

RedundantCompatibilityRule detects false backward compatibility patterns This implements CLAUDE.md principles: - "Delete cleanly, git remembers" - no fake compatibility - SRP - one way to do one thing Catches: - Multiple context key fallbacks (GetAdminIDFromContext checking 3 different keys) - False "backward compatibility" comments without external API consumers - Duplicate key definitions (AdminIDKey vs AdminIDKeyAlt)

func NewRedundantCompatibilityRule added in v1.4.2

func NewRedundantCompatibilityRule() *RedundantCompatibilityRule

NewRedundantCompatibilityRule creates the rule

func (*RedundantCompatibilityRule) AnalyzeFile added in v1.4.2

func (r *RedundantCompatibilityRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for redundant compatibility patterns

type ReimplementedStdlibRule added in v1.4.2

type ReimplementedStdlibRule struct {
	*rules.BaseRule
}

ReimplementedStdlibRule detects small helpers that redo what the standard library already does:

func itoa(i int) string {          // strconv.Itoa
    s := ""
    for i > 0 { s = string(rune('0'+i%10)) + s; i /= 10 }
    return s
}

The copy is not merely redundant: it is the version nobody tested. glint carried four such itoa helpers, and they all shared the same bug — a negative number came back as the empty string.

The rule recognizes the shapes it can name with certainty: digit-by-digit integer formatting and parsing, a linear search for an element, absolute value, the smaller/larger of two values, and reversing a slice in place.

func NewReimplementedStdlibRule added in v1.4.2

func NewReimplementedStdlibRule() *ReimplementedStdlibRule

NewReimplementedStdlibRule creates the rule

func (*ReimplementedStdlibRule) AnalyzeFile added in v1.4.2

func (r *ReimplementedStdlibRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks every function declared in the file.

type ResponseTypeInFunctionRule added in v1.4.2

type ResponseTypeInFunctionRule struct {
	*rules.BaseRule
}

ResponseTypeInFunctionRule detects a wire contract declared inside a function body.

A struct with json tags that a function builds and hands to a response writer is an API contract, but declaring it in function scope hides it from every consumer: type generators cannot emit it, other handlers cannot reuse it, and clients end up hand-copying the field list. Each hand-made copy then drifts on its own.

Real case (ProjectA, 2026-07-29): the dashboard response type lived inside the handler, so the Go→TypeScript generator never saw it and the frontend grew three hand-written copies with different field sets. Two screens read different copies and showed different balances under the same label.

Only produced contracts are reported. A local struct used to decode a request body is a normal Go idiom: it is filled by the decoder, never composed field by field, so it does not match.

func NewResponseTypeInFunctionRule added in v1.4.2

func NewResponseTypeInFunctionRule() *ResponseTypeInFunctionRule

NewResponseTypeInFunctionRule creates the rule

func (*ResponseTypeInFunctionRule) AnalyzeFile added in v1.4.2

func (r *ResponseTypeInFunctionRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile reports function-local structs with json tags that are sent as a response.

type ReturnNilErrorRule

type ReturnNilErrorRule struct {
	*rules.BaseRule
}

ReturnNilErrorRule detects functions returning (nil, nil) which is often a bug

func NewReturnNilErrorRule

func NewReturnNilErrorRule() *ReturnNilErrorRule

NewReturnNilErrorRule creates the rule

func (*ReturnNilErrorRule) AnalyzeFile

func (r *ReturnNilErrorRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for (nil, nil) returns

type SQLRowsCloseRule

type SQLRowsCloseRule struct {
	*rules.BaseRule
}

SQLRowsCloseRule detects SQL rows not being closed

func NewSQLRowsCloseRule

func NewSQLRowsCloseRule() *SQLRowsCloseRule

NewSQLRowsCloseRule creates the rule

func (*SQLRowsCloseRule) AnalyzeFile

func (r *SQLRowsCloseRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for unclosed SQL rows

type ScatteredConstructionRule added in v1.4.2

type ScatteredConstructionRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

ScatteredConstructionRule detects struct types that are constructed via struct literals in too many different functions. Each construction site is a potential point of failure when a new field is added — the field will be silently missing in all but the updated sites.

Principle: "One conversion function per type pair, not scattered literals"

func NewScatteredConstructionRule added in v1.4.2

func NewScatteredConstructionRule() *ScatteredConstructionRule

NewScatteredConstructionRule creates the rule

func (*ScatteredConstructionRule) AnalyzeFile added in v1.4.2

func (r *ScatteredConstructionRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for struct types constructed in too many places

func (*ScatteredConstructionRule) Configure added in v1.4.2

func (r *ScatteredConstructionRule) Configure(settings map[string]any) error

Configure allows setting rule options

func (*ScatteredConstructionRule) ResetState added in v1.4.2

func (r *ScatteredConstructionRule) ResetState()

ResetState clears the construction sites collected so far. The rule accumulates them across files, so without a reset a second project root would inherit the sites of the first one.

type SelectStarStructScanRule added in v1.4.2

type SelectStarStructScanRule struct {
	*rules.BaseRule
}

SelectStarStructScanRule detects `SELECT *` against a real table in Go SQL literals.

Родилось из REF-410. sqlx без Unsafe() требует, чтобы каждой колонке ответа нашлось поле в структуре назначения. Поэтому `SELECT *` привязывает чтение к текущему набору колонок: миграция, добавляющая колонку, ломает выборку на рантайме с "missing destination name", хотя Go-код не менялся и сборка прошла. Явный список колонок снимает эту связь.

Производные таблицы (`SELECT * FROM (...) t`) правилом не считаются нарушением: там звёздочка берёт колонки подзапроса, а их набор задан тут же в коде.

func NewSelectStarStructScanRule added in v1.4.2

func NewSelectStarStructScanRule() *SelectStarStructScanRule

NewSelectStarStructScanRule creates the rule.

func (*SelectStarStructScanRule) AnalyzeFile added in v1.4.2

func (r *SelectStarStructScanRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile walks string literals looking for SELECT * over a table.

type SelectThenWriteRaceRule added in v1.4.2

type SelectThenWriteRaceRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

SelectThenWriteRaceRule detects a read-validate-write race inside one function: a SELECT of a column without FOR UPDATE followed by an UPDATE of the same column of the same table. Two concurrent calls read the same value, both pass the validation performed between the queries, and the second one silently overwrites the first.

Real case (projectB, 2026-08-05): status transitions read `status`, ran a state-machine check on the value, then wrote `status` back. Concurrent sync-materializer and manual close both read the same status and both passed the transition validation.

The rule is silent when the SELECT locks the row (FOR UPDATE / FOR NO KEY UPDATE / FOR SHARE) — that is exactly the fix.

func NewSelectThenWriteRaceRule added in v1.4.2

func NewSelectThenWriteRaceRule() *SelectThenWriteRaceRule

NewSelectThenWriteRaceRule creates the rule.

func (*SelectThenWriteRaceRule) AnalyzeFile added in v1.4.2

func (r *SelectThenWriteRaceRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks each function's SQL literals for the read-then-write pattern.

type ShadowVariableRule

type ShadowVariableRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

ShadowVariableRule detects variable shadowing

func NewShadowVariableRule

func NewShadowVariableRule() *ShadowVariableRule

NewShadowVariableRule creates the rule

func (*ShadowVariableRule) AnalyzeFile

func (r *ShadowVariableRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for variable shadowing

type SilentConfigErrorRule added in v1.4.2

type SilentConfigErrorRule struct {
	*rules.BaseRule
}

SilentConfigErrorRule detects silent error ignoring in config/bootstrap code. CLAUDE.md: "Fail explicitly, never degrade silently" — absolute rule; in config/env-loading paths it is critical because misconfiguration stays invisible until a much later failure.

Unlike the generic `ignored-error` rule, this one intentionally does NOT honor `//nolint:errcheck` — policy forbids the pattern regardless of the linter suppression.

Detects in files whose path contains `/config/` OR matches `**/bootstrap_environment_loader.go` / `**/unified_config*.go`:

  • `_ = X()` where X is an env/config-loading call (ReadEnv, Load, loadEnvFileDirectly, godotenv.Load, cleanenv.ReadConfig, etc.)
  • Bare call `X()` whose error return is dropped entirely (no assignment) for the same set of env/config functions.

Skips:

  • Test files / test utility files
  • Generated files

func NewSilentConfigErrorRule added in v1.4.2

func NewSilentConfigErrorRule() *SilentConfigErrorRule

NewSilentConfigErrorRule creates the rule

func (*SilentConfigErrorRule) AnalyzeFile added in v1.4.2

func (r *SilentConfigErrorRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for silent config errors

func (*SilentConfigErrorRule) SuppressionExempt added in v1.4.2

func (r *SilentConfigErrorRule) SuppressionExempt() bool

SuppressionExempt reports that policy forbids silent config errors unconditionally, so inline nolint/safe comments do not silence this rule.

type SilentErrorHandlingRule added in v1.4.2

type SilentErrorHandlingRule struct {
	*rules.BaseRule
}

SilentErrorHandlingRule detects error checks that don't log or propagate the error This implements CLAUDE.md principle: "Log all errors, never ignore silently" Catches: if err != nil { return X } without logging or returning the error

func NewSilentErrorHandlingRule added in v1.4.2

func NewSilentErrorHandlingRule() *SilentErrorHandlingRule

NewSilentErrorHandlingRule creates the rule

func (*SilentErrorHandlingRule) AnalyzeFile added in v1.4.2

func (r *SilentErrorHandlingRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for silent error handling

type SilentlyOptionalDependencyRule added in v1.4.2

type SilentlyOptionalDependencyRule struct {
	*rules.BaseRule
}

SilentlyOptionalDependencyRule detects a dependency that is injected by a setter, whose absence silently switches a feature off, and which at least one construction site cannot have received.

Родилось из реального инцидента (REF-446). У сервиса расчёта доходности был метод SetAnomalyAlerter, а в детекторе аномалий стояло `if s.anomalyAlerter == nil { return }`. Сервис собирался в девяти местах, сеттер звали в восьми — и алерты по аномалиям доходности не ушли ни разу за всю историю прода. Ничего не падало и не логировалось: фича просто отсутствовала на том экземпляре, который считал ночной пересчёт.

Признаков нужно три сразу, и по отдельности ни один из них не проблема:

  1. зависимость приходит сеттером, а не в конструктор — её можно не задать;
  2. её отсутствие проверяется молчаливым `return` — никто не узнает, что не задали;
  3. точек сборки больше, чем вызовов сеттера — значит хотя бы одна осталась без него.

Третий признак и делает правило точным: сервис с единственной точкой сборки собран правильно, и трогать его незачем. Считается по всему проекту, тестовые файлы не в счёт — там конструируют без зависимостей намеренно.

func NewSilentlyOptionalDependencyRule added in v1.4.2

func NewSilentlyOptionalDependencyRule() *SilentlyOptionalDependencyRule

NewSilentlyOptionalDependencyRule creates the rule.

func (*SilentlyOptionalDependencyRule) AnalyzeFile added in v1.4.2

AnalyzeFile does nothing: the rule needs the whole project to count construction sites.

func (*SilentlyOptionalDependencyRule) AnalyzeGoProject added in v1.4.2

func (r *SilentlyOptionalDependencyRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject pairs setters with silent guards, then counts construction sites.

func (*SilentlyOptionalDependencyRule) RequiresSSA added in v1.4.2

func (r *SilentlyOptionalDependencyRule) RequiresSSA() bool

RequiresSSA reports that typed packages are enough — no SSA program needed.

type SleepWithoutContextRule added in v1.4.2

type SleepWithoutContextRule struct {
	*rules.BaseRule
}

SleepWithoutContextRule detects time.Sleep inside a function that has a context.Context available (as a parameter or captured from the enclosing function). Cancelling the context does not interrupt the pause: a sync loop sleeping 250ms per item keeps running long after the caller gave up, and a graceful shutdown waits out every pending sleep.

Real case (projectB, 2026-08-05): wallet sync slept 200ms between provider APIs and 250ms per Solana transaction with a live ctx in scope; stopping the sync had to wait for the whole backlog of pauses.

func NewSleepWithoutContextRule added in v1.4.2

func NewSleepWithoutContextRule() *SleepWithoutContextRule

NewSleepWithoutContextRule creates the rule.

func (*SleepWithoutContextRule) AnalyzeFile added in v1.4.2

func (r *SleepWithoutContextRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile flags time.Sleep calls that ignore an available context.

type StringConcatRule

type StringConcatRule struct {
	*rules.BaseRule
}

StringConcatRule detects string concatenation in loops

func NewStringConcatRule

func NewStringConcatRule() *StringConcatRule

NewStringConcatRule creates the rule

func (*StringConcatRule) AnalyzeFile

func (r *StringConcatRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for string concatenation in loops

type TautologicalAssertionRule added in v1.4.2

type TautologicalAssertionRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

TautologicalAssertionRule detects tests that cannot fail.

A green suite is trusted, so an assertion that holds no matter what the code does is worse than no test at all: it claims coverage of exactly the behaviour nobody is watching.

Three shapes, all seen in production repositories:

  • both sides of an equality assertion are the same expression;
  • the asserted value is a literal constant (expect(true).toBe(true));
  • the assertion sits inside a condition derived from the same value, so it skips itself precisely when the value would have been interesting.

Real case (ProjectA, 2026-07-29): a regression test named "withdrawal and analytics headline numbers stay in lockstep" compared displayedBalance with displayedBalance and documented a contract the code did not have. It stayed green while the two screens drifted apart and started showing different balances.

func NewTautologicalAssertionRule added in v1.4.2

func NewTautologicalAssertionRule() *TautologicalAssertionRule

NewTautologicalAssertionRule creates the rule

func (*TautologicalAssertionRule) AnalyzeFile added in v1.4.2

func (r *TautologicalAssertionRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile inspects test files only: an assertion outside a test is not an assertion.

type TechDebtRule

type TechDebtRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

TechDebtRule detects technical debt patterns beyond simple TODO comments

func NewTechDebtRule

func NewTechDebtRule() *TechDebtRule

NewTechDebtRule creates the rule

func (*TechDebtRule) AnalyzeFile

func (r *TechDebtRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for tech debt patterns

type TerminalAfterFailedCheckpointRule added in v1.4.2

type TerminalAfterFailedCheckpointRule struct {
	*rules.BaseRule
}

TerminalAfterFailedCheckpointRule detects terminal success after a durable checkpoint failure was ignored.

func NewTerminalAfterFailedCheckpointRule added in v1.4.2

func NewTerminalAfterFailedCheckpointRule() *TerminalAfterFailedCheckpointRule

NewTerminalAfterFailedCheckpointRule creates the rule.

func (*TerminalAfterFailedCheckpointRule) AnalyzeFile added in v1.4.2

AnalyzeFile checks each production Go function independently.

type TestExternalServiceRule added in v1.4.2

type TestExternalServiceRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

TestExternalServiceRule detects tests that reach a live third-party service.

A test exists to check our own code. Calling someone else's API from a test buys nothing — the vendor is not under our control and their behaviour is not our contract — and it costs three things: the suite starts failing on their outages and quotas, the vendor's rate limits turn into flaky red builds, and, worst, the test changes the outside world.

Real case (ProjectA, 2026-07-30). Four tests talked to production APIs on every `make test-all`: two of them took real deposit requisites from the payment provider cryptoprov, two burned paid ExtVault Pro credits. Over one week the suite consumed 537 provider addresses. The failures looked like flaky parallelism — the provider starts answering 451 after roughly ten allocations per window, so the more workers, the more red.

Two shapes are reported.

  • outbound_client: a test calls into a package that both talks HTTP and carries a literal third-party URL. Packages that merely hold configuration, or clients aimed at localhost, do not qualify — the package must actually make outbound requests.

  • credential_gate: a test guards itself with "skip unless the credential is present". That is not a guard at all. Credentials live in .env, and test runners export it (`set -a && source .env`), so the gate is open in exactly the environment where the test runs. Both ProjectA vault tests carried `if os.Getenv("EXTVAULT_ACCESS_KEY") == "" { t.Skip() }` and were documented as manual; they ran every single time. An opt-in must be a switch nobody sets by accident, not a secret everybody has.

A test that genuinely needs the live service stays possible: name its opt-in helper in the guard_functions setting, and calls guarded by it are not reported.

func NewTestExternalServiceRule added in v1.4.2

func NewTestExternalServiceRule() *TestExternalServiceRule

NewTestExternalServiceRule creates the rule

func (*TestExternalServiceRule) AnalyzeFile added in v1.4.2

func (r *TestExternalServiceRule) AnalyzeFile(_ *core.FileContext) []*core.Violation

AnalyzeFile is a no-op: deciding whether a called package talks to the outside world needs the whole project, not one file.

func (*TestExternalServiceRule) AnalyzeGoProject added in v1.4.2

func (r *TestExternalServiceRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject marks packages that make outbound calls, then looks for tests reaching them.

func (*TestExternalServiceRule) Configure added in v1.4.2

func (r *TestExternalServiceRule) Configure(settings map[string]any) error

Configure accepts the list of opt-in helpers that legitimise a live call.

func (*TestExternalServiceRule) RequiresSSA added in v1.4.2

func (r *TestExternalServiceRule) RequiresSSA() bool

RequiresSSA reports that plain typed packages are enough — no SSA program needed.

type TestSchemaMutationWithoutCleanupRule added in v1.4.2

type TestSchemaMutationWithoutCleanupRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

TestSchemaMutationWithoutCleanupRule detects a test that changes the database schema and leaves the change behind.

Тестовые базы обычно переиспользуются между прогонами (пул с TRUNCATE, шаблонная база, общий контейнер). Строки такой тест за собой чистит, а колонку или таблицу — нет: TRUNCATE структуру не трогает. Дальше своя же колонка ломает следующий прогон («column already exists»), а чужие тесты получают базу, не совпадающую со схемой из миграций, и падают в стороне от причины.

Правило требует, чтобы рядом с DDL стояла отмена: t.Cleanup или defer. Что именно там написано, правило не проверяет — важно, что автор про возврат схемы подумал.

func NewTestSchemaMutationWithoutCleanupRule added in v1.4.2

func NewTestSchemaMutationWithoutCleanupRule() *TestSchemaMutationWithoutCleanupRule

NewTestSchemaMutationWithoutCleanupRule creates the rule.

func (*TestSchemaMutationWithoutCleanupRule) AnalyzeFile added in v1.4.2

AnalyzeFile reports test functions that run DDL with no undo registered.

type TestWithoutAssertionRule added in v1.4.2

type TestWithoutAssertionRule struct {
	*rules.BaseRule
}

TestWithoutAssertionRule detects Go test functions that cannot fail because they never assert anything — typically "documentation" tests that print a finding and stay green forever:

func TestOverflowProtection(t *testing.T) {
    result := maxInt64.Mul(million)
    t.Logf("VULNERABILITY: No overflow detection on Mul()")
}

Such a test claims coverage of behaviour nobody verifies. Either assert what the code must do, or delete the test.

The give-away is t.Log/t.Logf standing where an assertion belongs: the test states a finding instead of checking it. A test that merely exercises code without logging is a different, legitimate thing — it fails if that code panics — and is not reported.

Not flagged: tests that assert via testify/t.Error/t.Fatal, tests that hand *testing.T to a helper (the helper asserts), tests whose only statement is a compile-time assertion (`var _ Iface = (*Impl)(nil)` — the compiler enforces it), smoke calls without logging, skipped tests (see skipped-tests), TestMain, benchmarks and fuzz targets.

Companion rules: unfalsifiable-test-case covers TS/JS tests whose assertions hold regardless of behaviour; tautological-assertion covers `require.True(t, true)`. This one covers the "no assertion at all" case in Go.

func NewTestWithoutAssertionRule added in v1.4.2

func NewTestWithoutAssertionRule() *TestWithoutAssertionRule

NewTestWithoutAssertionRule creates the rule

func (*TestWithoutAssertionRule) AnalyzeFile added in v1.4.2

func (r *TestWithoutAssertionRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks Go test functions for the missing-assertion pattern

type TimeEqualRule

type TimeEqualRule struct {
	*rules.BaseRule
}

TimeEqualRule detects time.Time comparisons using == instead of .Equal()

func NewTimeEqualRule

func NewTimeEqualRule() *TimeEqualRule

NewTimeEqualRule creates the rule

func (*TimeEqualRule) AnalyzeFile

func (r *TimeEqualRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for time.Time == comparisons

type TodoCommentRule

type TodoCommentRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

TodoCommentRule finds actionable TODO/FIXME/HACK/XXX comments in code

func NewTodoCommentRule

func NewTodoCommentRule() *TodoCommentRule

NewTodoCommentRule creates the rule

func (*TodoCommentRule) AnalyzeFile

func (r *TodoCommentRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile finds actionable task comments

type TombstoneCommentRule added in v1.4.2

type TombstoneCommentRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

TombstoneCommentRule detects "tombstone" comments describing code that was deleted:

// GetDB removed — architectural boundary violation eliminated
// УДАЛЕНО: processed статус (дубликат approved)
_ = disableFixes // CryptoProv fixes removed

CLAUDE.md: "Delete cleanly, git remembers" — history lives in git, not in comments. A tombstone is noise the moment the commit lands.

Not flagged: behavior descriptions ("entries are removed after TTL"), godoc deprecation markers (owned by the deprecated-comment rule), policy quotes.

func NewTombstoneCommentRule added in v1.4.2

func NewTombstoneCommentRule() *TombstoneCommentRule

NewTombstoneCommentRule creates the rule

func (*TombstoneCommentRule) AnalyzeFile added in v1.4.2

func (r *TombstoneCommentRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks comment lines for tombstones

type TypeInferrer

type TypeInferrer struct {
	// contains filtered or unexported fields
}

TypeInferrer infers types from AST declarations within a file

func NewTypeInferrer

func NewTypeInferrer(file *ast.File) *TypeInferrer

NewTypeInferrer creates a type inferrer and collects type info from the AST

func NewTypeInferrerFromNode added in v1.4.2

func NewTypeInferrerFromNode(node ast.Node) *TypeInferrer

NewTypeInferrerFromNode creates a type inferrer for a specific AST scope.

func (*TypeInferrer) GetType

func (ti *TypeInferrer) GetType(name string) (TypeInfo, bool)

GetType returns type info for a variable name. Names bound to several types in the same file report no type at all rather than the first one seen.

func (*TypeInferrer) IsAmbiguous added in v1.4.2

func (ti *TypeInferrer) IsAmbiguous(name string) bool

IsAmbiguous reports whether the file binds this name to more than one type.

func (*TypeInferrer) IsAny added in v1.4.2

func (ti *TypeInferrer) IsAny(name string) bool

IsAny checks if a variable is any/interface{}. It follows the GetType contract: a name the file binds to several types is ambiguous and reports no type at all — not the first one seen.

func (*TypeInferrer) IsDeclared added in v1.4.2

func (ti *TypeInferrer) IsDeclared(name string) bool

IsDeclared reports whether the analyzed scope introduces this name at all, regardless of whether its type could be inferred.

type TypeInfo

type TypeInfo struct {
	IsSlice         bool
	IsMap           bool
	IsTime          bool
	IsError         bool
	IsChan          bool
	TypeName        string // e.g., "[]string", "time.Time", "error"
	ElementTypeName string
}

TypeInfo holds inferred type information for a variable

type TypedNilIntoInterfaceRule added in v1.4.2

type TypedNilIntoInterfaceRule struct {
	*rules.BaseRule
}

TypedNilIntoInterfaceRule detects a nil-able concrete pointer handed to an interface.

Указатель, равный nil, уложенный в интерфейс, интерфейсом nil не является: у значения есть тип, поэтому `iface == nil` даёт false. Получатель, который отличает отсутствие зависимости именно этой проверкой, пропускает её и вызывает метод на nil-получателе.

Реальный случай (projectA, REF-446): сервис доходности принял алертер параметром конструктора, и точки сборки стали передавать *email.Service напрямую. Без SMTP это nil-указатель. Проверка `if s.anomalyAlerter == nil { return }` его не поймала, и первый же аномальный день в истории vault уронил пользовательский график баланса паникой вместо того, чтобы просто не отправить письмо.

Правило намеренно узкое, иначе тонет в шуме:

  • указатель должен где-то в этом же файле сравниваться с nil — иначе считать его пустым нет оснований;
  • получатель должен зависимость сохранять: конструктор, сеттер или присваивание в интерфейсную переменную. Передача в обходчик вроде ast.Inspect не в счёт;
  • проверка на nil в Cleanup/Close доказательством не считается: teardown по замыслу переживает частично собранный объект;
  • присваивание внутри `if ptr != nil { ... }` и код после `if ptr == nil { return }` признаются правильными — это и есть нужная нормализация.

func NewTypedNilIntoInterfaceRule added in v1.4.2

func NewTypedNilIntoInterfaceRule() *TypedNilIntoInterfaceRule

NewTypedNilIntoInterfaceRule creates the rule.

func (*TypedNilIntoInterfaceRule) AnalyzeFile added in v1.4.2

AnalyzeFile does nothing: nil-ability evidence is collected across the whole project.

func (*TypedNilIntoInterfaceRule) AnalyzeGoProject added in v1.4.2

func (r *TypedNilIntoInterfaceRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject collects nil-checked pointers, then finds where they enter interfaces.

func (*TypedNilIntoInterfaceRule) RequiresSSA added in v1.4.2

func (r *TypedNilIntoInterfaceRule) RequiresSSA() bool

RequiresSSA reports that typed packages are enough — no SSA program needed.

type UnboundedResponseReadRule added in v1.4.2

type UnboundedResponseReadRule struct {
	*rules.BaseRule
}

UnboundedResponseReadRule detects unbounded reads of HTTP response bodies.

func NewUnboundedResponseReadRule added in v1.4.2

func NewUnboundedResponseReadRule() *UnboundedResponseReadRule

NewUnboundedResponseReadRule creates the rule.

func (*UnboundedResponseReadRule) AnalyzeFile added in v1.4.2

func (r *UnboundedResponseReadRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile checks for unbounded HTTP response body reads.

type UnboundedSyncMapRule added in v1.4.2

type UnboundedSyncMapRule struct {
	*rules.BaseRule
}

UnboundedSyncMapRule detects package-level sync.Map variables that production code grows (Store/LoadOrStore) but never shrinks (Delete/CompareAndDelete/ Clear). In a long-running process such a map is a slow leak: every new key stays forever.

Родилось из ревью projectD 2026-08 (№22): пакетные sync.Map доменных расписаний, Crawl-delay и состояний robots.txt копили по записи на каждый встреченный домен. Демон работает месяцами — набор доменов только растёт, вытеснения не было ни в одном файле пакета.

Не считаются: локальные sync.Map (живут не дольше функции), карты без записей и карты, у которых есть хоть одно не-методное использование (передача указателя наружу — судьбу записей отсюда не видно). Delete только в _test.go не спасает: production-рост он не ограничивает.

func NewUnboundedSyncMapRule added in v1.4.2

func NewUnboundedSyncMapRule() *UnboundedSyncMapRule

NewUnboundedSyncMapRule creates the rule.

func (*UnboundedSyncMapRule) AnalyzeFile added in v1.4.2

func (r *UnboundedSyncMapRule) AnalyzeFile(_ *core.FileContext) []*core.Violation

AnalyzeFile does nothing: the rule needs the whole package to see eviction.

func (*UnboundedSyncMapRule) AnalyzeGoProject added in v1.4.2

func (r *UnboundedSyncMapRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject inspects every package for grow-only package-level sync.Maps.

func (*UnboundedSyncMapRule) RequiresSSA added in v1.4.2

func (r *UnboundedSyncMapRule) RequiresSSA() bool

RequiresSSA reports that typed packages are enough — no SSA program needed.

type UnfalsifiableTestCaseRule added in v1.4.2

type UnfalsifiableTestCaseRule struct {
	*rules.BaseRule
	// contains filtered or unexported fields
}

UnfalsifiableTestCaseRule detects a browser/API test whose every assertion holds no matter what the code under test does.

Родилось из разбора e2e-набора projectA (REF-410/REF-468). Два спека «проверяли» показ балансов так: мокали два адреса, которых на бэкенде не существует, шли на страницу, которой в приложении нет, и утверждали «URL содержит deposits, body виден, элементов больше нуля». Они годами проходили против страницы 404 и считались покрытием. Ещё несколько спеков писали expect([200, 404]).toContain(status) — при таком наборе одинаково засчитываются и рабочий эндпоинт, и удалённый.

Каждое из таких утверждений по отдельности бывает уместно как разогрев. Признак проблемы в том, что в тесте нет ни одного другого: тогда тест не может упасть и сообщает только о том, что фронтенд отдал HTML.

Правило работает по TypeScript и JavaScript тестам. Go-тесты закрывает tautological-assertion.

func NewUnfalsifiableTestCaseRule added in v1.4.2

func NewUnfalsifiableTestCaseRule() *UnfalsifiableTestCaseRule

NewUnfalsifiableTestCaseRule creates the rule.

func (*UnfalsifiableTestCaseRule) AnalyzeFile added in v1.4.2

func (r *UnfalsifiableTestCaseRule) AnalyzeFile(ctx *core.FileContext) []*core.Violation

AnalyzeFile walks each test body and reports the ones with no falsifiable assertion.

type UnguardedSharedFieldRule added in v1.4.2

type UnguardedSharedFieldRule struct {
	*rules.BaseRule
}

UnguardedSharedFieldRule detects a field that some methods protect with a mutex and others touch without it:

func (c *Counter) Add(n int) { c.mu.Lock(); defer c.mu.Unlock(); c.value += n }
func (c *Counter) Reset()    { c.value = 0 }   // same field, no lock

The lock proves the field is shared between goroutines; the method that skips it is a data race. Unlike a missing Unlock it breaks nothing locally, and the race detector only reports it when two goroutines happen to collide during a test — so it survives review and CI and fails in production.

A lock covers the object that owns it, at any depth: s.metrics.mu.Lock() guards s.metrics.total, not s.other.

Not flagged: fields no lock ever covers (they may be set once and only read afterwards), helpers called from inside a critical section, methods whose name promises the caller holds the lock (…Locked, …NoLock, …Unsafe), and plain functions such as constructors, where the value is not shared yet.

func NewUnguardedSharedFieldRule added in v1.4.2

func NewUnguardedSharedFieldRule() *UnguardedSharedFieldRule

NewUnguardedSharedFieldRule creates the rule

func (*UnguardedSharedFieldRule) AnalyzeFile added in v1.4.2

func (r *UnguardedSharedFieldRule) AnalyzeFile(_ *core.FileContext) []*core.Violation

AnalyzeFile is a no-op: the methods of a type may live in several files.

func (*UnguardedSharedFieldRule) AnalyzeGoProject added in v1.4.2

func (r *UnguardedSharedFieldRule) AnalyzeGoProject(ctx *core.GoProjectContext) ([]*core.Violation, error)

AnalyzeGoProject compares, for every field a lock covers somewhere, the places that take the lock with the places that do not.

func (*UnguardedSharedFieldRule) RequiresSSA added in v1.4.2

func (r *UnguardedSharedFieldRule) RequiresSSA() bool

RequiresSSA reports that typed syntax is enough for this rule.

Source Files

Jump to

Keyboard shortcuts

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