{"id":51632922,"url":"https://github.com/samgozman/maxcomments-lint","last_synced_at":"2026-07-13T10:30:31.322Z","repository":{"id":366114451,"uuid":"1274070076","full_name":"samgozman/maxcomments-lint","owner":"samgozman","description":"A golangci-lint module plugin that caps the number of comment lines","archived":false,"fork":false,"pushed_at":"2026-06-20T09:22:00.000Z","size":929,"stargazers_count":1,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-20T11:09:42.507Z","etag":null,"topics":["ci","go","golang","golangci-lint","linter"],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/samgozman.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-19T06:37:27.000Z","updated_at":"2026-06-20T09:15:29.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/samgozman/maxcomments-lint","commit_stats":null,"previous_names":["samgozman/maxcomments-lint"],"tags_count":1,"template":false,"template_full_name":null,"purl":"pkg:github/samgozman/maxcomments-lint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samgozman%2Fmaxcomments-lint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samgozman%2Fmaxcomments-lint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samgozman%2Fmaxcomments-lint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samgozman%2Fmaxcomments-lint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/samgozman","download_url":"https://codeload.github.com/samgozman/maxcomments-lint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/samgozman%2Fmaxcomments-lint/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35420322,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-13T02:00:06.543Z","response_time":119,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ci","go","golang","golangci-lint","linter"],"created_at":"2026-07-13T10:30:30.702Z","updated_at":"2026-07-13T10:30:31.314Z","avatar_url":"https://github.com/samgozman.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# maxcomments-lint\n\n[![CI](https://github.com/samgozman/maxcomments-lint/actions/workflows/ci.yml/badge.svg)](https://github.com/samgozman/maxcomments-lint/actions/workflows/ci.yml)\n[![Go Reference](https://pkg.go.dev/badge/github.com/samgozman/maxcomments-lint.svg)](https://pkg.go.dev/github.com/samgozman/maxcomments-lint)\n[![codecov](https://codecov.io/gh/samgozman/maxcomments-lint/graph/badge.svg?token=u4ogknIYUs)](https://codecov.io/gh/samgozman/maxcomments-lint)\n\nA [golangci-lint](https://golangci-lint.run) plugin that limits how many\ncomment lines a function or file may contain.\n\nIt flags functions that narrate every line instead of being split up or\nsimplified:\n\n```go\n// BAD: a comment on nearly every line\nfunc process(items []Item) error {\n\t// loop over all items\n\tfor _, item := range items {\n\t\t// skip invalid items\n\t\tif !item.Valid() {\n\t\t\tcontinue\n\t\t}\n\t\t// save the item\n\t\tif err := save(item); err != nil {\n\t\t\t// return the error\n\t\t\treturn err\n\t\t}\n\t}\n\treturn nil\n}\n```\n\nIt checks comment *quantity*, not *style*. For style, use\n[godot](https://github.com/tetafro/godot), `gocritic`, or `revive`.\n\nThe cap can be a flat number of comment lines or a *ratio* (at most one comment\nline per N lines of code), enforced per function and/or per file. So a small\nhelper and a 200-line function can be held to proportional budgets instead of\nthe same fixed limit.\n\n**Especially useful against AI-generated noise.** Coding assistants tend to\ncomment nearly every line. Capping comment density nudges that output back\ntoward self-explanatory code. And it's a compiled check, so it's\n*deterministic*: it enforces the limit on every run, unlike a prompt or rules\nfile the model may ignore.\n\n## How it works\n\nSet a limit, get a warning when code exceeds it. Limits come in two flavours,\neach usable per **function** and/or per **file**:\n\n- **Hard cap:** a fixed maximum number of comment lines\n  (`func.body-lines`, `func.doc-lines`, `file.lines`).\n- **Ratio:** at most one comment line per *N* code lines\n  (`func.ratio`, `file.ratio`); the budget is `floor(codeLines / N)`. A *code\n  line* is a non-blank line that isn't itself a comment. Use `ratio-min-lines`\n  to skip small scopes.\n\n### What counts as a comment\n\n- **Doc vs. body are counted separately.** A function's doc comment (the block\n  above `func`) is governed by `func.doc-lines`; everything inside the body by\n  `func.body-lines`/`func.ratio`. So a long, legitimate doc comment never trips\n  the budget meant for line-by-line body narration.\n- **Closures count on their own.** Each comment belongs to the *innermost*\n  function containing it, so a closure's comments are never folded into the\n  enclosing function.\n- **Lines, not tokens.** A `/* */` block or a stack of `//` lines counts by its\n  actual line span.\n- **Directives are never counted.** `//nolint:...`, `//go:generate`,\n  `//go:embed`, `//line ...` and the like are tooling instructions, not docs.\n- **File scope** sums every comment in the file, including the package doc.\n\n## Settings\n\nPer-scope budgets are grouped under `func:` and `file:`; the remaining keys are\nscope-independent and stay at the top level.\n\n| Key               | Type     | Default        | Description                                                                                |\n|-------------------|----------|----------------|--------------------------------------------------------------------------------------------|\n| `func.body-lines` | int      | `0` (disabled) | Hard cap: max **body** comment lines allowed per function (doc comment excluded).          |\n| `func.doc-lines`  | int      | `0` (disabled) | Hard cap: max **doc** comment lines allowed per function (the block above `func`).         |\n| `func.ratio`      | int      | `0` (disabled) | Ratio: allow 1 **body** comment line per this many code lines, per function.               |\n| `file.lines`      | int      | `0` (disabled) | Hard cap: max comment lines allowed per file.                                              |\n| `file.ratio`      | int      | `0` (disabled) | Ratio: allow 1 comment line per this many code lines, per file.                            |\n| `ratio-min-lines` | int      | `0` (no floor) | Skip the ratio checks for any scope with fewer than this many code lines.                  |\n| `ignore`          | []string | `[]` (none)    | Regular expressions matched against each file's path; matching files are skipped entirely. |\n| `check-generated` | bool     | `false`        | Check machine-generated files too. By default generated files are skipped.                 |\n\n```yaml\nsettings:\n  func:\n    body-lines: 3\n    doc-lines: 5\n    ratio: 8\n  file:\n    lines: 120\n    ratio: 5\n  ratio-min-lines: 10\n  ignore:\n    - 'testdata/'\n  check-generated: false\n```\n\nEvery diagnostic ends with the name of the setting that triggered it (e.g.\n`... max allowed is 3 (func.body-lines)`), so you can tell which knob to tune\nwhen more than one check is enabled.\n\n### Suppressing with `//nolint`\n\nThis plugin honours golangci-lint's `//nolint` directives directly:\n\n- **Per function:** a `//nolint:maxcomments` (or bare `//nolint` / `//nolint:all`)\n  in a function's doc comment or trailing on its `func` line suppresses both\n  the cap and ratio reports for that function.\n- **Per file:** the same directive placed **before the `package` clause** at\n  the top of a file suppresses the file-level checks. Function-level checks\n  still apply.\n\n### Ignoring files and folders\n\n```yaml\nsettings:\n  ignore:\n    - 'vendor/'\n    - 'testdata/'\n    - '_test\\.go$'\n    - '\\.pb\\.go$'   # generated code\n```\n\nAn invalid regex is reported as an error rather than silently ignored.\n\n### Generated files\n\nMachine-generated files are **skipped by default**: there's no point nudging\na code generator toward fewer comments. A file counts as generated when it\ncarries the [standard Go marker](https://pkg.go.dev/cmd/go#hdr-Generate_Go_files_by_processing_source)\n(`// Code generated ... DO NOT EDIT.`) before its `package` clause.\n\nTo lint generated files like any other code, opt in:\n\n```yaml\nsettings:\n  check-generated: true\n```\n\nThis is independent of `ignore`: explicit `ignore` patterns always apply, and\ngenerated files are skipped on top of them unless `check-generated` is set.\n\n## Using it in a project\n\nModule plugins must be compiled into golangci-lint itself. See the\n[Module Plugin System docs](https://golangci-lint.run/docs/plugins/module-plugins/).\nYou don't clone this repo; you reference it by version from your own project.\n\n### 1. Add a `.custom-gcl.yml` to your project root\n\n```yaml\nversion: v2.12.2   # the golangci-lint version to build\nplugins:\n  - module: github.com/samgozman/maxcomments-lint\n    import: github.com/samgozman/maxcomments-lint/maxcomments\n    version: v0.1.0   # pin a released tag of this plugin\n```\n\n### 2. Build a custom golangci-lint binary\n\n```bash\ngolangci-lint custom   # reads .custom-gcl.yml, builds ./custom-gcl\n```\n\n### 3. Configure your project's `.golangci.yml`\n\n```yaml\nversion: \"2\"\n\nlinters:\n  default: none\n  enable:\n    - maxcomments\n  settings:\n    custom:\n      maxcomments:\n        type: module\n        description: Limits the number of comment lines per function and per file.\n        original-url: github.com/samgozman/maxcomments-lint\n        settings:\n          func:\n            body-lines: 5\n            doc-lines: 15\n            # optional ratio mode (1 body comment line per 10 code lines):\n            # ratio: 10\n          file:\n            lines: 150\n            # ratio: 10\n          # ratio-min-lines: 10\n          ignore:\n            - 'vendor/'\n            - 'testdata/'\n```\n\n### 4. Run it\n\n```bash\n./custom-gcl run\n```\n\n## Editor integration\n\nA custom golangci-lint binary works with your IDE just like the stock one. You\nonly have to point the IDE at *your* binary (`./bin/custom-gcl`) instead of the\none on your `PATH`. Build it first (see above), then configure the IDE.\n\n\u003cdetails\u003e\n\u003csummary\u003eJetBrains GoLand IDE\u003c/summary\u003e\n\n1. Open **Settings → Go → Linters**.\n2. Tick **Execute 'golangci-lint run'** (and **'golangci-lint fmt'** if you\n   want formatting too).\n3. Set **Executable** to the absolute path of your custom binary, e.g.\n   `/path/to/your/project/bin/custom-gcl`.\n4. Tick **Use config** and point it at your `.golangci.yml`.\n5. Click **OK**. `maxcomments` now shows up in the linters list and its\n   warnings appear inline in the editor and in the **Problems** view.\n\n![GoLand linter settings](docs/goland-linter-settings.png)\n\n\u003c/details\u003e\n\n## Running in CI\n\nCI is the same three steps as local use: install upstream golangci-lint, build\nthe custom binary from `.custom-gcl.yml`, then lint with it. See the\n[`custom` command docs](https://golangci-lint.run/docs/plugins/module-plugins/)\nfor details, and this repo's own [`.github/workflows/ci.yml`](.github/workflows/ci.yml)\nfor a working example:\n\n```yaml\n- name: Install golangci-lint\n  run: |\n    curl -sSfL https://raw.githubusercontent.com/golangci/golangci-lint/HEAD/install.sh \\\n      | sh -s -- -b \"$(go env GOPATH)/bin\" v2.12.2\n\n- name: Build the custom binary\n  run: golangci-lint custom   # reads .custom-gcl.yml\n\n- name: Lint\n  run: ./bin/custom-gcl run\n```\n\nKeep the golangci-lint version pinned the same in `.custom-gcl.yml` and your CI.\n\n## Development\n\n```bash\ngo mod tidy\ngo test ./...\n```\n\nEach behaviour has its own `analysistest` fixture under\n`maxcomments/testdata/src/` (one package per scenario: `funclines`,\n`funcdoclines`, `directives`, `closures`, `funcratio`, `fileratio`,\n`ratiomin`, `nolintfile`, `nolintfunc`, `ignore`, `generated`,\n`generatedcheck`), alongside white-box unit tests for the pure helpers\n(`isDirective`, `ratioViolation`, `nolintForMaxcomments`, `matchesAny`).\n\n## Known gaps\n\n- No autofix, by design. Fix flagged comments yourself, or ask an AI to\n  summarise them down.\n- A function-level `//nolint` on a `func` *signature line* is matched by line\n  number, the same way golangci-lint applies `//nolint` to the diagnostics it\n  receives. In the rare case where two `func` tokens share one physical source\n  line (e.g. a one-line closure nested in another function), a trailing\n  `//nolint` on that line suppresses both. Keep each function on its own line\n  (which `gofmt` already does) to scope the directive precisely.\n\n## Contributing\n\nContributions are welcome. See [CONTRIBUTING.md](CONTRIBUTING.md) for the\nworkflow, project layout, and testing conventions. Notable changes are recorded\nin [CHANGELOG.md](CHANGELOG.md).\n\n## License\n\nMIT. See [LICENSE](LICENSE).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamgozman%2Fmaxcomments-lint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsamgozman%2Fmaxcomments-lint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsamgozman%2Fmaxcomments-lint/lists"}