src

Go monorepo.
git clone git://code.dwrz.net/src
Log | Files | Refs

stringlit.go (2769B)


      1 // Copyright 2025 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 package astutil
      6 
      7 import (
      8 	"fmt"
      9 	"go/ast"
     10 	"go/token"
     11 	"strconv"
     12 	"unicode/utf8"
     13 )
     14 
     15 // RangeInStringLiteral calculates the positional range within a string literal
     16 // corresponding to the specified start and end byte offsets within the logical string.
     17 func RangeInStringLiteral(lit *ast.BasicLit, start, end int) (Range, error) {
     18 	startPos, err := PosInStringLiteral(lit, start)
     19 	if err != nil {
     20 		return Range{}, fmt.Errorf("start: %v", err)
     21 	}
     22 	endPos, err := PosInStringLiteral(lit, end)
     23 	if err != nil {
     24 		return Range{}, fmt.Errorf("end: %v", err)
     25 	}
     26 	return Range{startPos, endPos}, nil
     27 }
     28 
     29 // PosInStringLiteral returns the position within a string literal
     30 // corresponding to the specified byte offset within the logical
     31 // string that it denotes.
     32 func PosInStringLiteral(lit *ast.BasicLit, offset int) (token.Pos, error) {
     33 	raw := lit.Value
     34 
     35 	value, err := strconv.Unquote(raw)
     36 	if err != nil {
     37 		return 0, err
     38 	}
     39 	if !(0 <= offset && offset <= len(value)) {
     40 		return 0, fmt.Errorf("invalid offset")
     41 	}
     42 
     43 	pos, _ := walkStringLiteral(lit, lit.End(), offset)
     44 	return pos, nil
     45 }
     46 
     47 // OffsetInStringLiteral returns the byte offset within the logical (unquoted)
     48 // string corresponding to the specified source position.
     49 func OffsetInStringLiteral(lit *ast.BasicLit, pos token.Pos) (int, error) {
     50 	if !NodeContainsPos(lit, pos) {
     51 		return 0, fmt.Errorf("invalid position")
     52 	}
     53 
     54 	raw := lit.Value
     55 
     56 	value, err := strconv.Unquote(raw)
     57 	if err != nil {
     58 		return 0, err
     59 	}
     60 
     61 	_, offset := walkStringLiteral(lit, pos, len(value))
     62 	return offset, nil
     63 }
     64 
     65 // walkStringLiteral iterates through the raw string literal to map between
     66 // a file position and a logical byte offset. It stops when it reaches
     67 // either the targetPos or the targetOffset.
     68 //
     69 // TODO(hxjiang): consider making an iterator.
     70 func walkStringLiteral(lit *ast.BasicLit, targetPos token.Pos, targetOffset int) (token.Pos, int) {
     71 	raw := lit.Value
     72 	norm := int(lit.End()-lit.Pos()) > len(lit.Value)
     73 
     74 	// remove quotes
     75 	quote := raw[0] // '"' or '`'
     76 	raw = raw[1 : len(raw)-1]
     77 
     78 	var (
     79 		i   = 0             // byte index within logical value
     80 		pos = lit.Pos() + 1 // position within literal
     81 	)
     82 
     83 	for raw != "" {
     84 		r, _, rest, _ := strconv.UnquoteChar(raw, quote) // can't fail
     85 		sz := len(raw) - len(rest)                       // length of literal char in raw bytes
     86 
     87 		nextPos := pos + token.Pos(sz)
     88 		if norm && r == '\n' {
     89 			nextPos++
     90 		}
     91 		nextI := i + utf8.RuneLen(r) // length of logical char in "cooked" bytes
     92 
     93 		if nextPos > targetPos || nextI > targetOffset {
     94 			break
     95 		}
     96 
     97 		raw = raw[sz:]
     98 		i = nextI
     99 		pos = nextPos
    100 	}
    101 
    102 	return pos, i
    103 }