all repos

clerk @ 7ca46359f2e42aa072a37d4952db28cf4b52a2b9

missing tooling for ledger/hledger
3 files changed, 94 insertions(+), 109 deletions(-)
improve lsputil tests
Author: Oleksandr Smirnov olexsmir@gmail.com
Committed at: 2026-08-03 12:52:15 +0300
Authored at: 2026-08-02 21:09:51 +0300
Change ID: ynnxqzrrkmuyxuuwsxtpmzpqowpostln
Parent: 03eda2a
M internal/lsp/lsputil/utf16_test.go
···
        1
        1
         package lsputil

      
        2
        2
         

      
        3
        3
         import (

      
        
        4
        +	"fmt"

      
        4
        5
         	"testing"

      
        5
        6
         	"unicode/utf8"

      
        6
        7
         )

      
        7
        8
         

      
        8
        
        -func TestUtf16Col_Basic(t *testing.T) {

      
        9
        
        -	col := Utf16Col("hello", 3)

      
        10
        
        -	if col != 3 {

      
        11
        
        -		t.Errorf("Utf16Col for ASCII = %d, want 3", col)

      
        
        9
        +func TestUtf16Col(t *testing.T) {

      
        
        10
        +	tests := map[string]struct {

      
        
        11
        +		line   string

      
        
        12
        +		offset int

      
        
        13
        +		want   int

      
        
        14
        +	}{

      
        
        15
        +		"ascii":              {"hello", 3, 3},

      
        
        16
        +		"cyrillic full line": {"Привіт", len("Привіт"), 6},

      
        12
        17
         	}

      
        13
        
        -}

      
        14
        
        -

      
        15
        
        -func TestUtf16Col_Cyrillic(t *testing.T) {

      
        16
        
        -	// "Привет" is 6 Cyrillic chars = 12 bytes, 6 UTF-16 code units

      
        17
        
        -	line := "Привет"

      
        18
        
        -	col := Utf16Col(line, len(line))

      
        19
        
        -	if col != 6 {

      
        20
        
        -		t.Errorf("Utf16Col for 'Привет' = %d, want 6", col)

      
        
        18
        +	for name, tt := range tests {

      
        
        19
        +		t.Run(name, func(t *testing.T) {

      
        
        20
        +			if got := Utf16Col(tt.line, tt.offset); got != tt.want {

      
        
        21
        +				t.Errorf("Utf16Col(%q, %d) = %d, want %d", tt.line, tt.offset, got, tt.want)

      
        
        22
        +			}

      
        
        23
        +		})

      
        21
        24
         	}

      
        22
        25
         }

      
        23
        26
         

      
        24
        
        -func TestUtf16Len_ASCII(t *testing.T) {

      
        25
        
        -	l := Utf16Len("hello world", 0, 5)

      
        26
        
        -	if l != 5 {

      
        27
        
        -		t.Errorf("Utf16Len = %d, want 5", l)

      
        
        27
        +func TestUtf16Len(t *testing.T) {

      
        
        28
        +	tests := map[string]struct {

      
        
        29
        +		content string

      
        
        30
        +		offset  int

      
        
        31
        +		end     int

      
        
        32
        +		want    int

      
        
        33
        +	}{

      
        
        34
        +		"ascii":    {"hello world", 0, 5, 5},

      
        
        35
        +		"cyrillic": {"Привіт світ", 0, len("Привіт"), 6},

      
        28
        36
         	}

      
        29
        
        -}

      
        30
        
        -

      
        31
        
        -func TestUtf16Len_Cyrillic(t *testing.T) {

      
        32
        
        -	// "Привет" = 6 UTF-16 code units

      
        33
        
        -	l := Utf16Len("Привет мир", 0, 12)

      
        34
        
        -	if l != 6 {

      
        35
        
        -		t.Errorf("Utf16Len for 'Привет' = %d, want 6", l)

      
        
        37
        +	for name, tt := range tests {

      
        
        38
        +		t.Run(name, func(t *testing.T) {

      
        
        39
        +			if got := Utf16Len(tt.content, tt.offset, tt.end); got != tt.want {

      
        
        40
        +				t.Errorf("Utf16Len(%q, %d, %d) = %d, want %d", tt.content, tt.offset, tt.end, got, tt.want)

      
        
        41
        +			}

      
        
        42
        +		})

      
        36
        43
         	}

      
        37
        44
         }

      
        38
        45
         

      
        39
        
        -func TestLineCol_Basic(t *testing.T) {

      
        40
        
        -	line, col := LineCol("hello\nworld", 11)

      
        41
        
        -	if line != 1 || col != 5 {

      
        42
        
        -		t.Errorf("LineCol = (%d,%d), want (1,5)", line, col)

      
        
        46
        +func TestLineCol(t *testing.T) {

      
        
        47
        +	tests := map[string]struct {

      
        
        48
        +		content   string

      
        
        49
        +		offset    int

      
        
        50
        +		line, col int

      
        
        51
        +	}{

      
        
        52
        +		"basic":             {"hello\nworld", 11, 1, 5},

      
        
        53
        +		"first line":        {"hello world", 5, 0, 5},

      
        
        54
        +		"line start":        {"ab\ncd", 3, 1, 0},

      
        
        55
        +		"newline ends line": {"ab\ncd", 2, 0, 2},

      
        
        56
        +		"past blank line":   {"a\n\nb", 3, 2, 0},

      
        
        57
        +		"blank line col":    {"a\n\nb", 2, 1, 0},

      
        
        58
        +		"CRLF line start":   {"ab\r\ncd", 4, 1, 0},

      
        
        59
        +		"crlf newline ends": {"ab\r\ncd", 2, 0, 2},

      
        43
        60
         	}

      
        44
        
        -}

      
        45
        
        -

      
        46
        
        -func TestLineCol_FirstLine(t *testing.T) {

      
        47
        
        -	line, col := LineCol("hello world", 5)

      
        48
        
        -	if line != 0 || col != 5 {

      
        49
        
        -		t.Errorf("LineCol = (%d,%d), want (0,5)", line, col)

      
        
        61
        +	for name, tt := range tests {

      
        
        62
        +		t.Run(name, func(t *testing.T) {

      
        
        63
        +			line, col := LineCol(tt.content, tt.offset)

      
        
        64
        +			if line != tt.line || col != tt.col {

      
        
        65
        +				t.Errorf("LineCol(%q, %d) = (%d,%d), want (%d,%d)", tt.content, tt.offset, line, col, tt.line, tt.col)

      
        
        66
        +			}

      
        
        67
        +		})

      
        50
        68
         	}

      
        51
        69
         }

      
        52
        70
         

      
        53
        
        -func TestLineCol_LineStart(t *testing.T) {

      
        54
        
        -	// offset at the first char of line 1 must be (1,0), not the tail of line 0

      
        55
        
        -	line, col := LineCol("ab\ncd", 3)

      
        56
        
        -	if line != 1 || col != 0 {

      
        57
        
        -		t.Errorf("LineCol(3) = (%d,%d), want (1,0)", line, col)

      
        
        71
        +func TestOffset(t *testing.T) {

      
        
        72
        +	tests := map[string]struct {

      
        
        73
        +		content   string

      
        
        74
        +		line, col int

      
        
        75
        +		want      int

      
        
        76
        +	}{

      
        
        77
        +		"basic":        {"hello\nworld", 1, 5, 11},

      
        
        78
        +		"past EOL col": {"ab\ncd", 0, 99, 2},

      
        
        79
        +		"cyrillic":     {"Привіт😀", 0, 6, 12},

      
        
        80
        +		"emoji":        {"Привіт😀", 0, 7, 16},

      
        58
        81
         	}

      
        59
        
        -	// newline char itself belongs to the line it ends

      
        60
        
        -	line, col = LineCol("ab\ncd", 2)

      
        61
        
        -	if line != 0 || col != 2 {

      
        62
        
        -		t.Errorf("LineCol(2) = (%d,%d), want (0,2)", line, col)

      
        
        82
        +	for name, tt := range tests {

      
        
        83
        +		t.Run(name, func(t *testing.T) {

      
        
        84
        +			if got := Offset(tt.content, tt.line, tt.col); got != tt.want {

      
        
        85
        +				t.Errorf("Offset(%d,%d) = %d, want %d", tt.line, tt.col, got, tt.want)

      
        
        86
        +			}

      
        
        87
        +		})

      
        63
        88
         	}

      
        64
        
        -}

      
        65
        89
         

      
        66
        
        -func TestLineCol_BlankLine(t *testing.T) {

      
        67
        
        -	// "a\n\nb": line 1 is blank; offset 3 is the start of line 2

      
        68
        
        -	line, col := LineCol("a\n\nb", 3)

      
        69
        
        -	if line != 2 || col != 0 {

      
        70
        
        -		t.Errorf("LineCol(3) = (%d,%d), want (2,0)", line, col)

      
        71
        
        -	}

      
        72
        
        -	line, col = LineCol("a\n\nb", 2)

      
        73
        
        -	if line != 1 || col != 0 {

      
        74
        
        -		t.Errorf("LineCol(2) = (%d,%d), want (1,0)", line, col)

      
        75
        
        -	}

      
        76
        
        -}

      
        77
        
        -

      
        78
        
        -func TestLineCol_CRLF(t *testing.T) {

      
        79
        
        -	line, col := LineCol("ab\r\ncd", 4)

      
        80
        
        -	if line != 1 || col != 0 {

      
        81
        
        -		t.Errorf("LineCol(4) = (%d,%d), want (1,0)", line, col)

      
        82
        
        -	}

      
        83
        
        -	line, col = LineCol("ab\r\ncd", 2)

      
        84
        
        -	if line != 0 || col != 2 {

      
        85
        
        -		t.Errorf("LineCol(2) = (%d,%d), want (0,2)", line, col)

      
        86
        
        -	}

      
        87
        
        -}

      
        88
        
        -

      
        89
        
        -func TestOffset_Basic(t *testing.T) {

      
        90
        
        -	if got := Offset("hello\nworld", 1, 5); got != 11 {

      
        91
        
        -		t.Errorf("Offset(1,5) = %d, want 11", got)

      
        92
        
        -	}

      
        
        90
        +	t.Run("past EOF line", func(t *testing.T) {

      
        
        91
        +		if got := Offset("ab\ncd", 5, 0); got != len("ab\ncd") {

      
        
        92
        +			t.Errorf("Offset = %d, want %d", got, len("ab\ncd"))

      
        
        93
        +		}

      
        
        94
        +	})

      
        93
        95
         }

      
        94
        96
         

      
        95
        97
         func TestOffset_RoundTrip(t *testing.T) {

      
        96
        
        -	content := "first\nПривет мир\r\nlast\n"

      
        97
        
        -	// only rune-boundary offsets round-trip (LineCol clamps mid-rune offsets)

      
        
        98
        +	content := "first\nПривіт світ\r\nlast\n"

      
        
        99
        +

      
        98
        100
         	var boundaries []int

      
        99
        101
         	for i := 0; i < len(content); {

      
        100
        102
         		_, size := utf8.DecodeRuneInString(content[i:])

      
        101
        
        -		if !(content[i] == '\n' && i > 0 && content[i-1] == '\r') {

      
        
        103
        +		if content[i] != '\n' || i <= 0 || content[i-1] != '\r' {

      
        102
        104
         			boundaries = append(boundaries, i)

      
        103
        105
         		}

      
        104
        106
         		i += size

      
        105
        107
         	}

      
        106
        
        -	for _, off := range boundaries {

      
        107
        
        -		line, col := LineCol(content, off)

      
        108
        
        -		if got := Offset(content, line, col); got != off {

      
        109
        
        -			t.Errorf("round trip at %d: Offset(LineCol(%d)) = %d", off, off, got)

      
        110
        
        -		}

      
        111
        
        -	}

      
        112
        
        -}

      
        113
        108
         

      
        114
        
        -func TestOffset_Clamps(t *testing.T) {

      
        115
        
        -	content := "ab\ncd"

      
        116
        
        -	if got := Offset(content, 5, 0); got != len(content) {

      
        117
        
        -		t.Errorf("past EOF line: Offset = %d, want %d", got, len(content))

      
        118
        
        -	}

      
        119
        
        -	// column beyond line end clamps to line end

      
        120
        
        -	if got := Offset(content, 0, 99); got != 2 {

      
        121
        
        -		t.Errorf("past EOL col: Offset = %d, want 2", got)

      
        122
        
        -	}

      
        123
        
        -}

      
        124
        
        -

      
        125
        
        -func TestOffset_UTF16(t *testing.T) {

      
        126
        
        -	// Cyrillic chars are 1 UTF-16 unit each; emoji are 2

      
        127
        
        -	content := "Привет😀"

      
        128
        
        -	if got := Offset(content, 0, 6); got != 12 {

      
        129
        
        -		t.Errorf("Offset after Cyrillic = %d, want 12", got)

      
        130
        
        -	}

      
        131
        
        -	if got := Offset(content, 0, 7); got != 16 {

      
        132
        
        -		t.Errorf("Offset after emoji (2 UTF-16 units) = %d, want 16", got)

      
        
        109
        +	for _, off := range boundaries {

      
        
        110
        +		t.Run(fmt.Sprintf("round trip at %d", off), func(t *testing.T) {

      
        
        111
        +			line, col := LineCol(content, off)

      
        
        112
        +			if got := Offset(content, line, col); got != off {

      
        
        113
        +				t.Errorf("round trip at %d: Offset(LineCol(%d)) = %d", off, off, got)

      
        
        114
        +			}

      
        
        115
        +		})

      
        133
        116
         	}

      
        134
        117
         }

      
        135
        118
         

      
        136
        
        -func TestPosition_RoundTrip(t *testing.T) {

      
        137
        
        -	content := "2024-01-15 Супермаркет\r\n    Витрати:Продукти  ¥50\n"

      
        
        119
        +func TestPosition(t *testing.T) {

      
        
        120
        +	content := "2024-01-15 Супермаркет\r\n  Витрати:Продукти  ¥50\n"

      
        138
        121
         	for _, off := range []int{0, 10, len("2024-01-15 Супермаркет"), len(content)} {

      
        139
        
        -		p := Position(content, off)

      
        140
        
        -		if got := Offset(content, int(p.Line), int(p.Character)); got != off {

      
        141
        
        -			t.Errorf("round trip of %d = %d (pos %d:%d), want identity", off, got, p.Line, p.Character)

      
        142
        
        -		}

      
        
        122
        +		t.Run(fmt.Sprintf("round trip at %d", off), func(t *testing.T) {

      
        
        123
        +			p := Position(content, off)

      
        
        124
        +			if got := Offset(content, int(p.Line), int(p.Character)); got != off {

      
        
        125
        +				t.Errorf("round trip of %d = %d (pos %d:%d), want identity", off, got, p.Line, p.Character)

      
        
        126
        +			}

      
        
        127
        +		})

      
        143
        128
         	}

      
        144
        129
         }

      
M internal/lsp/testdata/semantic-journal.txtar
···
        80
        80
         2020-02-14 * more

      
        81
        81
         

      
        82
        82
         2024-01-15 午餐

      
        83
        
        -; Привет мир

      
        84
        
        -2024-01-15 Такси

      
        
        83
        +; Привіт світ

      
        
        84
        +2024-01-15 Таксі

      
        85
        85
         

      
        86
        86
         ~ monthly

      
        87
        87
           a  $10

      ···
        317
        317
         78:13+4 property

      
        318
        318
         80:0+10 class

      
        319
        319
         80:11+2 property

      
        320
        
        -81:0+13 comment

      
        
        320
        +81:0+14 comment

      
        321
        321
         82:0+10 class

      
        322
        322
         82:11+5 property

      
        323
        323
         84:0+1 operator

      
M journal/parser/testdata/commodity_with_subdirectives.txtar
···
        1
        1
         -- input --

      
        2
        2
         commodity UAH

      
        3
        3
           format 1 000.00 UAH

      
        4
        
        -  note Божествена Гривня  ; this gets ignored

      
        
        4
        +  note Божественна Гривня  ; this gets ignored

      
        5
        5
         

      
        6
        6
         -- expect --

      
        7
        7
         Journal