mirror of
https://github.com/scratchfoundation/golangci-lint.git
synced 2025-08-28 22:28:43 -04:00
docs: use information from the previous release to create pages (#4457)
This commit is contained in:
parent
5cb16561d5
commit
85e1dee09a
17 changed files with 5496 additions and 747 deletions
|
@ -1,572 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/golangci/golangci-lint/internal/renameio"
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/linter"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
)
|
||||
|
||||
const listItemPrefix = "list-item-"
|
||||
|
||||
func main() {
|
||||
replacements, err := buildTemplateContext()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to build template context: %s", err)
|
||||
}
|
||||
|
||||
if err := rewriteDocs(replacements); err != nil {
|
||||
log.Fatalf("Failed to rewrite docs: %s", err)
|
||||
}
|
||||
|
||||
log.Print("Successfully expanded templates")
|
||||
}
|
||||
|
||||
func rewriteDocs(replacements map[string]string) error {
|
||||
madeReplacements := map[string]bool{}
|
||||
err := filepath.Walk(filepath.Join("docs", "src", "docs"),
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return processDoc(path, replacements, madeReplacements)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk dir: %w", err)
|
||||
}
|
||||
|
||||
if len(madeReplacements) != len(replacements) {
|
||||
for key := range replacements {
|
||||
if !madeReplacements[key] {
|
||||
log.Printf("Replacement %q wasn't performed", key)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%d replacements weren't performed", len(replacements)-len(madeReplacements))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processDoc(path string, replacements map[string]string, madeReplacements map[string]bool) error {
|
||||
contentBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %s: %w", path, err)
|
||||
}
|
||||
|
||||
content := string(contentBytes)
|
||||
hasReplacements := false
|
||||
for key, replacement := range replacements {
|
||||
nextContent := content
|
||||
nextContent = strings.ReplaceAll(nextContent, fmt.Sprintf("{.%s}", key), replacement)
|
||||
|
||||
// Yaml formatter in mdx code section makes extra spaces, need to match them too.
|
||||
nextContent = strings.ReplaceAll(nextContent, fmt.Sprintf("{ .%s }", key), replacement)
|
||||
|
||||
if nextContent != content {
|
||||
hasReplacements = true
|
||||
madeReplacements[key] = true
|
||||
content = nextContent
|
||||
}
|
||||
}
|
||||
if !hasReplacements {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Expanded template in %s, saving it", path)
|
||||
if err = renameio.WriteFile(path, []byte(content), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to write changes to file %s: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type latestRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
func getLatestVersion() (string, error) {
|
||||
req, err := http.NewRequest( //nolint:noctx
|
||||
http.MethodGet,
|
||||
"https://api.github.com/repos/golangci/golangci-lint/releases/latest",
|
||||
http.NoBody,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to prepare a http request: %w", err)
|
||||
}
|
||||
req.Header.Add("Accept", "application/vnd.github.v3+json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get http response for the latest tag: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read a body for the latest tag: %w", err)
|
||||
}
|
||||
release := latestRelease{}
|
||||
err = json.Unmarshal(body, &release)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal the body for the latest tag: %w", err)
|
||||
}
|
||||
return release.TagName, nil
|
||||
}
|
||||
|
||||
func buildTemplateContext() (map[string]string, error) {
|
||||
golangciYamlExample, err := os.ReadFile(".golangci.reference.yml")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read .golangci.reference.yml: %w", err)
|
||||
}
|
||||
|
||||
snippets, err := extractExampleSnippets(golangciYamlExample)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read .golangci.reference.yml: %w", err)
|
||||
}
|
||||
|
||||
if err = exec.Command("make", "build").Run(); err != nil {
|
||||
return nil, fmt.Errorf("can't run go install: %w", err)
|
||||
}
|
||||
|
||||
lintersOut, err := exec.Command("./golangci-lint", "help", "linters").Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't run linters cmd: %w", err)
|
||||
}
|
||||
|
||||
lintersOutParts := bytes.Split(lintersOut, []byte("\n\n"))
|
||||
|
||||
helpCmd := exec.Command("./golangci-lint", "run", "-h")
|
||||
helpCmd.Env = append(helpCmd.Env, os.Environ()...)
|
||||
helpCmd.Env = append(helpCmd.Env, "HELP_RUN=1") // make default concurrency stable: don't depend on machine CPU number
|
||||
help, err := helpCmd.Output()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't run help cmd: %w", err)
|
||||
}
|
||||
|
||||
helpLines := bytes.Split(help, []byte("\n"))
|
||||
shortHelp := bytes.Join(helpLines[2:], []byte("\n"))
|
||||
changeLog, err := os.ReadFile("CHANGELOG.md")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestVersion, err := getLatestVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the latest version: %w", err)
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"LintersExample": snippets.LintersSettings,
|
||||
"ConfigurationExample": snippets.ConfigurationFile,
|
||||
"LintersCommandOutputEnabledOnly": string(lintersOutParts[0]),
|
||||
"LintersCommandOutputDisabledOnly": string(lintersOutParts[1]),
|
||||
"EnabledByDefaultLinters": getLintersListMarkdown(true),
|
||||
"DisabledByDefaultLinters": getLintersListMarkdown(false),
|
||||
"DefaultExclusions": getDefaultExclusions(),
|
||||
"ThanksList": getThanksList(),
|
||||
"RunHelpText": string(shortHelp),
|
||||
"ChangeLog": string(changeLog),
|
||||
"LatestVersion": latestVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func getDefaultExclusions() string {
|
||||
bufferString := bytes.NewBufferString("")
|
||||
|
||||
for _, pattern := range config.DefaultExcludePatterns {
|
||||
_, _ = fmt.Fprintln(bufferString)
|
||||
_, _ = fmt.Fprintf(bufferString, "### %s\n", pattern.ID)
|
||||
_, _ = fmt.Fprintln(bufferString)
|
||||
_, _ = fmt.Fprintf(bufferString, "- linter: `%s`\n", pattern.Linter)
|
||||
_, _ = fmt.Fprintf(bufferString, "- pattern: `%s`\n", strings.ReplaceAll(pattern.Pattern, "`", "`"))
|
||||
_, _ = fmt.Fprintf(bufferString, "- why: %s\n", pattern.Why)
|
||||
}
|
||||
|
||||
return bufferString.String()
|
||||
}
|
||||
|
||||
func getLintersListMarkdown(enabled bool) string {
|
||||
dbManager, _ := lintersdb.NewManager(nil, nil, lintersdb.NewLinterBuilder())
|
||||
|
||||
lcs := dbManager.GetAllSupportedLinterConfigs()
|
||||
|
||||
var neededLcs []*linter.Config
|
||||
for _, lc := range lcs {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
if lc.EnabledByDefault == enabled {
|
||||
neededLcs = append(neededLcs, lc)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(neededLcs, func(i, j int) bool {
|
||||
return neededLcs[i].Name() < neededLcs[j].Name()
|
||||
})
|
||||
|
||||
lines := []string{
|
||||
"|Name|Description|Presets|AutoFix|Since|",
|
||||
"|---|---|---|---|---|---|",
|
||||
}
|
||||
|
||||
for _, lc := range neededLcs {
|
||||
line := fmt.Sprintf("|%s|%s|%s|%v|%s|",
|
||||
getName(lc),
|
||||
getDesc(lc),
|
||||
strings.Join(lc.InPresets, ", "),
|
||||
check(lc.CanAutoFix, "Auto fix supported"),
|
||||
lc.Since,
|
||||
)
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func getName(lc *linter.Config) string {
|
||||
name := lc.Name()
|
||||
|
||||
if lc.OriginalURL != "" {
|
||||
name = fmt.Sprintf("[%s](%s)", name, lc.OriginalURL)
|
||||
}
|
||||
|
||||
if hasSettings(lc.Name()) {
|
||||
name = fmt.Sprintf("%s [%s](#%s)", name, spanWithID(listItemPrefix+lc.Name(), "Configuration", "⚙️"), lc.Name())
|
||||
}
|
||||
|
||||
if !lc.IsDeprecated() {
|
||||
return name
|
||||
}
|
||||
|
||||
title := "deprecated"
|
||||
if lc.Deprecation.Replacement != "" {
|
||||
title += fmt.Sprintf(" since %s", lc.Deprecation.Since)
|
||||
}
|
||||
|
||||
return name + " " + span(title, "⚠")
|
||||
}
|
||||
|
||||
func getDesc(lc *linter.Config) string {
|
||||
desc := lc.Linter.Desc()
|
||||
if lc.IsDeprecated() {
|
||||
desc = lc.Deprecation.Message
|
||||
if lc.Deprecation.Replacement != "" {
|
||||
desc += fmt.Sprintf(" Replaced by %s.", lc.Deprecation.Replacement)
|
||||
}
|
||||
}
|
||||
|
||||
return formatDesc(desc)
|
||||
}
|
||||
|
||||
func formatDesc(desc string) string {
|
||||
runes := []rune(desc)
|
||||
|
||||
r, _ := utf8.DecodeRuneInString(desc)
|
||||
runes[0] = unicode.ToUpper(r)
|
||||
|
||||
if runes[len(runes)-1] != '.' {
|
||||
runes = append(runes, '.')
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(string(runes), "\n", "<br/>")
|
||||
}
|
||||
|
||||
func check(b bool, title string) string {
|
||||
if b {
|
||||
return span(title, "✔")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func hasSettings(name string) bool {
|
||||
tp := reflect.TypeOf(config.LintersSettings{})
|
||||
|
||||
for i := 0; i < tp.NumField(); i++ {
|
||||
if strings.EqualFold(name, tp.Field(i).Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func span(title, icon string) string {
|
||||
return fmt.Sprintf(`<span title=%q>%s</span>`, title, icon)
|
||||
}
|
||||
|
||||
func spanWithID(id, title, icon string) string {
|
||||
return fmt.Sprintf(`<span id=%q title=%q>%s</span>`, id, title, icon)
|
||||
}
|
||||
|
||||
type authorDetails struct {
|
||||
Linters []string
|
||||
Profile string
|
||||
Avatar string
|
||||
}
|
||||
|
||||
func getThanksList() string {
|
||||
addedAuthors := map[string]*authorDetails{}
|
||||
dbManager, _ := lintersdb.NewManager(nil, nil, lintersdb.NewLinterBuilder())
|
||||
|
||||
for _, lc := range dbManager.GetAllSupportedLinterConfigs() {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
if lc.OriginalURL == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
linterURL := lc.OriginalURL
|
||||
if lc.Name() == "staticcheck" {
|
||||
linterURL = "https://github.com/dominikh/go-tools"
|
||||
}
|
||||
|
||||
if author := extractAuthor(linterURL, "https://github.com/"); author != "" && author != "golangci" {
|
||||
if _, ok := addedAuthors[author]; ok {
|
||||
addedAuthors[author].Linters = append(addedAuthors[author].Linters, lc.Name())
|
||||
} else {
|
||||
addedAuthors[author] = &authorDetails{
|
||||
Linters: []string{lc.Name()},
|
||||
Profile: fmt.Sprintf("[%[1]s](https://github.com/sponsors/%[1]s)", author),
|
||||
Avatar: fmt.Sprintf(`<img src="https://github.com/%[1]s.png" alt="%[1]s" style="max-width: 100%%;" width="20px;" />`, author),
|
||||
}
|
||||
}
|
||||
} else if author := extractAuthor(linterURL, "https://gitlab.com/"); author != "" {
|
||||
if _, ok := addedAuthors[author]; ok {
|
||||
addedAuthors[author].Linters = append(addedAuthors[author].Linters, lc.Name())
|
||||
} else {
|
||||
addedAuthors[author] = &authorDetails{
|
||||
Linters: []string{lc.Name()},
|
||||
Profile: fmt.Sprintf("[%[1]s](https://gitlab.com/%[1]s)", author),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
authors := maps.Keys(addedAuthors)
|
||||
sort.Slice(authors, func(i, j int) bool {
|
||||
return strings.ToLower(authors[i]) < strings.ToLower(authors[j])
|
||||
})
|
||||
|
||||
lines := []string{
|
||||
"|Author|Linter(s)|",
|
||||
"|---|---|",
|
||||
}
|
||||
|
||||
for _, author := range authors {
|
||||
lines = append(lines, fmt.Sprintf("|%s %s|%s|",
|
||||
addedAuthors[author].Avatar, addedAuthors[author].Profile, strings.Join(addedAuthors[author].Linters, ", ")))
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func extractAuthor(originalURL, prefix string) string {
|
||||
if !strings.HasPrefix(originalURL, prefix) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.SplitN(strings.TrimPrefix(originalURL, prefix), "/", 2)[0]
|
||||
}
|
||||
|
||||
type SettingSnippets struct {
|
||||
ConfigurationFile string
|
||||
LintersSettings string
|
||||
}
|
||||
|
||||
func extractExampleSnippets(example []byte) (*SettingSnippets, error) {
|
||||
var data yaml.Node
|
||||
err := yaml.Unmarshal(example, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root := data.Content[0]
|
||||
|
||||
globalNode := &yaml.Node{
|
||||
Kind: root.Kind,
|
||||
Style: root.Style,
|
||||
Tag: root.Tag,
|
||||
Value: root.Value,
|
||||
Anchor: root.Anchor,
|
||||
Alias: root.Alias,
|
||||
HeadComment: root.HeadComment,
|
||||
LineComment: root.LineComment,
|
||||
FootComment: root.FootComment,
|
||||
Line: root.Line,
|
||||
Column: root.Column,
|
||||
}
|
||||
|
||||
snippets := SettingSnippets{}
|
||||
|
||||
builder := strings.Builder{}
|
||||
|
||||
for j, node := range root.Content {
|
||||
switch node.Value {
|
||||
case "run", "output", "linters", "linters-settings", "issues", "severity":
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
nextNode := root.Content[j+1]
|
||||
|
||||
newNode := &yaml.Node{
|
||||
Kind: nextNode.Kind,
|
||||
Content: []*yaml.Node{
|
||||
{
|
||||
HeadComment: fmt.Sprintf("See the dedicated %q documentation section.", node.Value),
|
||||
Kind: node.Kind,
|
||||
Style: node.Style,
|
||||
Tag: node.Tag,
|
||||
Value: "option",
|
||||
},
|
||||
{
|
||||
Kind: node.Kind,
|
||||
Style: node.Style,
|
||||
Tag: node.Tag,
|
||||
Value: "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
globalNode.Content = append(globalNode.Content, node, newNode)
|
||||
|
||||
if node.Value == "linters-settings" {
|
||||
snippets.LintersSettings, err = getLintersSettingSections(node, nextNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _ = builder.WriteString(
|
||||
fmt.Sprintf(
|
||||
"### `%s` configuration\n\nSee the dedicated [linters-settings](/usage/linters) documentation section.\n\n",
|
||||
node.Value,
|
||||
),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
nodeSection := &yaml.Node{
|
||||
Kind: root.Kind,
|
||||
Style: root.Style,
|
||||
Tag: root.Tag,
|
||||
Value: root.Value,
|
||||
Content: []*yaml.Node{node, nextNode},
|
||||
}
|
||||
|
||||
snippet, errSnip := marshallSnippet(nodeSection)
|
||||
if errSnip != nil {
|
||||
return nil, errSnip
|
||||
}
|
||||
|
||||
_, _ = builder.WriteString(fmt.Sprintf("### `%s` configuration\n\n%s", node.Value, snippet))
|
||||
}
|
||||
|
||||
overview, err := marshallSnippet(globalNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snippets.ConfigurationFile = overview + builder.String()
|
||||
|
||||
return &snippets, nil
|
||||
}
|
||||
|
||||
func getLintersSettingSections(node, nextNode *yaml.Node) (string, error) {
|
||||
dbManager, _ := lintersdb.NewManager(nil, nil, lintersdb.NewLinterBuilder())
|
||||
lcs := dbManager.GetAllSupportedLinterConfigs()
|
||||
|
||||
var lintersDesc = make(map[string]string)
|
||||
for _, lc := range lcs {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
// it's important to use lc.Name() nor name because name can be alias
|
||||
lintersDesc[lc.Name()] = getDesc(lc)
|
||||
}
|
||||
|
||||
builder := &strings.Builder{}
|
||||
|
||||
for i := 0; i < len(nextNode.Content); i += 2 {
|
||||
r := &yaml.Node{
|
||||
Kind: nextNode.Kind,
|
||||
Style: nextNode.Style,
|
||||
Tag: nextNode.Tag,
|
||||
Value: node.Value,
|
||||
Content: []*yaml.Node{
|
||||
{
|
||||
Kind: node.Kind,
|
||||
Value: node.Value,
|
||||
},
|
||||
{
|
||||
Kind: nextNode.Kind,
|
||||
Content: []*yaml.Node{nextNode.Content[i], nextNode.Content[i+1]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(builder, "### %s\n\n", nextNode.Content[i].Value)
|
||||
_, _ = fmt.Fprintf(builder, "%s\n\n", lintersDesc[nextNode.Content[i].Value])
|
||||
_, _ = fmt.Fprintln(builder, "```yaml")
|
||||
|
||||
encoder := yaml.NewEncoder(builder)
|
||||
encoder.SetIndent(2)
|
||||
|
||||
err := encoder.Encode(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(builder, "```")
|
||||
_, _ = fmt.Fprintln(builder)
|
||||
_, _ = fmt.Fprintf(builder, "[%s](#%s)\n\n", span("Back to the top", "🔼"), listItemPrefix+nextNode.Content[i].Value)
|
||||
_, _ = fmt.Fprintln(builder)
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func marshallSnippet(node *yaml.Node) (string, error) {
|
||||
builder := &strings.Builder{}
|
||||
|
||||
if node.Value != "" {
|
||||
_, _ = fmt.Fprintf(builder, "### %s\n\n", node.Value)
|
||||
}
|
||||
_, _ = fmt.Fprintln(builder, "```yaml")
|
||||
|
||||
encoder := yaml.NewEncoder(builder)
|
||||
encoder.SetIndent(2)
|
||||
|
||||
err := encoder.Encode(node)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(builder, "```")
|
||||
_, _ = fmt.Fprintln(builder)
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
133
scripts/website/dump_info/main.go
Normal file
133
scripts/website/dump_info/main.go
Normal file
|
@ -0,0 +1,133 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
"github.com/golangci/golangci-lint/scripts/website/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := saveLinters()
|
||||
if err != nil {
|
||||
log.Fatalf("Save linters: %v", err)
|
||||
}
|
||||
|
||||
err = saveDefaultExclusions()
|
||||
if err != nil {
|
||||
log.Fatalf("Save default exclusions: %v", err)
|
||||
}
|
||||
|
||||
err = saveCLIHelp(filepath.Join("assets", "cli-help.json"))
|
||||
if err != nil {
|
||||
log.Fatalf("Save CLI help: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func saveLinters() error {
|
||||
linters, _ := lintersdb.NewLinterBuilder().Build(config.NewDefault())
|
||||
|
||||
var wraps []types.LinterWrapper
|
||||
for _, l := range linters {
|
||||
wrapper := types.LinterWrapper{
|
||||
Name: l.Linter.Name(),
|
||||
Desc: l.Linter.Desc(),
|
||||
EnabledByDefault: l.EnabledByDefault,
|
||||
LoadMode: l.LoadMode,
|
||||
InPresets: l.InPresets,
|
||||
AlternativeNames: l.AlternativeNames,
|
||||
OriginalURL: l.OriginalURL,
|
||||
Internal: l.Internal,
|
||||
CanAutoFix: l.CanAutoFix,
|
||||
IsSlow: l.IsSlow,
|
||||
DoesChangeTypes: l.DoesChangeTypes,
|
||||
Since: l.Since,
|
||||
}
|
||||
|
||||
if l.Deprecation != nil {
|
||||
wrapper.Deprecation = &types.Deprecation{
|
||||
Since: l.Deprecation.Since,
|
||||
Message: l.Deprecation.Message,
|
||||
Replacement: l.Deprecation.Replacement,
|
||||
}
|
||||
}
|
||||
|
||||
wraps = append(wraps, wrapper)
|
||||
}
|
||||
|
||||
return saveToJSONFile(filepath.Join("assets", "linters-info.json"), wraps)
|
||||
}
|
||||
|
||||
func saveDefaultExclusions() error {
|
||||
var excludePatterns []types.ExcludePattern
|
||||
|
||||
for _, pattern := range config.DefaultExcludePatterns {
|
||||
excludePatterns = append(excludePatterns, types.ExcludePattern{
|
||||
ID: pattern.ID,
|
||||
Pattern: pattern.Pattern,
|
||||
Linter: pattern.Linter,
|
||||
Why: pattern.Why,
|
||||
})
|
||||
}
|
||||
|
||||
return saveToJSONFile(filepath.Join("assets", "default-exclusions.json"), excludePatterns)
|
||||
}
|
||||
|
||||
func saveCLIHelp(dst string) error {
|
||||
err := exec.Command("make", "build").Run()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't run make build: %w", err)
|
||||
}
|
||||
|
||||
lintersOut, err := exec.Command("./golangci-lint", "help", "linters").Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't run linters cmd: %w", err)
|
||||
}
|
||||
|
||||
lintersOutParts := bytes.Split(lintersOut, []byte("\n\n"))
|
||||
|
||||
helpCmd := exec.Command("./golangci-lint", "run", "-h")
|
||||
helpCmd.Env = append(helpCmd.Env, os.Environ()...)
|
||||
helpCmd.Env = append(helpCmd.Env, "HELP_RUN=1") // make default concurrency stable: don't depend on machine CPU number
|
||||
help, err := helpCmd.Output()
|
||||
if err != nil {
|
||||
return fmt.Errorf("can't run help cmd: %w", err)
|
||||
}
|
||||
|
||||
helpLines := bytes.Split(help, []byte("\n"))
|
||||
shortHelp := bytes.Join(helpLines[2:], []byte("\n"))
|
||||
|
||||
data := types.CLIHelp{
|
||||
Enable: string(lintersOutParts[0]),
|
||||
Disable: string(lintersOutParts[1]),
|
||||
Help: string(shortHelp),
|
||||
}
|
||||
|
||||
return saveToJSONFile(dst, data)
|
||||
}
|
||||
|
||||
func saveToJSONFile(dst string, data any) error {
|
||||
file, err := os.Create(dst)
|
||||
if err != nil {
|
||||
return fmt.Errorf("open file (%s): %w", dst, err)
|
||||
}
|
||||
|
||||
defer func() { _ = file.Close() }()
|
||||
|
||||
encoder := json.NewEncoder(file)
|
||||
encoder.SetIndent("", " ")
|
||||
|
||||
err = encoder.Encode(data)
|
||||
if err != nil {
|
||||
return fmt.Errorf("encode JSON (%s): %w", dst, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
48
scripts/website/expand_templates/exclusions.go
Normal file
48
scripts/website/expand_templates/exclusions.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/golangci/golangci-lint/scripts/website/types"
|
||||
)
|
||||
|
||||
const exclusionTmpl = `{{ $tick := "` + "`" + `" }}
|
||||
### {{ .ID }}
|
||||
|
||||
- linter: {{ $tick }}{{ .Linter }}{{ $tick }}
|
||||
- pattern: {{ $tick }}{{ .Pattern }}{{ $tick }}
|
||||
- why: {{ .Why }}
|
||||
`
|
||||
|
||||
func getDefaultExclusions() (string, error) {
|
||||
defaultExcludePatterns, err := readJSONFile[[]types.ExcludePattern](filepath.Join("assets", "default-exclusions.json"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
bufferString := bytes.NewBufferString("")
|
||||
|
||||
tmpl, err := template.New("exclusions").Parse(exclusionTmpl)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, pattern := range defaultExcludePatterns {
|
||||
data := map[string]any{
|
||||
"ID": pattern.ID,
|
||||
"Linter": pattern.Linter,
|
||||
"Pattern": strings.ReplaceAll(pattern.Pattern, "`", "`"),
|
||||
"Why": pattern.Why,
|
||||
}
|
||||
|
||||
err := tmpl.Execute(bufferString, data)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
return bufferString.String(), nil
|
||||
}
|
315
scripts/website/expand_templates/linters.go
Normal file
315
scripts/website/expand_templates/linters.go
Normal file
|
@ -0,0 +1,315 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
"sort"
|
||||
"strings"
|
||||
"unicode"
|
||||
"unicode/utf8"
|
||||
|
||||
"gopkg.in/yaml.v3"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/scripts/website/types"
|
||||
)
|
||||
|
||||
const listItemPrefix = "list-item-"
|
||||
|
||||
func getLintersListMarkdown(enabled bool) string {
|
||||
linters, err := readJSONFile[[]*types.LinterWrapper](filepath.Join("assets", "linters-info.json"))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
var neededLcs []*types.LinterWrapper
|
||||
for _, lc := range linters {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
if lc.EnabledByDefault == enabled {
|
||||
neededLcs = append(neededLcs, lc)
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(neededLcs, func(i, j int) bool {
|
||||
return neededLcs[i].Name < neededLcs[j].Name
|
||||
})
|
||||
|
||||
lines := []string{
|
||||
"|Name|Description|Presets|AutoFix|Since|",
|
||||
"|---|---|---|---|---|---|",
|
||||
}
|
||||
|
||||
for _, lc := range neededLcs {
|
||||
line := fmt.Sprintf("|%s|%s|%s|%v|%s|",
|
||||
getName(lc),
|
||||
getDesc(lc),
|
||||
strings.Join(lc.InPresets, ", "),
|
||||
check(lc.CanAutoFix, "Auto fix supported"),
|
||||
lc.Since,
|
||||
)
|
||||
lines = append(lines, line)
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func getName(lc *types.LinterWrapper) string {
|
||||
name := lc.Name
|
||||
|
||||
if lc.OriginalURL != "" {
|
||||
name = fmt.Sprintf("[%s](%s)", name, lc.OriginalURL)
|
||||
}
|
||||
|
||||
if hasSettings(lc.Name) {
|
||||
name = fmt.Sprintf("%s [%s](#%s)", name, spanWithID(listItemPrefix+lc.Name, "Configuration", "⚙️"), lc.Name)
|
||||
}
|
||||
|
||||
if lc.Deprecation == nil {
|
||||
return name
|
||||
}
|
||||
|
||||
title := "deprecated"
|
||||
if lc.Deprecation.Replacement != "" {
|
||||
title += fmt.Sprintf(" since %s", lc.Deprecation.Since)
|
||||
}
|
||||
|
||||
return name + " " + span(title, "⚠")
|
||||
}
|
||||
|
||||
func check(b bool, title string) string {
|
||||
if b {
|
||||
return span(title, "✔")
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func getDesc(lc *types.LinterWrapper) string {
|
||||
desc := lc.Desc
|
||||
if lc.Deprecation != nil {
|
||||
desc = lc.Deprecation.Message
|
||||
if lc.Deprecation.Replacement != "" {
|
||||
desc += fmt.Sprintf(" Replaced by %s.", lc.Deprecation.Replacement)
|
||||
}
|
||||
}
|
||||
|
||||
return formatDesc(desc)
|
||||
}
|
||||
|
||||
func formatDesc(desc string) string {
|
||||
runes := []rune(desc)
|
||||
|
||||
r, _ := utf8.DecodeRuneInString(desc)
|
||||
runes[0] = unicode.ToUpper(r)
|
||||
|
||||
if runes[len(runes)-1] != '.' {
|
||||
runes = append(runes, '.')
|
||||
}
|
||||
|
||||
return strings.ReplaceAll(string(runes), "\n", "<br/>")
|
||||
}
|
||||
|
||||
func hasSettings(name string) bool {
|
||||
tp := reflect.TypeOf(config.LintersSettings{})
|
||||
|
||||
for i := 0; i < tp.NumField(); i++ {
|
||||
if strings.EqualFold(name, tp.Field(i).Name) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func span(title, icon string) string {
|
||||
return fmt.Sprintf(`<span title=%q>%s</span>`, title, icon)
|
||||
}
|
||||
|
||||
func spanWithID(id, title, icon string) string {
|
||||
return fmt.Sprintf(`<span id=%q title=%q>%s</span>`, id, title, icon)
|
||||
}
|
||||
|
||||
type SettingSnippets struct {
|
||||
ConfigurationFile string
|
||||
LintersSettings string
|
||||
}
|
||||
|
||||
func extractExampleSnippets(example []byte) (*SettingSnippets, error) {
|
||||
var data yaml.Node
|
||||
err := yaml.Unmarshal(example, &data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
root := data.Content[0]
|
||||
|
||||
globalNode := &yaml.Node{
|
||||
Kind: root.Kind,
|
||||
Style: root.Style,
|
||||
Tag: root.Tag,
|
||||
Value: root.Value,
|
||||
Anchor: root.Anchor,
|
||||
Alias: root.Alias,
|
||||
HeadComment: root.HeadComment,
|
||||
LineComment: root.LineComment,
|
||||
FootComment: root.FootComment,
|
||||
Line: root.Line,
|
||||
Column: root.Column,
|
||||
}
|
||||
|
||||
snippets := SettingSnippets{}
|
||||
|
||||
builder := strings.Builder{}
|
||||
|
||||
for j, node := range root.Content {
|
||||
switch node.Value {
|
||||
case "run", "output", "linters", "linters-settings", "issues", "severity":
|
||||
default:
|
||||
continue
|
||||
}
|
||||
|
||||
nextNode := root.Content[j+1]
|
||||
|
||||
newNode := &yaml.Node{
|
||||
Kind: nextNode.Kind,
|
||||
Content: []*yaml.Node{
|
||||
{
|
||||
HeadComment: fmt.Sprintf("See the dedicated %q documentation section.", node.Value),
|
||||
Kind: node.Kind,
|
||||
Style: node.Style,
|
||||
Tag: node.Tag,
|
||||
Value: "option",
|
||||
},
|
||||
{
|
||||
Kind: node.Kind,
|
||||
Style: node.Style,
|
||||
Tag: node.Tag,
|
||||
Value: "value",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
globalNode.Content = append(globalNode.Content, node, newNode)
|
||||
|
||||
if node.Value == "linters-settings" {
|
||||
snippets.LintersSettings, err = getLintersSettingSections(node, nextNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, _ = builder.WriteString(
|
||||
fmt.Sprintf(
|
||||
"### `%s` configuration\n\nSee the dedicated [linters-settings](/usage/linters) documentation section.\n\n",
|
||||
node.Value,
|
||||
),
|
||||
)
|
||||
continue
|
||||
}
|
||||
|
||||
nodeSection := &yaml.Node{
|
||||
Kind: root.Kind,
|
||||
Style: root.Style,
|
||||
Tag: root.Tag,
|
||||
Value: root.Value,
|
||||
Content: []*yaml.Node{node, nextNode},
|
||||
}
|
||||
|
||||
snippet, errSnip := marshallSnippet(nodeSection)
|
||||
if errSnip != nil {
|
||||
return nil, errSnip
|
||||
}
|
||||
|
||||
_, _ = builder.WriteString(fmt.Sprintf("### `%s` configuration\n\n%s", node.Value, snippet))
|
||||
}
|
||||
|
||||
overview, err := marshallSnippet(globalNode)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
snippets.ConfigurationFile = overview + builder.String()
|
||||
|
||||
return &snippets, nil
|
||||
}
|
||||
|
||||
func getLintersSettingSections(node, nextNode *yaml.Node) (string, error) {
|
||||
linters, err := readJSONFile[[]*types.LinterWrapper](filepath.Join("assets", "linters-info.json"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
var lintersDesc = make(map[string]string)
|
||||
for _, lc := range linters {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
// it's important to use lc.Name() nor name because name can be alias
|
||||
lintersDesc[lc.Name] = getDesc(lc)
|
||||
}
|
||||
|
||||
builder := &strings.Builder{}
|
||||
|
||||
for i := 0; i < len(nextNode.Content); i += 2 {
|
||||
r := &yaml.Node{
|
||||
Kind: nextNode.Kind,
|
||||
Style: nextNode.Style,
|
||||
Tag: nextNode.Tag,
|
||||
Value: node.Value,
|
||||
Content: []*yaml.Node{
|
||||
{
|
||||
Kind: node.Kind,
|
||||
Value: node.Value,
|
||||
},
|
||||
{
|
||||
Kind: nextNode.Kind,
|
||||
Content: []*yaml.Node{nextNode.Content[i], nextNode.Content[i+1]},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintf(builder, "### %s\n\n", nextNode.Content[i].Value)
|
||||
_, _ = fmt.Fprintf(builder, "%s\n\n", lintersDesc[nextNode.Content[i].Value])
|
||||
_, _ = fmt.Fprintln(builder, "```yaml")
|
||||
|
||||
encoder := yaml.NewEncoder(builder)
|
||||
encoder.SetIndent(2)
|
||||
|
||||
err := encoder.Encode(r)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(builder, "```")
|
||||
_, _ = fmt.Fprintln(builder)
|
||||
_, _ = fmt.Fprintf(builder, "[%s](#%s)\n\n", span("Back to the top", "🔼"), listItemPrefix+nextNode.Content[i].Value)
|
||||
_, _ = fmt.Fprintln(builder)
|
||||
}
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
||||
|
||||
func marshallSnippet(node *yaml.Node) (string, error) {
|
||||
builder := &strings.Builder{}
|
||||
|
||||
if node.Value != "" {
|
||||
_, _ = fmt.Fprintf(builder, "### %s\n\n", node.Value)
|
||||
}
|
||||
_, _ = fmt.Fprintln(builder, "```yaml")
|
||||
|
||||
encoder := yaml.NewEncoder(builder)
|
||||
encoder.SetIndent(2)
|
||||
|
||||
err := encoder.Encode(node)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
_, _ = fmt.Fprintln(builder, "```")
|
||||
_, _ = fmt.Fprintln(builder)
|
||||
|
||||
return builder.String(), nil
|
||||
}
|
182
scripts/website/expand_templates/main.go
Normal file
182
scripts/website/expand_templates/main.go
Normal file
|
@ -0,0 +1,182 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/golangci/golangci-lint/internal/renameio"
|
||||
"github.com/golangci/golangci-lint/scripts/website/types"
|
||||
)
|
||||
|
||||
func main() {
|
||||
replacements, err := buildTemplateContext()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to build template context: %s", err)
|
||||
}
|
||||
|
||||
if err := rewriteDocs(replacements); err != nil {
|
||||
log.Fatalf("Failed to rewrite docs: %s", err)
|
||||
}
|
||||
|
||||
log.Print("Successfully expanded templates")
|
||||
}
|
||||
|
||||
func rewriteDocs(replacements map[string]string) error {
|
||||
madeReplacements := map[string]bool{}
|
||||
err := filepath.Walk(filepath.Join("docs", "src", "docs"),
|
||||
func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
return processDoc(path, replacements, madeReplacements)
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to walk dir: %w", err)
|
||||
}
|
||||
|
||||
if len(madeReplacements) != len(replacements) {
|
||||
for key := range replacements {
|
||||
if !madeReplacements[key] {
|
||||
log.Printf("Replacement %q wasn't performed", key)
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("%d replacements weren't performed", len(replacements)-len(madeReplacements))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func processDoc(path string, replacements map[string]string, madeReplacements map[string]bool) error {
|
||||
contentBytes, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to read %s: %w", path, err)
|
||||
}
|
||||
|
||||
content := string(contentBytes)
|
||||
hasReplacements := false
|
||||
for key, replacement := range replacements {
|
||||
nextContent := content
|
||||
nextContent = strings.ReplaceAll(nextContent, fmt.Sprintf("{.%s}", key), replacement)
|
||||
|
||||
// Yaml formatter in mdx code section makes extra spaces, need to match them too.
|
||||
nextContent = strings.ReplaceAll(nextContent, fmt.Sprintf("{ .%s }", key), replacement)
|
||||
|
||||
if nextContent != content {
|
||||
hasReplacements = true
|
||||
madeReplacements[key] = true
|
||||
content = nextContent
|
||||
}
|
||||
}
|
||||
if !hasReplacements {
|
||||
return nil
|
||||
}
|
||||
|
||||
log.Printf("Expanded template in %s, saving it", path)
|
||||
if err = renameio.WriteFile(path, []byte(content), os.ModePerm); err != nil {
|
||||
return fmt.Errorf("failed to write changes to file %s: %w", path, err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type latestRelease struct {
|
||||
TagName string `json:"tag_name"`
|
||||
}
|
||||
|
||||
func getLatestVersion() (string, error) {
|
||||
req, err := http.NewRequest( //nolint:noctx
|
||||
http.MethodGet,
|
||||
"https://api.github.com/repos/golangci/golangci-lint/releases/latest",
|
||||
http.NoBody,
|
||||
)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to prepare a http request: %w", err)
|
||||
}
|
||||
req.Header.Add("Accept", "application/vnd.github.v3+json")
|
||||
resp, err := http.DefaultClient.Do(req)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to get http response for the latest tag: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to read a body for the latest tag: %w", err)
|
||||
}
|
||||
release := latestRelease{}
|
||||
err = json.Unmarshal(body, &release)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to unmarshal the body for the latest tag: %w", err)
|
||||
}
|
||||
return release.TagName, nil
|
||||
}
|
||||
|
||||
func buildTemplateContext() (map[string]string, error) {
|
||||
golangciYamlExample, err := os.ReadFile(".golangci.reference.yml")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read .golangci.reference.yml: %w", err)
|
||||
}
|
||||
|
||||
snippets, err := extractExampleSnippets(golangciYamlExample)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("can't read .golangci.reference.yml: %w", err)
|
||||
}
|
||||
|
||||
helps, err := readJSONFile[types.CLIHelp](filepath.Join("assets", "cli-help.json"))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
changeLog, err := os.ReadFile("CHANGELOG.md")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
latestVersion, err := getLatestVersion()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get the latest version: %w", err)
|
||||
}
|
||||
|
||||
exclusions, err := getDefaultExclusions()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("default exclusions: %w", err)
|
||||
}
|
||||
|
||||
return map[string]string{
|
||||
"LintersExample": snippets.LintersSettings,
|
||||
"ConfigurationExample": snippets.ConfigurationFile,
|
||||
"LintersCommandOutputEnabledOnly": helps.Enable,
|
||||
"LintersCommandOutputDisabledOnly": helps.Disable,
|
||||
"EnabledByDefaultLinters": getLintersListMarkdown(true),
|
||||
"DisabledByDefaultLinters": getLintersListMarkdown(false),
|
||||
"DefaultExclusions": exclusions,
|
||||
"ThanksList": getThanksList(),
|
||||
"RunHelpText": helps.Help,
|
||||
"ChangeLog": string(changeLog),
|
||||
"LatestVersion": latestVersion,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func readJSONFile[T any](src string) (T, error) {
|
||||
file, err := os.Open(src)
|
||||
if err != nil {
|
||||
var zero T
|
||||
return zero, err
|
||||
}
|
||||
|
||||
var result T
|
||||
err = json.NewDecoder(file).Decode(&result)
|
||||
if err != nil {
|
||||
var zero T
|
||||
return zero, err
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
87
scripts/website/expand_templates/thanks.go
Normal file
87
scripts/website/expand_templates/thanks.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"golang.org/x/exp/maps"
|
||||
|
||||
"github.com/golangci/golangci-lint/pkg/config"
|
||||
"github.com/golangci/golangci-lint/pkg/lint/lintersdb"
|
||||
)
|
||||
|
||||
type authorDetails struct {
|
||||
Linters []string
|
||||
Profile string
|
||||
Avatar string
|
||||
}
|
||||
|
||||
func getThanksList() string {
|
||||
addedAuthors := map[string]*authorDetails{}
|
||||
|
||||
linters, _ := lintersdb.NewLinterBuilder().Build(config.NewDefault())
|
||||
|
||||
for _, lc := range linters {
|
||||
if lc.Internal {
|
||||
continue
|
||||
}
|
||||
|
||||
if lc.OriginalURL == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
linterURL := lc.OriginalURL
|
||||
if lc.Name() == "staticcheck" {
|
||||
linterURL = "https://github.com/dominikh/go-tools"
|
||||
}
|
||||
|
||||
if author := extractAuthor(linterURL, "https://github.com/"); author != "" && author != "golangci" {
|
||||
if _, ok := addedAuthors[author]; ok {
|
||||
addedAuthors[author].Linters = append(addedAuthors[author].Linters, lc.Name())
|
||||
} else {
|
||||
addedAuthors[author] = &authorDetails{
|
||||
Linters: []string{lc.Name()},
|
||||
Profile: fmt.Sprintf("[%[1]s](https://github.com/sponsors/%[1]s)", author),
|
||||
Avatar: fmt.Sprintf(`<img src="https://github.com/%[1]s.png" alt="%[1]s" style="max-width: 100%%;" width="20px;" />`, author),
|
||||
}
|
||||
}
|
||||
} else if author := extractAuthor(linterURL, "https://gitlab.com/"); author != "" {
|
||||
if _, ok := addedAuthors[author]; ok {
|
||||
addedAuthors[author].Linters = append(addedAuthors[author].Linters, lc.Name())
|
||||
} else {
|
||||
addedAuthors[author] = &authorDetails{
|
||||
Linters: []string{lc.Name()},
|
||||
Profile: fmt.Sprintf("[%[1]s](https://gitlab.com/%[1]s)", author),
|
||||
}
|
||||
}
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
authors := maps.Keys(addedAuthors)
|
||||
sort.Slice(authors, func(i, j int) bool {
|
||||
return strings.ToLower(authors[i]) < strings.ToLower(authors[j])
|
||||
})
|
||||
|
||||
lines := []string{
|
||||
"|Author|Linter(s)|",
|
||||
"|---|---|",
|
||||
}
|
||||
|
||||
for _, author := range authors {
|
||||
lines = append(lines, fmt.Sprintf("|%s %s|%s|",
|
||||
addedAuthors[author].Avatar, addedAuthors[author].Profile, strings.Join(addedAuthors[author].Linters, ", ")))
|
||||
}
|
||||
|
||||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func extractAuthor(originalURL, prefix string) string {
|
||||
if !strings.HasPrefix(originalURL, prefix) {
|
||||
return ""
|
||||
}
|
||||
|
||||
return strings.SplitN(strings.TrimPrefix(originalURL, prefix), "/", 2)[0]
|
||||
}
|
48
scripts/website/types/types.go
Normal file
48
scripts/website/types/types.go
Normal file
|
@ -0,0 +1,48 @@
|
|||
package types
|
||||
|
||||
import (
|
||||
"golang.org/x/tools/go/packages"
|
||||
)
|
||||
|
||||
type CLIHelp struct {
|
||||
Enable string `json:"enable"`
|
||||
Disable string `json:"disable"`
|
||||
Help string `json:"help"`
|
||||
}
|
||||
|
||||
type ExcludePattern struct {
|
||||
ID string `json:"id,omitempty"`
|
||||
Pattern string `json:"pattern,omitempty"`
|
||||
Linter string `json:"linter,omitempty"`
|
||||
Why string `json:"why,omitempty"`
|
||||
}
|
||||
|
||||
type Deprecation struct {
|
||||
Since string `json:"since,omitempty"`
|
||||
Message string `json:"message,omitempty"`
|
||||
Replacement string `json:"replacement,omitempty"`
|
||||
}
|
||||
|
||||
// LinterWrapper same fields but with struct tags.
|
||||
// The field Name and Desc are added to have the information about the linter.
|
||||
// The field Linter is removed (not serializable).
|
||||
type LinterWrapper struct {
|
||||
Name string `json:"name"` // From linter.
|
||||
Desc string `json:"desc"` // From linter.
|
||||
|
||||
EnabledByDefault bool `json:"enabledByDefault,omitempty"`
|
||||
|
||||
LoadMode packages.LoadMode `json:"loadMode,omitempty"`
|
||||
|
||||
InPresets []string `json:"inPresets,omitempty"`
|
||||
AlternativeNames []string `json:"alternativeNames,omitempty"`
|
||||
|
||||
OriginalURL string `json:"originalURL,omitempty"`
|
||||
Internal bool `json:"internal"`
|
||||
CanAutoFix bool `json:"canAutoFix,omitempty"`
|
||||
IsSlow bool `json:"isSlow"`
|
||||
DoesChangeTypes bool `json:"doesChangeTypes,omitempty"`
|
||||
|
||||
Since string `json:"since,omitempty"`
|
||||
Deprecation *Deprecation `json:"deprecation,omitempty"`
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue