Skip to content

Commit 09ded41

Browse files
babakksldez
andauthored
feat: add godoclint linter (#6062)
Co-authored-by: Fernandez Ludovic <[email protected]>
1 parent 5678698 commit 09ded41

14 files changed

+674
-0
lines changed

.golangci.next.reference.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,7 @@ linters:
5959
- goconst
6060
- gocritic
6161
- gocyclo
62+
- godoclint
6263
- godot
6364
- godox
6465
- goheader
@@ -171,6 +172,7 @@ linters:
171172
- goconst
172173
- gocritic
173174
- gocyclo
175+
- godoclint
174176
- godot
175177
- godox
176178
- goheader
@@ -1268,6 +1270,74 @@ linters:
12681270
# Default: 30 (but we recommend 10-20)
12691271
min-complexity: 10
12701272

1273+
godoclint:
1274+
# Default set of rules to enable.
1275+
# Possible values are: `basic`, `all` or `none`.
1276+
# Default: `basic` (enables `pkg-doc`, `single-pkg-doc`, `start-with-name`, and `deprecated`)
1277+
default: all
1278+
1279+
# List of rules to enable in addition to the default set.
1280+
# Default: empty
1281+
enable:
1282+
# Check proper package-level godoc, if any.
1283+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#pkg-doc
1284+
- pkg-doc
1285+
# Assert at most one godoc per package.
1286+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#single-pkg-doc
1287+
- single-pkg-doc
1288+
# Require all packages to have a godoc.
1289+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#require-pkg-doc
1290+
- require-pkg-doc
1291+
# Assert symbol godocs start with the symbol name.
1292+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#start-with-name
1293+
- start-with-name
1294+
# Require godoc for all public symbols.
1295+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#require-doc
1296+
- require-doc
1297+
# Assert correct formatting of deprecation notes.
1298+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#deprecated
1299+
- deprecated
1300+
# Assert maximum line length for godocs.
1301+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#max-len
1302+
- max-len
1303+
# Assert no unused link in godocs.
1304+
# https://github.com/godoc-lint/godoc-lint?tab=readme-ov-file#no-unused-link
1305+
- no-unused-link
1306+
1307+
# List of rules to disable.
1308+
# Default: empty
1309+
disable:
1310+
- pkg-doc
1311+
- single-pkg-doc
1312+
- require-pkg-doc
1313+
- start-with-name
1314+
- require-doc
1315+
- deprecated
1316+
- max-len
1317+
- no-unused-link
1318+
1319+
# A map for fine-tuning individual rules.
1320+
# All subkeys are optional.
1321+
options:
1322+
max-len:
1323+
# Maximum line length for godocs, not including the `// `, or `/*` or `*/` tokens.
1324+
# Default: 77
1325+
length: 127
1326+
1327+
require-doc:
1328+
# Ignore exported (public) symbols when applying the `require-doc` rule.
1329+
# Default: false
1330+
ignore-exported: true
1331+
1332+
# Ignore unexported (private) symbols when applying the `require-doc` rule.
1333+
# Default: true
1334+
ignore-unexported: false
1335+
1336+
start-with-name:
1337+
# Include unexported symbols when applying the `start-with-name` rule.
1338+
# Default: false
1339+
include-unexported: true
1340+
12711341
godot:
12721342
# Comments to be checked: `declarations`, `toplevel`, `noinline` or `all`.
12731343
# Default: declarations

go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ require (
4646
github.com/go-critic/go-critic v0.13.0
4747
github.com/go-viper/mapstructure/v2 v2.4.0
4848
github.com/go-xmlfmt/xmlfmt v1.1.3
49+
github.com/godoc-lint/godoc-lint v0.10.0
4950
github.com/gofrs/flock v0.12.1
5051
github.com/golangci/asciicheck v0.5.0
5152
github.com/golangci/dupl v0.0.0-20250308024227-f665c8d69b32

go.sum

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

jsonschema/golangci.next.jsonschema.json

Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -477,6 +477,18 @@
477477
"-QF1012"
478478
]
479479
},
480+
"godoclint-rules": {
481+
"enum": [
482+
"pkg-doc",
483+
"single-pkg-doc",
484+
"require-pkg-doc",
485+
"start-with-name",
486+
"require-doc",
487+
"deprecated",
488+
"max-len",
489+
"no-unused-link"
490+
]
491+
},
480492
"gosec-rules": {
481493
"enum": [
482494
"G101",
@@ -795,6 +807,7 @@
795807
"goconst",
796808
"gocritic",
797809
"gocyclo",
810+
"godoclint",
798811
"godot",
799812
"godox",
800813
"err113",
@@ -1849,6 +1862,74 @@
18491862
}
18501863
}
18511864
},
1865+
"godoclintSettings": {
1866+
"type": "object",
1867+
"properties": {
1868+
"default": {
1869+
"type": "string",
1870+
"enum": ["all", "basic", "none"],
1871+
"default": "basic",
1872+
"description": "Default set of rules to enable."
1873+
},
1874+
"enable": {
1875+
"description": "List of rules to enable in addition to the default set.",
1876+
"type": "array",
1877+
"uniqueItems": true,
1878+
"items": {
1879+
"$ref": "#/definitions/godoclint-rules"
1880+
}
1881+
},
1882+
"disable": {
1883+
"description": "List of rules to disable.",
1884+
"type": "array",
1885+
"uniqueItems": true,
1886+
"items": {
1887+
"$ref": "#/definitions/godoclint-rules"
1888+
}
1889+
},
1890+
"options": {
1891+
"type": "object",
1892+
"description": "A map for setting individual rule options.",
1893+
"properties": {
1894+
"max-len": {
1895+
"type": "object",
1896+
"properties": {
1897+
"length": {
1898+
"type": "integer",
1899+
"description": "Maximum line length for godocs, not including the `// `, or `/*` or `*/` tokens.",
1900+
"default": 77
1901+
}
1902+
}
1903+
},
1904+
"require-doc": {
1905+
"type": "object",
1906+
"properties": {
1907+
"ignore-exported": {
1908+
"type": "boolean",
1909+
"description": "Ignore exported (public) symbols when applying the `require-doc` rule.",
1910+
"default": false
1911+
},
1912+
"ignore-unexported": {
1913+
"type": "boolean",
1914+
"description": "Ignore unexported (private) symbols when applying the `require-doc` rule.",
1915+
"default": true
1916+
}
1917+
}
1918+
},
1919+
"start-with-name": {
1920+
"type": "object",
1921+
"properties": {
1922+
"include-unexported": {
1923+
"type": "boolean",
1924+
"description": "Include unexported symbols when applying the `start-with-name` rule.",
1925+
"default": false
1926+
}
1927+
}
1928+
}
1929+
}
1930+
}
1931+
}
1932+
},
18521933
"godotSettings": {
18531934
"type": "object",
18541935
"additionalProperties": false,
@@ -4559,6 +4640,9 @@
45594640
"gocyclo": {
45604641
"$ref": "#/definitions/settings/definitions/gocycloSettings"
45614642
},
4643+
"godoclint": {
4644+
"$ref": "#/definitions/settings/definitions/godoclintSettings"
4645+
},
45624646
"godot": {
45634647
"$ref": "#/definitions/settings/definitions/godotSettings"
45644648
},

pkg/config/linters_settings.go

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -242,6 +242,7 @@ type LintersSettings struct {
242242
Goconst GoConstSettings `mapstructure:"goconst"`
243243
Gocritic GoCriticSettings `mapstructure:"gocritic"`
244244
Gocyclo GoCycloSettings `mapstructure:"gocyclo"`
245+
Godoclint GodoclintSettings `mapstructure:"godoclint"`
245246
Godot GodotSettings `mapstructure:"godot"`
246247
Godox GodoxSettings `mapstructure:"godox"`
247248
Goheader GoHeaderSettings `mapstructure:"goheader"`
@@ -521,6 +522,24 @@ type GoCycloSettings struct {
521522
MinComplexity int `mapstructure:"min-complexity"`
522523
}
523524

525+
type GodoclintSettings struct {
526+
Default *string `mapstructure:"default"`
527+
Enable []string `mapstructure:"enable"`
528+
Disable []string `mapstructure:"disable"`
529+
Options struct {
530+
MaxLen struct {
531+
Length *uint `mapstructure:"length"`
532+
} `mapstructure:"max-len"`
533+
RequireDoc struct {
534+
IgnoreExported *bool `mapstructure:"ignore-exported"`
535+
IgnoreUnexported *bool `mapstructure:"ignore-unexported"`
536+
} `mapstructure:"require-doc"`
537+
StartWithName struct {
538+
IncludeUnexported *bool `mapstructure:"include-unexported"`
539+
} `mapstructure:"start-with-name"`
540+
} `mapstructure:"options"`
541+
}
542+
524543
type GodotSettings struct {
525544
Scope string `mapstructure:"scope"`
526545
Exclude []string `mapstructure:"exclude"`
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
package godoclint
2+
3+
import (
4+
"errors"
5+
"fmt"
6+
"slices"
7+
8+
glcompose "github.com/godoc-lint/godoc-lint/pkg/compose"
9+
glconfig "github.com/godoc-lint/godoc-lint/pkg/config"
10+
"github.com/godoc-lint/godoc-lint/pkg/model"
11+
12+
"github.com/golangci/golangci-lint/v2/pkg/config"
13+
"github.com/golangci/golangci-lint/v2/pkg/goanalysis"
14+
"github.com/golangci/golangci-lint/v2/pkg/golinters/internal"
15+
)
16+
17+
func New(settings *config.GodoclintSettings) *goanalysis.Linter {
18+
var pcfg glconfig.PlainConfig
19+
20+
if settings != nil {
21+
err := checkSettings(settings)
22+
if err != nil {
23+
internal.LinterLogger.Fatalf("godoclint: %v", err)
24+
}
25+
26+
// The following options are explicitly ignored: they must be handled globally with exclusions or nolint directives.
27+
// - Include
28+
// - Exclude
29+
30+
// The following options are explicitly ignored: these options cannot work as expected because the global configuration about tests.
31+
// - Options.MaxLenIncludeTests
32+
// - Options.PkgDocIncludeTests
33+
// - Options.SinglePkgDocIncludeTests
34+
// - Options.RequirePkgDocIncludeTests
35+
// - Options.RequireDocIncludeTests
36+
// - Options.StartWithNameIncludeTests
37+
// - Options.NoUnusedLinkIncludeTests
38+
39+
pcfg = glconfig.PlainConfig{
40+
Default: settings.Default,
41+
Enable: settings.Enable,
42+
Disable: settings.Disable,
43+
Options: &glconfig.PlainRuleOptions{
44+
MaxLenLength: settings.Options.MaxLen.Length,
45+
MaxLenIncludeTests: pointer(true),
46+
PkgDocIncludeTests: pointer(false),
47+
SinglePkgDocIncludeTests: pointer(true),
48+
RequirePkgDocIncludeTests: pointer(false),
49+
RequireDocIncludeTests: pointer(true),
50+
RequireDocIgnoreExported: settings.Options.RequireDoc.IgnoreExported,
51+
RequireDocIgnoreUnexported: settings.Options.RequireDoc.IgnoreUnexported,
52+
StartWithNameIncludeTests: pointer(false),
53+
StartWithNameIncludeUnexported: settings.Options.StartWithName.IncludeUnexported,
54+
NoUnusedLinkIncludeTests: pointer(true),
55+
},
56+
}
57+
}
58+
59+
composition := glcompose.Compose(glcompose.CompositionConfig{
60+
BaseDirPlainConfig: &pcfg,
61+
ExitFunc: func(_ int, err error) {
62+
internal.LinterLogger.Errorf("godoclint: %v", err)
63+
},
64+
})
65+
66+
return goanalysis.
67+
NewLinterFromAnalyzer(composition.Analyzer.GetAnalyzer()).
68+
WithLoadMode(goanalysis.LoadModeSyntax)
69+
}
70+
71+
func checkSettings(settings *config.GodoclintSettings) error {
72+
switch deref(settings.Default) {
73+
case string(model.DefaultSetAll):
74+
if len(settings.Enable) > 0 {
75+
return errors.New("cannot use 'enable' with 'default=all'")
76+
}
77+
78+
case string(model.DefaultSetNone):
79+
if len(settings.Disable) > 0 {
80+
return errors.New("cannot use 'disable' with 'default=none'")
81+
}
82+
83+
default:
84+
for _, rule := range settings.Enable {
85+
if slices.Contains(settings.Disable, rule) {
86+
return fmt.Errorf("a rule cannot be enabled and disabled at the same time: '%s'", rule)
87+
}
88+
}
89+
90+
for _, rule := range settings.Disable {
91+
if slices.Contains(settings.Enable, rule) {
92+
return fmt.Errorf("a rule cannot be enabled and disabled at the same time: '%s'", rule)
93+
}
94+
}
95+
}
96+
97+
return nil
98+
}
99+
100+
func pointer[T any](v T) *T { return &v }
101+
102+
func deref[T any](v *T) T {
103+
if v == nil {
104+
var zero T
105+
return zero
106+
}
107+
108+
return *v
109+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package godoclint
2+
3+
import (
4+
"testing"
5+
6+
"github.com/golangci/golangci-lint/v2/test/testshared/integration"
7+
)
8+
9+
func TestFromTestdata(t *testing.T) {
10+
integration.RunTestdata(t)
11+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
version: "2"
2+
3+
linters:
4+
settings:
5+
godoclint:
6+
default: none
7+
enable:
8+
- pkg-doc
9+
- require-pkg-doc
10+
- start-with-name
11+
- require-doc
12+
- deprecated
13+
- max-len
14+
- no-unused-link
15+
options:
16+
start-with-name:
17+
include-unexported: true
18+
require-doc:
19+
ignore-exported: false
20+
ignore-unexported: false
21+
max-len:
22+
length: 127

0 commit comments

Comments
 (0)