blob: 0c76e90b1c0b881ae907808dfac14bfafc22f7bf [file] [log] [blame]
// Copyright 2020 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 godoc
import (
"bytes"
"context"
"encoding/gob"
"fmt"
"go/token"
"io"
"golang.org/x/pkgsite/internal/derrors"
"golang.org/x/pkgsite/internal/godoc/codec"
)
// The encoding type identifies the encoding being used, to distinguish them
// when reading from the DB.
const (
encodingTypeLen = 4 // all encoding types must be this many bytes
fastEncodingType = "AST2"
)
// ErrInvalidEncodingType is returned when the data to DecodePackage has an
// invalid encoding type.
var ErrInvalidEncodingType = fmt.Errorf("want initial bytes to be %q but they aren't", fastEncodingType)
// Encode encodes a Package into a byte slice.
// During its operation, Encode modifies the AST,
// but it restores it to a state suitable for
// rendering before it returns.
func (p *Package) Encode(ctx context.Context) (_ []byte, err error) {
defer derrors.Wrap(&err, "godoc.Package.Encode()")
return p.fastEncode()
}
// DecodPackage decodes a byte slice encoded with Package.Encode into a Package.
func DecodePackage(data []byte) (_ *Package, err error) {
defer derrors.Wrap(&err, "DecodePackage()")
if len(data) < encodingTypeLen {
return nil, ErrInvalidEncodingType
}
switch string(data[:encodingTypeLen]) {
case fastEncodingType:
return fastDecodePackage(data[encodingTypeLen:])
default:
return nil, ErrInvalidEncodingType
}
}
func (p *Package) fastEncode() (_ []byte, err error) {
defer derrors.Wrap(&err, "godoc.Package.FastEncode()")
var buf bytes.Buffer
io.WriteString(&buf, fastEncodingType)
enc := codec.NewEncoder()
fsb, err := fsetToBytes(p.Fset)
if err != nil {
return nil, err
}
if err := enc.Encode(fsb); err != nil {
return nil, err
}
if err := enc.Encode(&p.encPackage); err != nil {
return nil, err
}
buf.Write(enc.Bytes())
return buf.Bytes(), nil
}
func fastDecodePackage(data []byte) (_ *Package, err error) {
defer derrors.Wrap(&err, "FastDecodePackage()")
dec := codec.NewDecoder(data)
x, err := dec.Decode()
if err != nil {
return nil, err
}
fsetBytes, ok := x.([]byte)
if !ok {
return nil, fmt.Errorf("first decoded value is %T, wanted []byte", fsetBytes)
}
fset, err := fsetFromBytes(fsetBytes)
if err != nil {
return nil, err
}
x, err = dec.Decode()
if err != nil {
return nil, err
}
ep, ok := x.(*encPackage)
if !ok {
return nil, fmt.Errorf("second decoded value is %T, wanted *encPackage", ep)
}
return &Package{
Fset: fset,
encPackage: *ep,
}, nil
}
// token.FileSet uses some unexported types in its encoding, so we can't use our
// own codec from it. Instead we use gob and encode the resulting bytes.
func fsetToBytes(fset *token.FileSet) ([]byte, error) {
var buf bytes.Buffer
enc := gob.NewEncoder(&buf)
if err := fset.Write(enc.Encode); err != nil {
return nil, err
}
return buf.Bytes(), nil
}
func fsetFromBytes(data []byte) (*token.FileSet, error) {
dec := gob.NewDecoder(bytes.NewReader(data))
fset := token.NewFileSet()
if err := fset.Read(dec.Decode); err != nil {
return nil, err
}
return fset, nil
}
//go:generate go run gen_ast.go
// Used by the gen program to generate encodings for unexported types.
var TypesToGenerate = []any{&encPackage{}}