Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .claude/.gitignore
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
plans/
skills/
!skills/
skills/.local-skills/
commands/
agents/
hooks/
settings.local.json
1 change: 1 addition & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ This is a mono-repo with multiple Go modules tied together by `go.work`.
| `internal/difflib/` | Internalized go-difflib for generating diffs |
| `internal/fdleak/` | File descriptor leak detection |
| `internal/leak/` | Goroutine leak detection |
| `internal/tools` | Internal tools exposed |
| `enable/stubs/` | Public API for enabling optional features (yaml, colors) |

### Code generation architecture
Expand Down
121 changes: 121 additions & 0 deletions .claude/skills/add-assertion/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
# Adding a New Assertion

Step-by-step workflow for adding a new assertion function to testify.

## Workflow

1. Add function to the appropriate domain file in `internal/assertions/`
2. Add `// Domain: <domain>` as the first line inside the function body
3. Add `// Opposite: <Name>` on the next line if a logical opposite exists
4. Add `Examples:` section to the doc comment
5. Add tests to the corresponding `*_test.go` file
6. Run `go generate ./...` to produce all 8 variants + docs
7. Run `go test work ./...` to verify everything

## Function template

```go
// FuncName asserts that <what it does>.
//
// # Usage
//
// assertions.FuncName(t, arg1, arg2)
//
// # Examples
//
// success: arg1Value, arg2Value
// failure: arg1Value, arg2Value
func FuncName(t T, arg1, arg2 any, msgAndArgs ...any) bool {
// Domain: <domain>
// Opposite: <OppositeName> (omit if none)
if h, ok := t.(H); ok {
h.Helper()
}

// implementation
if !condition {
return Fail(t, "message", msgAndArgs...)
}

return true
}
```

## Doc comment annotations

### Domain tag (in doc comment header)

```go
// domain: equality
```

Assigns the function to a documentation domain. Add domain descriptions in
`internal/assertions/doc.go` if creating a new domain.

### Domain comment (inside function body)

```go
// Domain: equality
```

First line inside the function body. Used by the codegen scanner.

### Opposite annotation

```go
// Opposite: NotEqual
```

Second line inside the body (after Domain). Only on the affirmative form
(e.g., on `Equal`, not on `NotEqual`).

### Examples section

```go
// # Examples
//
// success: 123, 123
// failure: 123, 456
```

Drives generated smoke tests for all 8 variants. Three case types:
- `success: <args>` -- test should pass
- `failure: <args>` -- test should fail
- `panic: <args>` followed by `<expected panic message>` on next line

For complex values that can't be represented inline, use `// NOT IMPLEMENTED`:
```go
// success: &customStruct{Field: "value"}, // NOT IMPLEMENTED
```

**Never use `// TODO`** -- it triggers false positives in code quality tools.

## What gets generated

From a single function, `go generate` produces:

| Variant | Package | Example |
|---------|---------|---------|
| Package-level | `assert` | `assert.FuncName(t, ...)` |
| Formatted | `assert` | `assert.FuncNamef(t, ..., "msg")` |
| Forward method | `assert` | `a.FuncName(...)` |
| Forward formatted | `assert` | `a.FuncNamef(..., "msg")` |
| Package-level | `require` | `require.FuncName(t, ...)` |
| Formatted | `require` | `require.FuncNamef(t, ..., "msg")` |
| Forward method | `require` | `r.FuncName(...)` |
| Forward formatted | `require` | `r.FuncNamef(..., "msg")` |

Plus: tests for all variants, documentation entry in `docs/doc-site/api/<domain>.md`.

Generic assertions (with type params) produce 4 variants (no forward methods --
Go limitation).

## Checklist

- [ ] Function in `internal/assertions/<domain>.go`
- [ ] `// Domain:` and optionally `// Opposite:` inside function body
- [ ] Doc comment with `# Usage`, `# Examples` sections
- [ ] Tests in `internal/assertions/<domain>_test.go`
- [ ] `go generate ./...` succeeds
- [ ] `go test work ./...` passes
- [ ] `golangci-lint run --new-from-rev master` clean
85 changes: 85 additions & 0 deletions .claude/skills/codegen/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Code Generation

How the testify code and documentation generator works.

## Running

```bash
# Generate everything (code + docs) from internal/assertions/
go generate ./...

# Or run the generator directly
cd codegen && go run . -output-packages assert,require -include-doc

# Code only (skip docs)
cd codegen && go run . -output-packages assert,require -include-doc=false
```

## Generator pipeline

1. **Scanner** (`codegen/internal/scanner/`) parses `internal/assertions/` using
`go/packages` and `go/types`. Extracts function signatures, doc comments,
domain tags, examples, and metadata.
- `comments/` -- doc comment extraction
- `comments-parser/` -- domain tags, examples, metadata parsing
- `signature/` -- function signature analysis

2. **Model** (`codegen/internal/model/`) holds the intermediate representation:
functions, type params, tests, documentation, metrics.

3. **Generator** (`codegen/internal/generator/`) renders templates:
- **Code templates** produce `assert/` and `require/` packages with all variants
- **Doc templates** produce Hugo markdown in `docs/doc-site/api/`
- **Metrics** produces `metrics.yaml` for Hugo site params

## Key templates

