152 lines
4.0 KiB
Go
152 lines
4.0 KiB
Go
// Copyright 2023 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 tokeninternal provides access to some internal features of the token
|
|
// package.
|
|
package tokeninternal
|
|
|
|
import (
|
|
"fmt"
|
|
"go/token"
|
|
"sort"
|
|
"sync"
|
|
"unsafe"
|
|
)
|
|
|
|
// GetLines returns the table of line-start offsets from a token.File.
|
|
func GetLines(file *token.File) []int {
|
|
// token.File has a Lines method on Go 1.21 and later.
|
|
if file, ok := (interface{})(file).(interface{ Lines() []int }); ok {
|
|
return file.Lines()
|
|
}
|
|
|
|
// This declaration must match that of token.File.
|
|
// This creates a risk of dependency skew.
|
|
// For now we check that the size of the two
|
|
// declarations is the same, on the (fragile) assumption
|
|
// that future changes would add fields.
|
|
type tokenFile119 struct {
|
|
_ string
|
|
_ int
|
|
_ int
|
|
mu sync.Mutex // we're not complete monsters
|
|
lines []int
|
|
_ []struct{}
|
|
}
|
|
type tokenFile118 struct {
|
|
_ *token.FileSet // deleted in go1.19
|
|
tokenFile119
|
|
}
|
|
|
|
type uP = unsafe.Pointer
|
|
switch unsafe.Sizeof(*file) {
|
|
case unsafe.Sizeof(tokenFile118{}):
|
|
var ptr *tokenFile118
|
|
*(*uP)(uP(&ptr)) = uP(file)
|
|
ptr.mu.Lock()
|
|
defer ptr.mu.Unlock()
|
|
return ptr.lines
|
|
|
|
case unsafe.Sizeof(tokenFile119{}):
|
|
var ptr *tokenFile119
|
|
*(*uP)(uP(&ptr)) = uP(file)
|
|
ptr.mu.Lock()
|
|
defer ptr.mu.Unlock()
|
|
return ptr.lines
|
|
|
|
default:
|
|
panic("unexpected token.File size")
|
|
}
|
|
}
|
|
|
|
// AddExistingFiles adds the specified files to the FileSet if they
|
|
// are not already present. It panics if any pair of files in the
|
|
// resulting FileSet would overlap.
|
|
func AddExistingFiles(fset *token.FileSet, files []*token.File) {
|
|
// Punch through the FileSet encapsulation.
|
|
type tokenFileSet struct {
|
|
// This type remained essentially consistent from go1.16 to go1.21.
|
|
mutex sync.RWMutex
|
|
base int
|
|
files []*token.File
|
|
_ *token.File // changed to atomic.Pointer[token.File] in go1.19
|
|
}
|
|
|
|
// If the size of token.FileSet changes, this will fail to compile.
|
|
const delta = int64(unsafe.Sizeof(tokenFileSet{})) - int64(unsafe.Sizeof(token.FileSet{}))
|
|
var _ [-delta * delta]int
|
|
|
|
type uP = unsafe.Pointer
|
|
var ptr *tokenFileSet
|
|
*(*uP)(uP(&ptr)) = uP(fset)
|
|
ptr.mutex.Lock()
|
|
defer ptr.mutex.Unlock()
|
|
|
|
// Merge and sort.
|
|
newFiles := append(ptr.files, files...)
|
|
sort.Slice(newFiles, func(i, j int) bool {
|
|
return newFiles[i].Base() < newFiles[j].Base()
|
|
})
|
|
|
|
// Reject overlapping files.
|
|
// Discard adjacent identical files.
|
|
out := newFiles[:0]
|
|
for i, file := range newFiles {
|
|
if i > 0 {
|
|
prev := newFiles[i-1]
|
|
if file == prev {
|
|
continue
|
|
}
|
|
if prev.Base()+prev.Size()+1 > file.Base() {
|
|
panic(fmt.Sprintf("file %s (%d-%d) overlaps with file %s (%d-%d)",
|
|
prev.Name(), prev.Base(), prev.Base()+prev.Size(),
|
|
file.Name(), file.Base(), file.Base()+file.Size()))
|
|
}
|
|
}
|
|
out = append(out, file)
|
|
}
|
|
newFiles = out
|
|
|
|
ptr.files = newFiles
|
|
|
|
// Advance FileSet.Base().
|
|
if len(newFiles) > 0 {
|
|
last := newFiles[len(newFiles)-1]
|
|
newBase := last.Base() + last.Size() + 1
|
|
if ptr.base < newBase {
|
|
ptr.base = newBase
|
|
}
|
|
}
|
|
}
|
|
|
|
// FileSetFor returns a new FileSet containing a sequence of new Files with
|
|
// the same base, size, and line as the input files, for use in APIs that
|
|
// require a FileSet.
|
|
//
|
|
// Precondition: the input files must be non-overlapping, and sorted in order
|
|
// of their Base.
|
|
func FileSetFor(files ...*token.File) *token.FileSet {
|
|
fset := token.NewFileSet()
|
|
for _, f := range files {
|
|
f2 := fset.AddFile(f.Name(), f.Base(), f.Size())
|
|
lines := GetLines(f)
|
|
f2.SetLines(lines)
|
|
}
|
|
return fset
|
|
}
|
|
|
|
// CloneFileSet creates a new FileSet holding all files in fset. It does not
|
|
// create copies of the token.Files in fset: they are added to the resulting
|
|
// FileSet unmodified.
|
|
func CloneFileSet(fset *token.FileSet) *token.FileSet {
|
|
var files []*token.File
|
|
fset.Iterate(func(f *token.File) bool {
|
|
files = append(files, f)
|
|
return true
|
|
})
|
|
newFileSet := token.NewFileSet()
|
|
AddExistingFiles(newFileSet, files)
|
|
return newFileSet
|
|
}
|