Source file src/cmd/vendor/golang.org/x/tools/internal/astutil/stringlit.go

     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  }
   104  

View as plain text