Located in `codegen/internal/generator/templates/`:

| Template | Produces |
|----------|----------|
| `assertion_assertions.gotmpl` | `assert` package-level functions |
| `assertion_format.gotmpl` | `assert` formatted variants (`*f`) |
| `assertion_forward.gotmpl` | `assert` forward methods |
| `requirement_*.gotmpl` | `require` equivalents (calls `FailNow`) |
| `doc_index.md.gotmpl` | API index page (`_index.md`) |
| `doc_page.md.gotmpl` | Per-domain doc pages |
| `doc_metrics.md.gotmpl` | Quick index & metrics page |

## Template functions

Custom template functions in `codegen/internal/generator/funcmaps/`:
- `Slugize(name)` -- converts function name to markdown anchor
- `Titleize(s)` -- title-cases a string
- `hopen` / `hclose` -- Hugo shortcode delimiters (`{{% ... %}}`)

## Domain organization

Functions are grouped by `// Domain: <name>` tags inside the function body.
Domain descriptions live in `internal/assertions/doc.go` as special comments.
The generator reorganizes package-based docs into domain-based pages
(19 domains currently).

## Generated output

**Never edit generated files directly.** They carry a `DO NOT EDIT` header.

| Output | Location |
|--------|----------|
| `assert/` package | Generated functions + tests |
| `require/` package | Generated functions + tests |
| `docs/doc-site/api/*.md` | Domain-organized Hugo pages |
| `docs/doc-site/api/metrics.md` | Quick index + API metrics |
| `hack/doc-site/hugo/metrics.yaml` | Hugo site params (counts) |

Exceptions (not generated): `assert/doc.go`, `require/doc.go`, ad-hoc testable examples.

## Adding support for a new construct

If the generator needs to support a new Go construct (e.g., new type param
pattern), the work is in:
1. `codegen/internal/scanner/` -- teach the scanner to extract it
2. `codegen/internal/model/` -- add fields to the model
3. `codegen/internal/generator/templates/` -- update templates to render it

The scanner and generator have comprehensive tests (~1,400+ lines across test files).
80 changes: 80 additions & 0 deletions .claude/skills/doc-site/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Documentation Site

Hugo-based documentation site for testify, auto-generated from source code.

## Running locally

```bash
# 1. Generate docs from source
go generate ./...

# 2. Start Hugo dev server
cd hack/doc-site/hugo
./gendoc.sh

# Visit http://localhost:1313/testify/
# Auto-reloads on changes to docs/doc-site/
```

## Site structure

```
hack/doc-site/hugo/
hugo.yaml # Main Hugo config
metrics.yaml # Generated metrics (codegen output, merged into site params)
testify.yaml # Version info + build metadata
gendoc.sh # Dev server launcher
layouts/ # Custom layout overrides
themes/hugo-relearn/ # Relearn documentation theme

docs/doc-site/ # Content (mounted by Hugo)
api/ # Generated: domain pages, index, metrics (DO NOT EDIT)
usage/ # Hand-written: USAGE, GENERICS, CHANGES, MIGRATION, etc.
project/ # Hand-written: APPROACH, maintainer docs
```

## Generated vs hand-written content

| Path | Generated? | Notes |
|------|-----------|-------|
| `docs/doc-site/api/*.md` | Yes | Domain pages, index, metrics. Regenerate with `go generate` |
| `docs/doc-site/api/metrics.md` | Yes | Quick index table + API counts |
| `docs/doc-site/usage/*.md` | No | Hand-written guides |
| `docs/doc-site/project/*.md` | No | Hand-written project docs |

## Dynamic counts via Hugo params

Function and assertion counts are generated into `metrics.yaml` and merged
into Hugo's `site.Params.metrics`. Use the relearn `siteparam` shortcode
to reference them in hand-written markdown:

```markdown
We have {{% siteparam "metrics.assertions" %}} assertions across
{{% siteparam "metrics.domains" %}} domains.
```

Available params: `metrics.domains`, `metrics.functions`, `metrics.assertions`,
`metrics.generics`, `metrics.nongeneric_assertions`, `metrics.helpers`, `metrics.others`.

Per-domain: `metrics.by_domain.<slug>.count`, `metrics.by_domain.<slug>.name`.

Hugo math functions (`sub`, `mul`, `add`) are NOT available in markdown content.
For computed values, add them to the codegen `buildMetrics()` in
`codegen/internal/generator/doc_generator.go`.

## Adding a new hand-written page

1. Create `docs/doc-site/<section>/<FILE>.md` with Hugo front matter
2. Set `weight:` to control ordering in the sidebar
3. Use relearn shortcodes: `{{% notice %}}`, `{{% expand %}}`, `{{< tabs >}}`, etc.
4. Reference API counts with `{{% siteparam "metrics.<key>" %}}`

## Relearn theme features used

- `{{% notice style="info" %}}` -- callout boxes
- `{{% expand title="..." %}}` -- collapsible sections
- `{{< tabs >}}` / `{{% tab %}}` -- tabbed content
- `{{< cards >}}` / `{{% card %}}` -- side-by-side cards
- `{{% icon icon="star" color=orange %}}` -- inline icons
- `{{% siteparam "key" %}}` -- site param substitution
- `{{< mermaid >}}` -- diagrams
Loading
Loading