all repos

clerk @ 2298caf

missing tooling for ledger/hledger
2 files changed, 35 insertions(+), 24 deletions(-)
linter: performance; resolve cwd once per flush, buffer output
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-09-26 18:20:01 +0300
Authored at: 2026-09-26 17:36:58 +0300
Change ID: pxytruqywoxupqrnqqnursynmzxmxpuy
Parent: e0319e5
M internal/linter/report.go
···
        1
        1
         package linter

      
        2
        2
         

      
        3
        3
         import (

      
        
        4
        +	"bufio"

      
        4
        5
         	"encoding/json"

      
        5
        6
         	"errors"

      
        6
        
        -	"fmt"

      
        7
        7
         	"io"

      
        8
        8
         	"os"

      
        9
        9
         	"path/filepath"

      
        10
        
        -	"sort"

      
        
        10
        +	"slices"

      
        
        11
        +	"strconv"

      
        
        12
        +	"strings"

      
        11
        13
         )

      
        12
        14
         

      
        13
        15
         // PathStyle controls how file paths are shown in output.

      ···
        21
        23
         

      
        22
        24
         // Reporter collects lint findings across files and flushes them in the desired format.

      
        23
        25
         type Reporter struct {

      
        24
        
        -	w     io.Writer

      
        
        26
        +	w     *bufio.Writer

      
        25
        27
         	finds []Find

      
        26
        28
         	style PathStyle

      
        27
        29
         	cfg   Config

      
        28
        30
         }

      
        29
        31
         

      
        30
        32
         func NewReporter(w io.Writer, style PathStyle, cfg Config) *Reporter {

      
        31
        
        -	return &Reporter{w: w, style: style, cfg: cfg}

      
        
        33
        +	return &Reporter{w: bufio.NewWriter(w), style: style, cfg: cfg}

      
        32
        34
         }

      
        33
        35
         

      
        34
        36
         func (r *Reporter) Collect(finds []Find) {

      ···
        48
        50
         }

      
        49
        51
         

      
        50
        52
         func (r *Reporter) Flush(format string) error {

      
        
        53
        +	defer r.w.Flush()

      
        51
        54
         	switch format {

      
        52
        55
         	case "json":

      
        53
        56
         		return fprintJSON(r.w, r.style, r.finds)

      ···
        62
        65
         // fprint writes finds in text format: file:line:col code: message.

      
        63
        66
         func fprint(w io.Writer, style PathStyle, finds []Find) {

      
        64
        67
         	sortFinds(finds)

      
        
        68
        +	wd, _ := os.Getwd()

      
        65
        69
         	for _, find := range finds {

      
        66
        
        -		_, _ = fmt.Fprintf(w, "%s:%d:%d: %s: %s\n",

      
        67
        
        -			formatPath(style, find.Span.File),

      
        68
        
        -			find.Span.Start.Line, find.Span.Start.Col,

      
        69
        
        -			find.Code, find.Message)

      
        
        70
        +		io.WriteString(w, formatPath(style, find.Span.File, wd))

      
        
        71
        +		io.WriteString(w, ":")

      
        
        72
        +		io.WriteString(w, strconv.Itoa(find.Span.Start.Line))

      
        
        73
        +		io.WriteString(w, ":")

      
        
        74
        +		io.WriteString(w, strconv.Itoa(find.Span.Start.Col))

      
        
        75
        +		io.WriteString(w, ": ")

      
        
        76
        +		io.WriteString(w, string(find.Code))

      
        
        77
        +		io.WriteString(w, ": ")

      
        
        78
        +		io.WriteString(w, find.Message)

      
        
        79
        +		io.WriteString(w, "\n")

      
        70
        80
         	}

      
        71
        81
         }

      
        72
        82
         

      ···
        82
        92
         // fprintJSON writes finds as [findJSON] array.

      
        83
        93
         func fprintJSON(w io.Writer, style PathStyle, finds []Find) error {

      
        84
        94
         	sortFinds(finds)

      
        
        95
        +	wd, _ := os.Getwd()

      
        85
        96
         	jsonFinds := make([]findJSON, len(finds))

      
        86
        97
         	for i, find := range finds {

      
        87
        98
         		jsonFinds[i] = findJSON{

      
        88
        99
         			Message:  find.Message,

      
        89
        100
         			Severity: find.Severity.String(), // TODO: it's unset

      
        90
        101
         			Code:     string(find.Code),

      
        91
        
        -			File:     formatPath(style, find.Span.File),

      
        
        102
        +			File:     formatPath(style, find.Span.File, wd),

      
        92
        103
         			Line:     find.Span.Start.Line,

      
        93
        104
         			Column:   find.Span.Start.Col,

      
        94
        105
         		}

      ···
        96
        107
         	return json.NewEncoder(w).Encode(jsonFinds)

      
        97
        108
         }

      
        98
        109
         

      
        99
        
        -func formatPath(style PathStyle, p string) string {

      
        
        110
        +func formatPath(style PathStyle, path, wdir string) string {

      
        100
        111
         	switch style {

      
        101
        112
         	case PathBasename:

      
        102
        
        -		return filepath.Base(p)

      
        
        113
        +		return filepath.Base(path)

      
        103
        114
         	case PathAbsolute:

      
        104
        
        -		return p

      
        
        115
        +		return path

      
        105
        116
         	case PathRelative:

      
        106
        
        -		wd, err := os.Getwd()

      
        107
        
        -		if err == nil {

      
        108
        
        -			if rel, err := filepath.Rel(wd, p); err == nil {

      
        
        117
        +		if wdir != "" {

      
        
        118
        +			if rel, err := filepath.Rel(wdir, path); err == nil {

      
        109
        119
         				return rel

      
        110
        120
         			}

      
        111
        121
         		}

      
        112
        
        -		return p

      
        
        122
        +		return path

      
        113
        123
         	default:

      
        114
        124
         		panic("impossible PathStyle value")

      
        115
        125
         	}

      
        116
        126
         }

      
        117
        127
         

      
        118
        128
         func sortFinds(finds []Find) {

      
        119
        
        -	sort.Slice(finds, func(i, j int) bool {

      
        120
        
        -		if finds[i].Span.Start.Line != finds[j].Span.Start.Line {

      
        121
        
        -			return finds[i].Span.Start.Line < finds[j].Span.Start.Line

      
        
        129
        +	slices.SortFunc(finds, func(a, b Find) int {

      
        
        130
        +		if a.Span.Start.Line != b.Span.Start.Line {

      
        
        131
        +			return a.Span.Start.Line - b.Span.Start.Line

      
        122
        132
         		}

      
        123
        
        -		if finds[i].Span.Start.Col != finds[j].Span.Start.Col {

      
        124
        
        -			return finds[i].Span.Start.Col < finds[j].Span.Start.Col

      
        
        133
        +		if a.Span.Start.Col != b.Span.Start.Col {

      
        
        134
        +			return a.Span.Start.Col - b.Span.Start.Col

      
        125
        135
         		}

      
        126
        
        -		if finds[i].Code != finds[j].Code {

      
        127
        
        -			return finds[i].Code < finds[j].Code

      
        
        136
        +		if a.Code != b.Code {

      
        
        137
        +			return strings.Compare(string(a.Code), string(b.Code))

      
        128
        138
         		}

      
        129
        
        -		return finds[i].Message < finds[j].Message

      
        
        139
        +		return strings.Compare(a.Message, b.Message)

      
        130
        140
         	})

      
        131
        141
         }

      
M journal/ast/ast.go
···
        78
        78
         	if sep == 0 {

      
        79
        79
         		sep = '-'

      
        80
        80
         	}

      
        
        81
        +	// NOTE(perf): replace sprint with strings.Builder or other kind of buffer

      
        81
        82
         	if d.Year == 0 {

      
        82
        83
         		return fmt.Sprintf("%d%c%d", d.Month, sep, d.Day)

      
        83
        84
         	}