| // Copyright 2019 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package span_test |
| |
| import ( |
| "strings" |
| "testing" |
| |
| "golang.org/x/tools/internal/span" |
| ) |
| |
| // The funny character below is 4 bytes long in UTF-8; two UTF-16 code points |
| var funnyString = []byte("š23\nš45") |
| |
| var toUTF16Tests = []struct { |
| scenario string |
| input []byte |
| line int // 1-indexed count |
| col int // 1-indexed byte position in line |
| offset int // 0-indexed byte offset into input |
| resUTF16col int // 1-indexed UTF-16 col number |
| pre string // everything before the cursor on the line |
| post string // everything from the cursor onwards |
| err string // expected error string in call to ToUTF16Column |
| issue *bool |
| }{ |
| { |
| scenario: "cursor missing content", |
| input: nil, |
| err: "ToUTF16Column: point is missing position", |
| }, |
| { |
| scenario: "cursor missing position", |
| input: funnyString, |
| line: -1, |
| col: -1, |
| err: "ToUTF16Column: point is missing position", |
| }, |
| { |
| scenario: "cursor missing offset", |
| input: funnyString, |
| line: 1, |
| col: 1, |
| offset: -1, |
| err: "ToUTF16Column: point is missing offset", |
| }, |
| { |
| scenario: "zero length input; cursor at first col, first line", |
| input: []byte(""), |
| line: 1, |
| col: 1, |
| offset: 0, |
| resUTF16col: 1, |
| }, |
| { |
| scenario: "cursor before funny character; first line", |
| input: funnyString, |
| line: 1, |
| col: 1, |
| offset: 0, |
| resUTF16col: 1, |
| pre: "", |
| post: "š23", |
| }, |
| { |
| scenario: "cursor after funny character; first line", |
| input: funnyString, |
| line: 1, |
| col: 5, // 4 + 1 (1-indexed) |
| offset: 4, |
| resUTF16col: 3, // 2 + 1 (1-indexed) |
| pre: "š", |
| post: "23", |
| }, |
| { |
| scenario: "cursor after last character on first line", |
| input: funnyString, |
| line: 1, |
| col: 7, // 4 + 1 + 1 + 1 (1-indexed) |
| offset: 6, // 4 + 1 + 1 |
| resUTF16col: 5, // 2 + 1 + 1 + 1 (1-indexed) |
| pre: "š23", |
| post: "", |
| }, |
| { |
| scenario: "cursor before funny character; second line", |
| input: funnyString, |
| line: 2, |
| col: 1, |
| offset: 7, // length of first line |
| resUTF16col: 1, |
| pre: "", |
| post: "š45", |
| }, |
| { |
| scenario: "cursor after funny character; second line", |
| input: funnyString, |
| line: 1, |
| col: 5, // 4 + 1 (1-indexed) |
| offset: 11, // 7 (length of first line) + 4 |
| resUTF16col: 3, // 2 + 1 (1-indexed) |
| pre: "š", |
| post: "45", |
| }, |
| { |
| scenario: "cursor after last character on second line", |
| input: funnyString, |
| line: 2, |
| col: 7, // 4 + 1 + 1 + 1 (1-indexed) |
| offset: 13, // 7 (length of first line) + 4 + 1 + 1 |
| resUTF16col: 5, // 2 + 1 + 1 + 1 (1-indexed) |
| pre: "š45", |
| post: "", |
| }, |
| { |
| scenario: "cursor beyond end of file", |
| input: funnyString, |
| line: 2, |
| col: 8, // 4 + 1 + 1 + 1 + 1 (1-indexed) |
| offset: 14, // 4 + 1 + 1 + 1 |
| err: "ToUTF16Column: offsets 7-14 outside file contents (13)", |
| }, |
| } |
| |
| var fromUTF16Tests = []struct { |
| scenario string |
| input []byte |
| line int // 1-indexed line number (isn't actually used) |
| offset int // 0-indexed byte offset to beginning of line |
| utf16col int // 1-indexed UTF-16 col number |
| resCol int // 1-indexed byte position in line |
| resOffset int // 0-indexed byte offset into input |
| pre string // everything before the cursor on the line |
| post string // everything from the cursor onwards |
| err string // expected error string in call to ToUTF16Column |
| }{ |
| { |
| scenario: "zero length input; cursor at first col, first line", |
| input: []byte(""), |
| line: 1, |
| offset: 0, |
| utf16col: 1, |
| resCol: 1, |
| resOffset: 0, |
| pre: "", |
| post: "", |
| }, |
| { |
| scenario: "missing offset", |
| input: funnyString, |
| line: 1, |
| offset: -1, |
| err: "FromUTF16Column: point is missing offset", |
| }, |
| { |
| scenario: "cursor before funny character", |
| input: funnyString, |
| line: 1, |
| utf16col: 1, |
| resCol: 1, |
| resOffset: 0, |
| pre: "", |
| post: "š23", |
| }, |
| { |
| scenario: "cursor after funny character", |
| input: funnyString, |
| line: 1, |
| utf16col: 3, |
| resCol: 5, |
| resOffset: 4, |
| pre: "š", |
| post: "23", |
| }, |
| { |
| scenario: "cursor after last character on line", |
| input: funnyString, |
| line: 1, |
| utf16col: 5, |
| resCol: 7, |
| resOffset: 6, |
| pre: "š23", |
| post: "", |
| }, |
| { |
| scenario: "cursor beyond last character on line", |
| input: funnyString, |
| line: 1, |
| offset: 0, |
| utf16col: 6, |
| resCol: 7, |
| resOffset: 6, |
| pre: "š23", |
| post: "", |
| }, |
| { |
| scenario: "cursor before funny character; second line", |
| input: funnyString, |
| line: 2, |
| offset: 7, // length of first line |
| utf16col: 1, |
| resCol: 1, |
| resOffset: 7, |
| pre: "", |
| post: "š45", |
| }, |
| { |
| scenario: "cursor after funny character; second line", |
| input: funnyString, |
| line: 2, |
| offset: 7, // length of first line |
| utf16col: 3, // 2 + 1 (1-indexed) |
| resCol: 5, // 4 + 1 (1-indexed) |
| resOffset: 11, // 7 (length of first line) + 4 |
| pre: "š", |
| post: "45", |
| }, |
| { |
| scenario: "cursor after last character on second line", |
| input: funnyString, |
| line: 2, |
| offset: 7, // length of first line |
| utf16col: 5, // 2 + 1 + 1 + 1 (1-indexed) |
| resCol: 7, // 4 + 1 + 1 + 1 (1-indexed) |
| resOffset: 13, // 7 (length of first line) + 4 + 1 + 1 |
| pre: "š45", |
| post: "", |
| }, |
| { |
| scenario: "cursor beyond end of file", |
| input: funnyString, |
| line: 2, |
| offset: 7, |
| utf16col: 6, // 2 + 1 + 1 + 1 + 1(1-indexed) |
| resCol: 8, // 4 + 1 + 1 + 1 + 1 (1-indexed) |
| resOffset: 14, // 7 (length of first line) + 4 + 1 + 1 + 1 |
| err: "FromUTF16Column: chr goes beyond the content", |
| }, |
| { |
| scenario: "offset beyond end of file", |
| input: funnyString, |
| line: 2, |
| offset: 14, |
| utf16col: 2, |
| err: "FromUTF16Column: offset (14) greater than length of content (13)", |
| }, |
| } |
| |
| func TestToUTF16(t *testing.T) { |
| for _, e := range toUTF16Tests { |
| t.Run(e.scenario, func(t *testing.T) { |
| if e.issue != nil && !*e.issue { |
| t.Skip("expected to fail") |
| } |
| p := span.NewPoint(e.line, e.col, e.offset) |
| got, err := span.ToUTF16Column(p, e.input) |
| if err != nil { |
| if err.Error() != e.err { |
| t.Fatalf("expected error %v; got %v", e.err, err) |
| } |
| return |
| } |
| if e.err != "" { |
| t.Fatalf("unexpected success; wanted %v", e.err) |
| } |
| if got != e.resUTF16col { |
| t.Fatalf("expected result %v; got %v", e.resUTF16col, got) |
| } |
| pre, post := getPrePost(e.input, p.Offset()) |
| if string(pre) != e.pre { |
| t.Fatalf("expected #%d pre %q; got %q", p.Offset(), e.pre, pre) |
| } |
| if string(post) != e.post { |
| t.Fatalf("expected #%d, post %q; got %q", p.Offset(), e.post, post) |
| } |
| }) |
| } |
| } |
| |
| func TestFromUTF16(t *testing.T) { |
| for _, e := range fromUTF16Tests { |
| t.Run(e.scenario, func(t *testing.T) { |
| p := span.NewPoint(e.line, 1, e.offset) |
| p, err := span.FromUTF16Column(p, e.utf16col, []byte(e.input)) |
| if err != nil { |
| if err.Error() != e.err { |
| t.Fatalf("expected error %v; got %v", e.err, err) |
| } |
| return |
| } |
| if e.err != "" { |
| t.Fatalf("unexpected success; wanted %v", e.err) |
| } |
| if p.Column() != e.resCol { |
| t.Fatalf("expected resulting col %v; got %v", e.resCol, p.Column()) |
| } |
| if p.Offset() != e.resOffset { |
| t.Fatalf("expected resulting offset %v; got %v", e.resOffset, p.Offset()) |
| } |
| pre, post := getPrePost(e.input, p.Offset()) |
| if string(pre) != e.pre { |
| t.Fatalf("expected #%d pre %q; got %q", p.Offset(), e.pre, pre) |
| } |
| if string(post) != e.post { |
| t.Fatalf("expected #%d post %q; got %q", p.Offset(), e.post, post) |
| } |
| }) |
| } |
| } |
| |
| func getPrePost(content []byte, offset int) (string, string) { |
| pre, post := string(content)[:offset], string(content)[offset:] |
| if i := strings.LastIndex(pre, "\n"); i >= 0 { |
| pre = pre[i+1:] |
| } |
| if i := strings.IndexRune(post, '\n'); i >= 0 { |
| post = post[:i] |
| } |
| return pre, post |
| } |