all repos

clerk @ a69e534

missing tooling for ledger/hledger

clerk/lint.go (view raw)

Oleksandr Smirnov Oleksandr Smirnov
olexsmir@gmail.com
linter: report all output in one json array (for json output), 1 month ago
1
package main
2
3
import (
4
	"flag"
5
	"fmt"
6
	"io"
7
	"os"
8
	"path/filepath"
9
	"strings"
10
11
	"olexsmir.xyz/clerk/internal/linter"
12
	"olexsmir.xyz/clerk/journal"
13
)
14
15
func runLint(args []string) {
16
	fs := flag.NewFlagSet("lint", flag.ExitOnError)
17
	format := fs.String("format", "text", "output format: text, json")
18
	pathStyle := fs.String("path-style", "relative", "file path style: basename, relative, absolute")
19
	fs.Usage = func() {
20
		fmt.Fprintf(os.Stderr, "Usage: clerk lint [flags] [path...]\n")
21
		fs.PrintDefaults()
22
	}
23
	fs.Parse(args)
24
25
	style := parsePathStyle(*pathStyle)
26
	l := linter.NewLinter(linter.Rules)
27
	reporter := linter.NewReporter(os.Stdout, style)
28
	paths := fs.Args()
29
30
	if len(paths) == 0 {
31
		finds := lintStdin(l)
32
		reporter.Collect(finds)
33
		reporter.Flush(*format)
34
		if hasFatal(finds) {
35
			os.Exit(1)
36
		}
37
		os.Exit(0)
38
	}
39
40
	exitCode := 0
41
	for _, path := range paths {
42
		info, err := os.Stat(path)
43
		if err != nil {
44
			fmt.Fprintf(os.Stderr, "error: %s: %v\n", path, err)
45
			exitCode = 1
46
			continue
47
		}
48
		if info.IsDir() {
49
			filepath.Walk(path, func(fpath string, finfo os.FileInfo, err error) error {
50
				if err != nil || finfo.IsDir() || !journal.IsJournalFile(fpath) {
51
					return nil
52
				}
53
				finds, fatal := lintFile(l, fpath)
54
				reporter.Collect(finds)
55
				if fatal {
56
					exitCode = 1
57
				}
58
				return nil
59
			})
60
			continue
61
		}
62
		finds, fatal := lintFile(l, path)
63
		reporter.Collect(finds)
64
		if fatal {
65
			exitCode = 1
66
		}
67
	}
68
	reporter.Flush(*format)
69
	os.Exit(exitCode)
70
}
71
72
func lintFile(l *linter.Linter, path string) ([]linter.Find, bool) {
73
	src, err := os.ReadFile(path)
74
	if err != nil {
75
		fmt.Fprintf(os.Stderr, "error: %s: %v\n", path, err)
76
		return nil, true
77
	}
78
	pf, err := journal.NewLoader().LoadBytes(path, src)
79
	if err != nil {
80
		fmt.Fprintf(os.Stderr, "error: %s: %v\n", path, err)
81
		return nil, true
82
	}
83
	finds := l.Run(pf.Ast)
84
	return finds, hasFatal(finds)
85
}
86
87
func lintStdin(l *linter.Linter) []linter.Find {
88
	src, err := io.ReadAll(os.Stdin)
89
	if err != nil {
90
		fmt.Fprintf(os.Stderr, "error reading stdin: %v\n", err)
91
		os.Exit(1)
92
	}
93
	pf, err := journal.NewLoader().LoadBytes("stdin", src)
94
	if err != nil {
95
		fmt.Fprintf(os.Stderr, "error: %v\n", err)
96
		os.Exit(1)
97
	}
98
	return l.Run(pf.Ast)
99
}
100
101
func hasFatal(finds []linter.Find) bool {
102
	for _, f := range finds {
103
		if f.Severity <= 2 {
104
			return true
105
		}
106
	}
107
	return false
108
}
109
110
func parsePathStyle(s string) linter.PathStyle {
111
	switch strings.ToLower(s) {
112
	default:
113
		return linter.PathRelative
114
	case "basename":
115
		return linter.PathBasename
116
	case "absolute":
117
		return linter.PathAbsolute
118
	}
119
}