@@ -50,7 +50,12 @@ commitlint checks if your commit message meets the [conventional commit format](
5050 - [ Trailer / sign-off rules] ( #trailer--sign-off-rules )
5151 - [ Breaking change rules] ( #breaking-change-rules )
5252 - [ Available Formatters] ( #available-formatters )
53- - [ Extensibility] ( #extensibility )
53+ - [ Programmatic Usage] ( #programmatic-usage )
54+ - [ One-liner with default config] ( #one-liner-with-default-config )
55+ - [ Full control with default config] ( #full-control-with-default-config )
56+ - [ Lint with a config file] ( #lint-with-a-config-file )
57+ - [ Custom rules] ( #custom-rules )
58+ - [ Custom formatters] ( #custom-formatters )
5459 - [ FAQ] ( #faq )
5560 - [ License] ( #license )
5661
@@ -223,7 +228,7 @@ ignores: []
223228
224229### Commit Types
225230
226- Commonly used commit types from [Conventional Commit Types](https://github.com/commitizen/conventional-commit-types)
231+ Commonly used commit types
227232
228233| Type | Description |
229234|:---------|:---------------------------------------------------------------------------------|
@@ -411,13 +416,237 @@ Total 1 errors, 0 warnings, 0 other severities
411416{"input":"fear: do not fear for commit message","issues":[{"description":"type 'fear' is not allowed, you can use one of [build chore ci docs feat fix perf refactor revert style test]","name":"type-enum","severity":"error"}]}
412417` ` `
413418
414- # # Extensibility
419+ # # Programmatic Usage
420+
421+ All public packages are importable. The module path is `github.com/conventionalcommit/commitlint`.
422+
423+ ` ` ` bash
424+ go get github.com/conventionalcommit/commitlint@latest
425+ ` ` `
426+
427+ Key packages :
428+
429+ | Package | Purpose |
430+ |:--------|:--------|
431+ | `config` | Parse config files, build a `Linter`, access defaults |
432+ | `lint` | Core types : ` Linter` , `Rule`, `Formatter`, `Config`, `Result`, `Issue` |
433+ | `registry` | Register and look up custom rules / formatters |
434+ | `rule` | Built-in rule implementations |
435+ | `formatter` | Built-in formatters (`default`, `json`) |
436+
437+ # ## One-liner with default config
438+
439+ The simplest entry point — no config file required :
440+
441+ ` ` ` go
442+ package main
443+
444+ import (
445+ "fmt"
446+ "github.com/conventionalcommit/commitlint/config"
447+ )
448+
449+ func main() {
450+ result, err := config.LintMessage("feat: add login page")
451+ if err != nil {
452+ panic(err)
453+ }
454+
455+ for _, issue := range result.Issues() {
456+ fmt.Printf("%s: %s: %s\n ", issue.Severity(), issue.RuleName(), issue.Description())
457+ }
458+
459+ if len(result.Issues()) == 0 {
460+ fmt.Println("commit message is valid")
461+ }
462+ }
463+ ` ` `
464+
465+ # ## Full control with default config
466+
467+ Build the linter yourself for more control (e.g. to swap the formatter) :
468+
469+ ` ` ` go
470+ package main
471+
472+ import (
473+ "fmt"
474+ "github.com/conventionalcommit/commitlint/config"
475+ "github.com/conventionalcommit/commitlint/formatter"
476+ )
477+
478+ func main() {
479+ conf := config.NewDefault()
480+ // optionally customise conf here
481+
482+ linter, err := config.NewLinter(conf)
483+ if err != nil {
484+ panic(err)
485+ }
486+
487+ result, err := linter.ParseAndLint("feat: add login page")
488+ if err != nil {
489+ panic(err)
490+ }
491+
492+ out, err := (&formatter.JSONFormatter{}).Format(result)
493+ if err != nil {
494+ panic(err)
495+ }
496+ fmt.Println(out)
497+ }
498+ ` ` `
499+
500+ # ## Lint with a config file
501+
502+ Load a `.commitlint.yaml` and lint against it :
503+
504+ ` ` ` go
505+ package main
506+
507+ import (
508+ "fmt"
509+ "github.com/conventionalcommit/commitlint/config"
510+ )
511+
512+ func main() {
513+ conf, err := config.Parse(".commitlint.yaml")
514+ if err != nil {
515+ panic(err)
516+ }
517+
518+ linter, err := config.NewLinter(conf)
519+ if err != nil {
520+ panic(err)
521+ }
522+
523+ result, err := linter.ParseAndLint("feat: add login page")
524+ if err != nil {
525+ panic(err)
526+ }
527+
528+ for _, issue := range result.Issues() {
529+ fmt.Printf("%s: %s\n ", issue.RuleName(), issue.Description())
530+ }
531+ }
532+ ` ` `
533+
534+ # ## Custom rules
535+
536+ Implement the `lint.Rule` interface and register it before building a linter :
537+
538+ ` ` ` go
539+ package main
540+
541+ import (
542+ "fmt"
543+ "github.com/conventionalcommit/commitlint/config"
544+ "github.com/conventionalcommit/commitlint/lint"
545+ "github.com/conventionalcommit/commitlint/registry"
546+ )
547+
548+ // NoWIPRule rejects commit messages whose description starts with "WIP".
549+ type NoWIPRule struct{}
550+
551+ func (r *NoWIPRule) Name() string { return "no-wip" }
552+ func (r *NoWIPRule) Apply(setting lint.RuleSetting) error { return nil }
553+ func (r *NoWIPRule) Validate(commit lint.Commit) (*lint.Issue, error) {
554+ if len(commit.Description()) >= 3 && commit.Description()[:3] == "WIP" {
555+ return lint.NewIssue("description must not start with WIP"), nil
556+ }
557+ return nil, nil
558+ }
559+
560+ func main() {
561+ if err := registry.RegisterRule(&NoWIPRule{}); err != nil {
562+ panic(err)
563+ }
564+
565+ conf := config.NewDefault()
566+ conf.Rules = append(conf.Rules, "no-wip")
567+ conf.Settings["no-wip"] = lint.RuleSetting{}
568+
569+ linter, err := config.NewLinter(conf)
570+ if err != nil {
571+ panic(err)
572+ }
573+
574+ result, err := linter.ParseAndLint("feat: WIP do not merge")
575+ if err != nil {
576+ panic(err)
577+ }
578+
579+ for _, issue := range result.Issues() {
580+ fmt.Printf("%s: %s\n ", issue.RuleName(), issue.Description())
581+ }
582+ }
583+ ` ` `
584+
585+ # ## Custom formatters
586+
587+ Implement `lint.Formatter` and register it :
588+
589+ ` ` ` go
590+ package main
591+
592+ import (
593+ "fmt"
594+ "strings"
595+ "github.com/conventionalcommit/commitlint/config"
596+ "github.com/conventionalcommit/commitlint/lint"
597+ "github.com/conventionalcommit/commitlint/registry"
598+ )
599+
600+ type SimpleFormatter struct{}
601+
602+ func (f *SimpleFormatter) Name() string { return "simple" }
603+ func (f *SimpleFormatter) Format(result *lint.Result) (string, error) {
604+ if len(result.Issues()) == 0 {
605+ return "ok", nil
606+ }
607+ var sb strings.Builder
608+ for _, issue := range result.Issues() {
609+ fmt.Fprintf(&sb, "[%s] %s: %s\n ", issue.Severity(), issue.RuleName(), issue.Description())
610+ }
611+ return sb.String(), nil
612+ }
613+
614+ func main() {
615+ if err := registry.RegisterFormatter(&SimpleFormatter{}); err != nil {
616+ panic(err)
617+ }
618+
619+ conf := config.NewDefault()
620+ conf.Formatter = "simple"
621+
622+ format, err := config.GetFormatter(conf)
623+ if err != nil {
624+ panic(err)
625+ }
626+
627+ linter, err := config.NewLinter(conf)
628+ if err != nil {
629+ panic(err)
630+ }
631+
632+ result, err := linter.ParseAndLint("bad message")
633+ if err != nil {
634+ panic(err)
635+ }
636+
637+ out, err := format.Format(result)
638+ if err != nil {
639+ panic(err)
640+ }
641+ fmt.Print(out)
642+ }
643+ ` ` `
415644
416645# # FAQ
417646
418647- How to have custom config for each repository?
419648
420- Place `.commitlint.yaml` file in repo root directory. linter follows [config precedence](#precedence).
649+ Place `.commitlint.yaml` file in repo root directory. linter follows [config precedence](#config- precedence).
421650
422651 To create a sample config, run `commitlint config create` (or `commitlint config create --all` to include all available settings)
423652
0 commit comments