remove unused packages
This commit is contained in:
parent
079ff1ee3e
commit
b1f5661baf
@ -1,320 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/api/encoder"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/astnav"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ls"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/logging"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tsoptions"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
|
||||
"github.com/go-json-experiment/json"
|
||||
)
|
||||
|
||||
type handleMap[T any] map[Handle[T]]*T
|
||||
|
||||
type APIInit struct {
|
||||
Logger logging.Logger
|
||||
FS vfs.FS
|
||||
SessionOptions *project.SessionOptions
|
||||
}
|
||||
|
||||
type API struct {
|
||||
logger logging.Logger
|
||||
session *project.Session
|
||||
|
||||
projects map[Handle[project.Project]]tspath.Path
|
||||
filesMu sync.Mutex
|
||||
files handleMap[ast.SourceFile]
|
||||
symbolsMu sync.Mutex
|
||||
symbols handleMap[ast.Symbol]
|
||||
typesMu sync.Mutex
|
||||
types handleMap[checker.Type]
|
||||
}
|
||||
|
||||
func NewAPI(init *APIInit) *API {
|
||||
api := &API{
|
||||
session: project.NewSession(&project.SessionInit{
|
||||
Logger: init.Logger,
|
||||
FS: init.FS,
|
||||
Options: init.SessionOptions,
|
||||
}),
|
||||
projects: make(map[Handle[project.Project]]tspath.Path),
|
||||
files: make(handleMap[ast.SourceFile]),
|
||||
symbols: make(handleMap[ast.Symbol]),
|
||||
types: make(handleMap[checker.Type]),
|
||||
}
|
||||
|
||||
return api
|
||||
}
|
||||
|
||||
func (api *API) HandleRequest(ctx context.Context, method string, payload []byte) ([]byte, error) {
|
||||
params, err := unmarshalPayload(method, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
switch Method(method) {
|
||||
case MethodRelease:
|
||||
if id, ok := params.(*string); ok {
|
||||
return nil, api.releaseHandle(*id)
|
||||
} else {
|
||||
return nil, fmt.Errorf("expected string for release handle, got %T", params)
|
||||
}
|
||||
case MethodGetSourceFile:
|
||||
params := params.(*GetSourceFileParams)
|
||||
sourceFile, err := api.GetSourceFile(params.Project, params.FileName)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return encoder.EncodeSourceFile(sourceFile, string(FileHandle(sourceFile)))
|
||||
case MethodParseConfigFile:
|
||||
return encodeJSON(api.ParseConfigFile(params.(*ParseConfigFileParams).FileName))
|
||||
case MethodLoadProject:
|
||||
return encodeJSON(api.LoadProject(ctx, params.(*LoadProjectParams).ConfigFileName))
|
||||
case MethodGetSymbolAtPosition:
|
||||
params := params.(*GetSymbolAtPositionParams)
|
||||
return encodeJSON(api.GetSymbolAtPosition(ctx, params.Project, params.FileName, int(params.Position)))
|
||||
case MethodGetSymbolsAtPositions:
|
||||
params := params.(*GetSymbolsAtPositionsParams)
|
||||
return encodeJSON(core.TryMap(params.Positions, func(position uint32) (any, error) {
|
||||
return api.GetSymbolAtPosition(ctx, params.Project, params.FileName, int(position))
|
||||
}))
|
||||
case MethodGetSymbolAtLocation:
|
||||
params := params.(*GetSymbolAtLocationParams)
|
||||
return encodeJSON(api.GetSymbolAtLocation(ctx, params.Project, params.Location))
|
||||
case MethodGetSymbolsAtLocations:
|
||||
params := params.(*GetSymbolsAtLocationsParams)
|
||||
return encodeJSON(core.TryMap(params.Locations, func(location Handle[ast.Node]) (any, error) {
|
||||
return api.GetSymbolAtLocation(ctx, params.Project, location)
|
||||
}))
|
||||
case MethodGetTypeOfSymbol:
|
||||
params := params.(*GetTypeOfSymbolParams)
|
||||
return encodeJSON(api.GetTypeOfSymbol(ctx, params.Project, params.Symbol))
|
||||
case MethodGetTypesOfSymbols:
|
||||
params := params.(*GetTypesOfSymbolsParams)
|
||||
return encodeJSON(core.TryMap(params.Symbols, func(symbol Handle[ast.Symbol]) (any, error) {
|
||||
return api.GetTypeOfSymbol(ctx, params.Project, symbol)
|
||||
}))
|
||||
default:
|
||||
return nil, fmt.Errorf("unhandled API method %q", method)
|
||||
}
|
||||
}
|
||||
|
||||
func (api *API) Close() {
|
||||
api.session.Close()
|
||||
}
|
||||
|
||||
func (api *API) ParseConfigFile(configFileName string) (*ConfigFileResponse, error) {
|
||||
configFileName = api.toAbsoluteFileName(configFileName)
|
||||
configFileContent, ok := api.session.FS().ReadFile(configFileName)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("could not read file %q", configFileName)
|
||||
}
|
||||
configDir := tspath.GetDirectoryPath(configFileName)
|
||||
tsConfigSourceFile := tsoptions.NewTsconfigSourceFileFromFilePath(configFileName, api.toPath(configFileName), configFileContent)
|
||||
parsedCommandLine := tsoptions.ParseJsonSourceFileConfigFileContent(
|
||||
tsConfigSourceFile,
|
||||
api.session,
|
||||
configDir,
|
||||
nil, /*existingOptions*/
|
||||
configFileName,
|
||||
nil, /*resolutionStack*/
|
||||
nil, /*extraFileExtensions*/
|
||||
nil, /*extendedConfigCache*/
|
||||
)
|
||||
return &ConfigFileResponse{
|
||||
FileNames: parsedCommandLine.FileNames(),
|
||||
Options: parsedCommandLine.CompilerOptions(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (api *API) LoadProject(ctx context.Context, configFileName string) (*ProjectResponse, error) {
|
||||
project, err := api.session.OpenProject(ctx, api.toAbsoluteFileName(configFileName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
data := NewProjectResponse(project)
|
||||
api.projects[data.Id] = project.ConfigFilePath()
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (api *API) GetSymbolAtPosition(ctx context.Context, projectId Handle[project.Project], fileName string, position int) (*SymbolResponse, error) {
|
||||
projectPath, ok := api.projects[projectId]
|
||||
if !ok {
|
||||
return nil, errors.New("project ID not found")
|
||||
}
|
||||
snapshot, release := api.session.Snapshot()
|
||||
defer release()
|
||||
project := snapshot.ProjectCollection.GetProjectByPath(projectPath)
|
||||
if project == nil {
|
||||
return nil, errors.New("project not found")
|
||||
}
|
||||
|
||||
languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
|
||||
symbol, err := languageService.GetSymbolAtPosition(ctx, fileName, position)
|
||||
if err != nil || symbol == nil {
|
||||
return nil, err
|
||||
}
|
||||
data := NewSymbolResponse(symbol)
|
||||
api.symbolsMu.Lock()
|
||||
defer api.symbolsMu.Unlock()
|
||||
api.symbols[data.Id] = symbol
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (api *API) GetSymbolAtLocation(ctx context.Context, projectId Handle[project.Project], location Handle[ast.Node]) (*SymbolResponse, error) {
|
||||
projectPath, ok := api.projects[projectId]
|
||||
if !ok {
|
||||
return nil, errors.New("project ID not found")
|
||||
}
|
||||
snapshot, release := api.session.Snapshot()
|
||||
defer release()
|
||||
project := snapshot.ProjectCollection.GetProjectByPath(projectPath)
|
||||
if project == nil {
|
||||
return nil, errors.New("project not found")
|
||||
}
|
||||
|
||||
fileHandle, pos, kind, err := parseNodeHandle(location)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
api.filesMu.Lock()
|
||||
defer api.filesMu.Unlock()
|
||||
sourceFile, ok := api.files[fileHandle]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("file %q not found", fileHandle)
|
||||
}
|
||||
token := astnav.GetTokenAtPosition(sourceFile, pos)
|
||||
if token == nil {
|
||||
return nil, fmt.Errorf("token not found at position %d in file %q", pos, sourceFile.FileName())
|
||||
}
|
||||
node := ast.FindAncestorKind(token, kind)
|
||||
if node == nil {
|
||||
return nil, fmt.Errorf("node of kind %s not found at position %d in file %q", kind.String(), pos, sourceFile.FileName())
|
||||
}
|
||||
languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
|
||||
symbol := languageService.GetSymbolAtLocation(ctx, node)
|
||||
if symbol == nil {
|
||||
return nil, nil
|
||||
}
|
||||
data := NewSymbolResponse(symbol)
|
||||
api.symbolsMu.Lock()
|
||||
defer api.symbolsMu.Unlock()
|
||||
api.symbols[data.Id] = symbol
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func (api *API) GetTypeOfSymbol(ctx context.Context, projectId Handle[project.Project], symbolHandle Handle[ast.Symbol]) (*TypeResponse, error) {
|
||||
projectPath, ok := api.projects[projectId]
|
||||
if !ok {
|
||||
return nil, errors.New("project ID not found")
|
||||
}
|
||||
snapshot, release := api.session.Snapshot()
|
||||
defer release()
|
||||
project := snapshot.ProjectCollection.GetProjectByPath(projectPath)
|
||||
if project == nil {
|
||||
return nil, errors.New("project not found")
|
||||
}
|
||||
|
||||
api.symbolsMu.Lock()
|
||||
defer api.symbolsMu.Unlock()
|
||||
symbol, ok := api.symbols[symbolHandle]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("symbol %q not found", symbolHandle)
|
||||
}
|
||||
languageService := ls.NewLanguageService(project.GetProgram(), snapshot)
|
||||
t := languageService.GetTypeOfSymbol(ctx, symbol)
|
||||
if t == nil {
|
||||
return nil, nil
|
||||
}
|
||||
return NewTypeData(t), nil
|
||||
}
|
||||
|
||||
func (api *API) GetSourceFile(projectId Handle[project.Project], fileName string) (*ast.SourceFile, error) {
|
||||
projectPath, ok := api.projects[projectId]
|
||||
if !ok {
|
||||
return nil, errors.New("project ID not found")
|
||||
}
|
||||
snapshot, release := api.session.Snapshot()
|
||||
defer release()
|
||||
project := snapshot.ProjectCollection.GetProjectByPath(projectPath)
|
||||
if project == nil {
|
||||
return nil, errors.New("project not found")
|
||||
}
|
||||
|
||||
sourceFile := project.GetProgram().GetSourceFile(fileName)
|
||||
if sourceFile == nil {
|
||||
return nil, fmt.Errorf("source file %q not found", fileName)
|
||||
}
|
||||
api.filesMu.Lock()
|
||||
defer api.filesMu.Unlock()
|
||||
api.files[FileHandle(sourceFile)] = sourceFile
|
||||
return sourceFile, nil
|
||||
}
|
||||
|
||||
func (api *API) releaseHandle(handle string) error {
|
||||
switch handle[0] {
|
||||
case handlePrefixProject:
|
||||
projectId := Handle[project.Project](handle)
|
||||
_, ok := api.projects[projectId]
|
||||
if !ok {
|
||||
return fmt.Errorf("project %q not found", handle)
|
||||
}
|
||||
delete(api.projects, projectId)
|
||||
case handlePrefixFile:
|
||||
fileId := Handle[ast.SourceFile](handle)
|
||||
api.filesMu.Lock()
|
||||
defer api.filesMu.Unlock()
|
||||
_, ok := api.files[fileId]
|
||||
if !ok {
|
||||
return fmt.Errorf("file %q not found", handle)
|
||||
}
|
||||
delete(api.files, fileId)
|
||||
case handlePrefixSymbol:
|
||||
symbolId := Handle[ast.Symbol](handle)
|
||||
api.symbolsMu.Lock()
|
||||
defer api.symbolsMu.Unlock()
|
||||
_, ok := api.symbols[symbolId]
|
||||
if !ok {
|
||||
return fmt.Errorf("symbol %q not found", handle)
|
||||
}
|
||||
delete(api.symbols, symbolId)
|
||||
case handlePrefixType:
|
||||
typeId := Handle[checker.Type](handle)
|
||||
api.typesMu.Lock()
|
||||
defer api.typesMu.Unlock()
|
||||
_, ok := api.types[typeId]
|
||||
if !ok {
|
||||
return fmt.Errorf("type %q not found", handle)
|
||||
}
|
||||
delete(api.types, typeId)
|
||||
default:
|
||||
return fmt.Errorf("unhandled handle type %q", handle[0])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (api *API) toAbsoluteFileName(fileName string) string {
|
||||
return tspath.GetNormalizedAbsolutePath(fileName, api.session.GetCurrentDirectory())
|
||||
}
|
||||
|
||||
func (api *API) toPath(fileName string) tspath.Path {
|
||||
return tspath.ToPath(fileName, api.session.GetCurrentDirectory(), api.session.FS().UseCaseSensitiveFileNames())
|
||||
}
|
||||
|
||||
func encodeJSON(v any, err error) ([]byte, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return json.Marshal(v)
|
||||
}
|
||||
@ -1,815 +0,0 @@
|
||||
package encoder
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"slices"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
||||
)
|
||||
|
||||
const (
|
||||
NodeOffsetKind = iota * 4
|
||||
NodeOffsetPos
|
||||
NodeOffsetEnd
|
||||
NodeOffsetNext
|
||||
NodeOffsetParent
|
||||
NodeOffsetData
|
||||
// NodeSize is the number of bytes that represents a single node in the encoded format.
|
||||
NodeSize
|
||||
)
|
||||
|
||||
const (
|
||||
NodeDataTypeChildren uint32 = iota << 30
|
||||
NodeDataTypeString
|
||||
NodeDataTypeExtendedData
|
||||
)
|
||||
|
||||
const (
|
||||
NodeDataTypeMask uint32 = 0xc0_00_00_00
|
||||
NodeDataChildMask uint32 = 0x00_00_00_ff
|
||||
NodeDataStringIndexMask uint32 = 0x00_ff_ff_ff
|
||||
)
|
||||
|
||||
const (
|
||||
SyntaxKindNodeList uint32 = 1<<32 - 1
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderOffsetMetadata = iota * 4
|
||||
HeaderOffsetStringOffsets
|
||||
HeaderOffsetStringData
|
||||
HeaderOffsetExtendedData
|
||||
HeaderOffsetNodes
|
||||
HeaderSize
|
||||
)
|
||||
|
||||
const (
|
||||
ProtocolVersion uint8 = 1
|
||||
)
|
||||
|
||||
// Source File Binary Format
|
||||
// =========================
|
||||
//
|
||||
// The following defines a protocol for serializing TypeScript SourceFile objects to a compact binary format. All integer
|
||||
// values are little-endian.
|
||||
//
|
||||
// Overview
|
||||
// --------
|
||||
//
|
||||
// The format comprises six sections:
|
||||
//
|
||||
// | Section | Length | Description |
|
||||
// | ------------------ | ------------------ | ---------------------------------------------------------------------------------------- |
|
||||
// | Header | 20 bytes | Contains byte offsets to the start of each section. |
|
||||
// | String offsets | 8 bytes per string | Pairs of starting byte offsets and ending byte offsets into the **string data** section. |
|
||||
// | String data | variable | UTF-8 encoded string data. |
|
||||
// | Extended node data | variable | Extra data for some kinds of nodes. |
|
||||
// | Nodes | 24 bytes per node | Defines the AST structure of the file, with references to strings and extended data. |
|
||||
//
|
||||
// Header (20 bytes)
|
||||
// -----------------
|
||||
//
|
||||
// The header contains the following fields:
|
||||
//
|
||||
// | Byte offset | Type | Field |
|
||||
// | ----------- | ------ | ----------------------------------------- |
|
||||
// | 0 | uint8 | Protocol version |
|
||||
// | 1-4 | | Reserved |
|
||||
// | 4-8 | uint32 | Byte offset to string offsets section |
|
||||
// | 8-12 | uint32 | Byte offset to string data section |
|
||||
// | 12-16 | uint32 | Byte offset to extended node data section |
|
||||
// | 16-20 | uint32 | Byte offset to nodes section |
|
||||
//
|
||||
// String offsets (8 bytes per string)
|
||||
// -----------------------------------
|
||||
//
|
||||
// Each string offset entry consists of two 4-byte unsigned integers, representing the start and end byte offsets into the
|
||||
// **string data** section.
|
||||
//
|
||||
// String data (variable)
|
||||
// ----------------------
|
||||
//
|
||||
// The string data section contains UTF-8 encoded string data. In typical cases, the entirety of the string data is the
|
||||
// source file text, and individual nodes with string properties reference their positional slice of the file text. In
|
||||
// cases where a node's string property is not equal to the slice of file text at its position, the unique string is
|
||||
// appended to the string data section after the file text.
|
||||
//
|
||||
// Extended node data (variable)
|
||||
// -----------------------------
|
||||
//
|
||||
// The extended node data section contains additional data for specific node types. The length and meaning of each entry
|
||||
// is defined by the node type.
|
||||
//
|
||||
// Currently, the only node types that use this section are `TemplateHead`, `TemplateMiddle`, `TemplateTail`, and
|
||||
// `SourceFile`. The extended data format for the first three is:
|
||||
//
|
||||
// | Byte offset | Type | Field |
|
||||
// | ----------- | ------ | ------------------------------------------------ |
|
||||
// | 0-4 | uint32 | Index of `text` in the string offsets section |
|
||||
// | 4-8 | uint32 | Index of `rawText` in the string offsets section |
|
||||
// | 8-12 | uint32 | Value of `templateFlags` |
|
||||
//
|
||||
// and for `SourceFile` is:
|
||||
//
|
||||
// | Byte offset | Type | Field |
|
||||
// | ----------- | ------ | ------------------------------------------------- |
|
||||
// | 0-4 | uint32 | Index of `text` in the string offsets section |
|
||||
// | 4-8 | uint32 | Index of `fileName` in the string offsets section |
|
||||
// | 8-12 | uint32 | Index of `id` in the string offsets section |
|
||||
//
|
||||
// Nodes (24 bytes per node)
|
||||
// -------------------------
|
||||
//
|
||||
// The nodes section contains the AST structure of the file. Nodes are represented in a flat array in source order,
|
||||
// heavily inspired by https://marvinh.dev/blog/speeding-up-javascript-ecosystem-part-11/. Each node has the following
|
||||
// structure:
|
||||
//
|
||||
// | Byte offset | Type | Field |
|
||||
// | ----------- | ------ | -------------------------- |
|
||||
// | 0-4 | uint32 | Kind |
|
||||
// | 4-8 | uint32 | Pos |
|
||||
// | 8-12 | uint32 | End |
|
||||
// | 12-16 | uint32 | Node index of next sibling |
|
||||
// | 16-20 | uint32 | Node index of parent |
|
||||
// | 20-24 | | Node data |
|
||||
//
|
||||
// The first 24 bytes of the nodes section are zeros representing a nil node, such that nodes without a parent or next
|
||||
// sibling can unambiuously use `0` for those indices.
|
||||
//
|
||||
// NodeLists are represented as normal nodes with the special `kind` value `0xff_ff_ff_ff`. They are considered the parent
|
||||
// of their contents in the encoded format. A client reconstructing an AST similar to TypeScript's internal representation
|
||||
// should instead set the `parent` pointers of a NodeList's children to the NodeList's parent. A NodeList's `data` field
|
||||
// is the uint32 length of the list, and does not use one of the data types described below.
|
||||
//
|
||||
// For node types other than NodeList, the node data field encodes one of the following, determined by the first 2 bits of
|
||||
// the field:
|
||||
//
|
||||
// | Value | Data type | Description |
|
||||
// | ----- | --------- | ------------------------------------------------------------------------------------ |
|
||||
// | 0b00 | Children | Disambiguates which named properties of the node its children should be assigned to. |
|
||||
// | 0b01 | String | The index of the node's string property in the **string offsets** section. |
|
||||
// | 0b10 | Extended | The byte offset of the node's extended data into the **extended node data** section. |
|
||||
// | 0b11 | Reserved | Reserved for future use. |
|
||||
//
|
||||
// In all node data types, the remaining 6 bits of the first byte are used to encode booleans specific to the node type:
|
||||
//
|
||||
// | Node type | Bits 2-5 | Bit 1 | Bit 0 |
|
||||
// | ------------------------- | -------- | ------------- | ------------------------------- |
|
||||
// | `ImportSpecifier` | | | `isTypeOnly` |
|
||||
// | `ImportClause` | | | `isTypeOnly` |
|
||||
// | `ExportSpecifier` | | | `isTypeOnly` |
|
||||
// | `ImportEqualsDeclaration` | | | `isTypeOnly` |
|
||||
// | `ExportDeclaration` | | | `isTypeOnly` |
|
||||
// | `ImportTypeNode` | | | `isTypeOf` |
|
||||
// | `ExportAssignment` | | | `isExportEquals` |
|
||||
// | `Block` | | | `multiline` |
|
||||
// | `ArrayLiteralExpression` | | | `multiline` |
|
||||
// | `ObjectLiteralExpression` | | | `multiline` |
|
||||
// | `JsxText` | | | `containsOnlyTriviaWhiteSpaces` |
|
||||
// | `JSDocTypeLiteral` | | | `isArrayType` |
|
||||
// | `JsDocPropertyTag` | | `isNameFirst` | `isBracketed` |
|
||||
// | `JsDocParameterTag` | | `isNameFirst` | `isBracketed` |
|
||||
// | `VariableDeclarationList` | | is `const` | is `let` |
|
||||
// | `ImportAttributes` | | is `assert` | `multiline` |
|
||||
//
|
||||
// The remaining 3 bytes of the node data field vary by data type:
|
||||
//
|
||||
// ### Children (0b00)
|
||||
//
|
||||
// If a node has fewer children than its type allows, additional data is needed to determine which properties the children
|
||||
// correspond to. The last byte of the 4-byte data field is a bitmask representing the child properties of the node type,
|
||||
// in visitor order, where `1` indicates that the child at that property is present and `0` indicates that the property is
|
||||
// nil. For example, a `MethodDeclaration` has the following child properties:
|
||||
//
|
||||
// | Property name | Bit position |
|
||||
// | -------------- | ------------ |
|
||||
// | modifiers | 0 |
|
||||
// | asteriskToken | 1 |
|
||||
// | name | 2 |
|
||||
// | postfixToken | 3 |
|
||||
// | typeParameters | 4 |
|
||||
// | parameters | 5 |
|
||||
// | returnType | 6 |
|
||||
// | body | 7 |
|
||||
//
|
||||
// A bitmask with value `0b01100101` would indicate that the next four direct descendants (i.e., node records that have a
|
||||
// `parent` set to the node index of the `MethodDeclaration`) of the node are its `modifiers`, `name`, `parameters`, and
|
||||
// `body` properties, in that order. The remaining properties are nil. (To reconstruct the node with named properties, the
|
||||
// client must consult a static table of each node type's child property names.)
|
||||
//
|
||||
// The bitmask may be zero for node types that can only have a single child, since no disambiguation is needed.
|
||||
// Additionally, the children data type may be used for nodes that can never have children, but do not require other
|
||||
// data types.
|
||||
//
|
||||
// ### String (0b01)
|
||||
//
|
||||
// The string data type is used for nodes with a single string property. (Currently, the name of that property is always
|
||||
// `text`.) The last three bytes of the 4-byte data field form a single 24-bit unsigned integer (i.e.,
|
||||
// `uint32(0x00_ff_ff_ff & node.data)`) _N_ that is an index into the **string offsets** section. The *N*th 32-bit
|
||||
// unsigned integer in the **string offsets** section is the byte offset of the start of the string in the **string data**
|
||||
// section, and the *N+1*th 32-bit unsigned integer is the byte offset of the end of the string in the
|
||||
// **string data** section.
|
||||
//
|
||||
// ### Extended (0b10)
|
||||
//
|
||||
// The extended data type is used for nodes with properties that don't fit into either the children or string data types.
|
||||
// The last three bytes of the 4-byte data field form a single 24-bit unsigned integer (i.e.,
|
||||
// `uint32(0x00_ff_ff_ff & node.data)`) _N_ that is a byte offset into the **extended node data** section. The length and
|
||||
// meaning of the data at that offset is defined by the node type. See the **Extended node data** section for details on
|
||||
// the format of the extended data for specific node types.
|
||||
|
||||
func EncodeSourceFile(sourceFile *ast.SourceFile, id string) ([]byte, error) {
|
||||
var parentIndex, nodeCount, prevIndex uint32
|
||||
var extendedData []byte
|
||||
strs := newStringTable(sourceFile.Text(), sourceFile.TextCount)
|
||||
nodes := make([]byte, 0, (sourceFile.NodeCount+1)*NodeSize)
|
||||
|
||||
visitor := &ast.NodeVisitor{
|
||||
Hooks: ast.NodeVisitorHooks{
|
||||
VisitNodes: func(nodeList *ast.NodeList, visitor *ast.NodeVisitor) *ast.NodeList {
|
||||
if nodeList == nil || len(nodeList.Nodes) == 0 {
|
||||
return nodeList
|
||||
}
|
||||
|
||||
nodeCount++
|
||||
if prevIndex != 0 {
|
||||
// this is the next sibling of `prevNode`
|
||||
b0, b1, b2, b3 := uint8(nodeCount), uint8(nodeCount>>8), uint8(nodeCount>>16), uint8(nodeCount>>24)
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+0] = b0
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+1] = b1
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+2] = b2
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+3] = b3
|
||||
}
|
||||
|
||||
nodes = appendUint32s(nodes, SyntaxKindNodeList, uint32(nodeList.Pos()), uint32(nodeList.End()), 0, parentIndex, uint32(len(nodeList.Nodes)))
|
||||
|
||||
saveParentIndex := parentIndex
|
||||
|
||||
currentIndex := nodeCount
|
||||
prevIndex = 0
|
||||
parentIndex = currentIndex
|
||||
visitor.VisitSlice(nodeList.Nodes)
|
||||
prevIndex = currentIndex
|
||||
parentIndex = saveParentIndex
|
||||
|
||||
return nodeList
|
||||
},
|
||||
VisitModifiers: func(modifiers *ast.ModifierList, visitor *ast.NodeVisitor) *ast.ModifierList {
|
||||
if modifiers != nil && len(modifiers.Nodes) > 0 {
|
||||
visitor.Hooks.VisitNodes(&modifiers.NodeList, visitor)
|
||||
}
|
||||
return modifiers
|
||||
},
|
||||
},
|
||||
}
|
||||
visitor.Visit = func(node *ast.Node) *ast.Node {
|
||||
nodeCount++
|
||||
if prevIndex != 0 {
|
||||
// this is the next sibling of `prevNode`
|
||||
b0, b1, b2, b3 := uint8(nodeCount), uint8(nodeCount>>8), uint8(nodeCount>>16), uint8(nodeCount>>24)
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+0] = b0
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+1] = b1
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+2] = b2
|
||||
nodes[prevIndex*NodeSize+NodeOffsetNext+3] = b3
|
||||
}
|
||||
|
||||
nodes = appendUint32s(nodes, uint32(node.Kind), uint32(node.Pos()), uint32(node.End()), 0, parentIndex, getNodeData(node, strs, &extendedData))
|
||||
|
||||
saveParentIndex := parentIndex
|
||||
|
||||
currentIndex := nodeCount
|
||||
prevIndex = 0
|
||||
parentIndex = currentIndex
|
||||
visitor.VisitEachChild(node)
|
||||
prevIndex = currentIndex
|
||||
parentIndex = saveParentIndex
|
||||
return node
|
||||
}
|
||||
|
||||
nodes = appendUint32s(nodes, 0, 0, 0, 0, 0, 0)
|
||||
|
||||
nodeCount++
|
||||
parentIndex++
|
||||
nodes = appendUint32s(nodes, uint32(sourceFile.Kind), uint32(sourceFile.Pos()), uint32(sourceFile.End()), 0, 0, getSourceFileData(sourceFile, id, strs, &extendedData))
|
||||
|
||||
visitor.VisitEachChild(sourceFile.AsNode())
|
||||
|
||||
metadata := uint32(ProtocolVersion) << 24
|
||||
offsetStringTableOffsets := HeaderSize
|
||||
offsetStringTableData := HeaderSize + len(strs.offsets)*4
|
||||
offsetExtendedData := offsetStringTableData + strs.stringLength()
|
||||
offsetNodes := offsetExtendedData + len(extendedData)
|
||||
|
||||
header := []uint32{
|
||||
metadata,
|
||||
uint32(offsetStringTableOffsets),
|
||||
uint32(offsetStringTableData),
|
||||
uint32(offsetExtendedData),
|
||||
uint32(offsetNodes),
|
||||
}
|
||||
|
||||
var headerBytes, strsBytes []byte
|
||||
headerBytes = appendUint32s(nil, header...)
|
||||
strsBytes = strs.encode()
|
||||
|
||||
return slices.Concat(
|
||||
headerBytes,
|
||||
strsBytes,
|
||||
extendedData,
|
||||
nodes,
|
||||
), nil
|
||||
}
|
||||
|
||||
func appendUint32s(buf []byte, values ...uint32) []byte {
|
||||
for _, value := range values {
|
||||
var err error
|
||||
if buf, err = binary.Append(buf, binary.LittleEndian, value); err != nil {
|
||||
// The only error binary.Append can return is for values that are not fixed-size.
|
||||
// This can never happen here, since we are always appending uint32.
|
||||
panic(fmt.Sprintf("failed to append uint32: %v", err))
|
||||
}
|
||||
}
|
||||
return buf
|
||||
}
|
||||
|
||||
func getSourceFileData(sourceFile *ast.SourceFile, id string, strs *stringTable, extendedData *[]byte) uint32 {
|
||||
t := NodeDataTypeExtendedData
|
||||
extendedDataOffset := len(*extendedData)
|
||||
textIndex := strs.add(sourceFile.Text(), sourceFile.Kind, sourceFile.Pos(), sourceFile.End())
|
||||
fileNameIndex := strs.add(sourceFile.FileName(), 0, 0, 0)
|
||||
idIndex := strs.add(id, 0, 0, 0)
|
||||
*extendedData = appendUint32s(*extendedData, textIndex, fileNameIndex, idIndex)
|
||||
return t | uint32(extendedDataOffset)
|
||||
}
|
||||
|
||||
func getNodeData(node *ast.Node, strs *stringTable, extendedData *[]byte) uint32 {
|
||||
t := getNodeDataType(node)
|
||||
switch t {
|
||||
case NodeDataTypeChildren:
|
||||
return t | getNodeDefinedData(node) | uint32(getChildrenPropertyMask(node))
|
||||
case NodeDataTypeString:
|
||||
return t | getNodeDefinedData(node) | recordNodeStrings(node, strs)
|
||||
case NodeDataTypeExtendedData:
|
||||
return t | getNodeDefinedData(node) | recordExtendedData(node, strs, extendedData)
|
||||
default:
|
||||
panic("unreachable")
|
||||
}
|
||||
}
|
||||
|
||||
func getNodeDataType(node *ast.Node) uint32 {
|
||||
switch node.Kind {
|
||||
case ast.KindJsxText,
|
||||
ast.KindIdentifier,
|
||||
ast.KindPrivateIdentifier,
|
||||
ast.KindStringLiteral,
|
||||
ast.KindNumericLiteral,
|
||||
ast.KindBigIntLiteral,
|
||||
ast.KindRegularExpressionLiteral,
|
||||
ast.KindNoSubstitutionTemplateLiteral,
|
||||
ast.KindJSDocText:
|
||||
return NodeDataTypeString
|
||||
case ast.KindTemplateHead,
|
||||
ast.KindTemplateMiddle,
|
||||
ast.KindTemplateTail,
|
||||
ast.KindSourceFile:
|
||||
return NodeDataTypeExtendedData
|
||||
default:
|
||||
return NodeDataTypeChildren
|
||||
}
|
||||
}
|
||||
|
||||
// getChildrenPropertyMask returns a mask of which children properties are present in the node.
|
||||
// It is defined for node kinds that have more than one property that is a pointer to a child node.
|
||||
// Example: QualifiedName has two children properties: Left and Right, which are visited in that order.
|
||||
// result&1 is non-zero if Left is present, and result&2 is non-zero if Right is present. If the client
|
||||
// knows that QualifiedName has properties ["Left", "Right"] and sees an encoded node with only one
|
||||
// child, it can use the mask to determine which property is present.
|
||||
func getChildrenPropertyMask(node *ast.Node) uint8 {
|
||||
switch node.Kind {
|
||||
case ast.KindQualifiedName:
|
||||
n := node.AsQualifiedName()
|
||||
return (boolToByte(n.Left != nil) << 0) | (boolToByte(n.Right != nil) << 1)
|
||||
case ast.KindTypeParameter:
|
||||
n := node.AsTypeParameter()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Constraint != nil) << 2) | (boolToByte(n.DefaultType != nil) << 3)
|
||||
case ast.KindIfStatement:
|
||||
n := node.AsIfStatement()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.ThenStatement != nil) << 1) | (boolToByte(n.ElseStatement != nil) << 2)
|
||||
case ast.KindDoStatement:
|
||||
n := node.AsDoStatement()
|
||||
return (boolToByte(n.Statement != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
|
||||
case ast.KindWhileStatement:
|
||||
n := node.AsWhileStatement()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
|
||||
case ast.KindForStatement:
|
||||
n := node.AsForStatement()
|
||||
return (boolToByte(n.Initializer != nil) << 0) | (boolToByte(n.Condition != nil) << 1) | (boolToByte(n.Incrementor != nil) << 2) | (boolToByte(n.Statement != nil) << 3)
|
||||
case ast.KindForInStatement, ast.KindForOfStatement:
|
||||
n := node.AsForInOrOfStatement()
|
||||
return (boolToByte(n.AwaitModifier != nil) << 0) | (boolToByte(n.Initializer != nil) << 1) | (boolToByte(n.Expression != nil) << 2) | (boolToByte(n.Statement != nil) << 3)
|
||||
case ast.KindWithStatement:
|
||||
n := node.AsWithStatement()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
|
||||
case ast.KindSwitchStatement:
|
||||
n := node.AsSwitchStatement()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.CaseBlock != nil) << 1)
|
||||
case ast.KindCaseClause, ast.KindDefaultClause:
|
||||
n := node.AsCaseOrDefaultClause()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Statements != nil) << 1)
|
||||
case ast.KindTryStatement:
|
||||
n := node.AsTryStatement()
|
||||
return (boolToByte(n.TryBlock != nil) << 0) | (boolToByte(n.CatchClause != nil) << 1) | (boolToByte(n.FinallyBlock != nil) << 2)
|
||||
case ast.KindCatchClause:
|
||||
n := node.AsCatchClause()
|
||||
return (boolToByte(n.VariableDeclaration != nil) << 0) | (boolToByte(n.Block != nil) << 1)
|
||||
case ast.KindLabeledStatement:
|
||||
n := node.AsLabeledStatement()
|
||||
return (boolToByte(n.Label != nil) << 0) | (boolToByte(n.Statement != nil) << 1)
|
||||
case ast.KindVariableStatement:
|
||||
n := node.AsVariableStatement()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.DeclarationList != nil) << 1)
|
||||
case ast.KindVariableDeclaration:
|
||||
n := node.AsVariableDeclaration()
|
||||
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.ExclamationToken != nil) << 1) | (boolToByte(n.Type != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
|
||||
case ast.KindParameter:
|
||||
n := node.AsParameterDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.DotDotDotToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.QuestionToken != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Initializer != nil) << 5)
|
||||
case ast.KindBindingElement:
|
||||
n := node.AsBindingElement()
|
||||
return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.PropertyName != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
|
||||
case ast.KindFunctionDeclaration:
|
||||
n := node.AsFunctionDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
|
||||
case ast.KindInterfaceDeclaration:
|
||||
n := node.AsInterfaceDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
|
||||
case ast.KindTypeAliasDeclaration:
|
||||
n := node.AsTypeAliasDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Type != nil) << 3)
|
||||
case ast.KindEnumMember:
|
||||
n := node.AsEnumMember()
|
||||
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Initializer != nil) << 1)
|
||||
case ast.KindEnumDeclaration:
|
||||
n := node.AsEnumDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Members != nil) << 2)
|
||||
case ast.KindModuleDeclaration:
|
||||
n := node.AsModuleDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.Body != nil) << 2)
|
||||
case ast.KindImportEqualsDeclaration:
|
||||
n := node.AsImportEqualsDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.ModuleReference != nil) << 2)
|
||||
case ast.KindImportDeclaration, ast.KindJSImportDeclaration:
|
||||
n := node.AsImportDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.ImportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3)
|
||||
case ast.KindImportSpecifier:
|
||||
n := node.AsImportSpecifier()
|
||||
return (boolToByte(n.PropertyName != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
|
||||
case ast.KindImportClause:
|
||||
n := node.AsImportClause()
|
||||
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.NamedBindings != nil) << 1)
|
||||
case ast.KindExportAssignment, ast.KindJSExportAssignment:
|
||||
n := node.AsExportAssignment()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
|
||||
case ast.KindNamespaceExportDeclaration:
|
||||
n := node.AsNamespaceExportDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
|
||||
case ast.KindExportDeclaration:
|
||||
n := node.AsExportDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.ExportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3)
|
||||
case ast.KindExportSpecifier:
|
||||
n := node.AsExportSpecifier()
|
||||
return (boolToByte(n.PropertyName != nil) << 0) | (boolToByte(n.Name() != nil) << 1)
|
||||
case ast.KindCallSignature:
|
||||
n := node.AsCallSignatureDeclaration()
|
||||
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
|
||||
case ast.KindConstructSignature:
|
||||
n := node.AsConstructSignatureDeclaration()
|
||||
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
|
||||
case ast.KindConstructor:
|
||||
n := node.AsConstructorDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Body != nil) << 4)
|
||||
case ast.KindGetAccessor:
|
||||
n := node.AsGetAccessorDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Parameters != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Body != nil) << 5)
|
||||
case ast.KindSetAccessor:
|
||||
n := node.AsSetAccessorDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Parameters != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Body != nil) << 5)
|
||||
case ast.KindIndexSignature:
|
||||
n := node.AsIndexSignatureDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
|
||||
case ast.KindMethodSignature:
|
||||
n := node.AsMethodSignatureDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5)
|
||||
case ast.KindMethodDeclaration:
|
||||
n := node.AsMethodDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.PostfixToken != nil) << 3) | (boolToByte(n.TypeParameters != nil) << 4) | (boolToByte(n.Parameters != nil) << 5) | (boolToByte(n.Type != nil) << 6) | (boolToByte(n.Body != nil) << 7)
|
||||
case ast.KindPropertySignature:
|
||||
n := node.AsPropertySignatureDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Initializer != nil) << 4)
|
||||
case ast.KindPropertyDeclaration:
|
||||
n := node.AsPropertyDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.Initializer != nil) << 4)
|
||||
case ast.KindBinaryExpression:
|
||||
n := node.AsBinaryExpression()
|
||||
return (boolToByte(n.Left != nil) << 0) | (boolToByte(n.OperatorToken != nil) << 1) | (boolToByte(n.Right != nil) << 2)
|
||||
case ast.KindYieldExpression:
|
||||
n := node.AsYieldExpression()
|
||||
return (boolToByte(n.AsteriskToken != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
|
||||
case ast.KindArrowFunction:
|
||||
n := node.AsArrowFunction()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3) | (boolToByte(n.EqualsGreaterThanToken != nil) << 4) | (boolToByte(n.Body != nil) << 5)
|
||||
case ast.KindFunctionExpression:
|
||||
n := node.AsFunctionExpression()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.AsteriskToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.TypeParameters != nil) << 3) | (boolToByte(n.Parameters != nil) << 4) | (boolToByte(n.Type != nil) << 5) | (boolToByte(n.Body != nil) << 6)
|
||||
case ast.KindAsExpression:
|
||||
n := node.AsAsExpression()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Type != nil) << 1)
|
||||
case ast.KindSatisfiesExpression:
|
||||
n := node.AsSatisfiesExpression()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Type != nil) << 1)
|
||||
case ast.KindConditionalExpression:
|
||||
n := node.AsConditionalExpression()
|
||||
return (boolToByte(n.Condition != nil) << 0) | (boolToByte(n.QuestionToken != nil) << 1) | (boolToByte(n.WhenTrue != nil) << 2) | (boolToByte(n.ColonToken != nil) << 3) | (boolToByte(n.WhenFalse != nil) << 4)
|
||||
case ast.KindPropertyAccessExpression:
|
||||
n := node.AsPropertyAccessExpression()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.Name() != nil) << 2)
|
||||
case ast.KindElementAccessExpression:
|
||||
n := node.AsElementAccessExpression()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.ArgumentExpression != nil) << 2)
|
||||
case ast.KindCallExpression:
|
||||
n := node.AsCallExpression()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.TypeArguments != nil) << 2) | (boolToByte(n.Arguments != nil) << 3)
|
||||
case ast.KindNewExpression:
|
||||
n := node.AsNewExpression()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Arguments != nil) << 2)
|
||||
case ast.KindTemplateExpression:
|
||||
n := node.AsTemplateExpression()
|
||||
return (boolToByte(n.Head != nil) << 0) | (boolToByte(n.TemplateSpans != nil) << 1)
|
||||
case ast.KindTemplateSpan:
|
||||
n := node.AsTemplateSpan()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.Literal != nil) << 1)
|
||||
case ast.KindTaggedTemplateExpression:
|
||||
n := node.AsTaggedTemplateExpression()
|
||||
return (boolToByte(n.Tag != nil) << 0) | (boolToByte(n.QuestionDotToken != nil) << 1) | (boolToByte(n.TypeArguments != nil) << 2) | (boolToByte(n.Template != nil) << 3)
|
||||
case ast.KindPropertyAssignment:
|
||||
n := node.AsPropertyAssignment()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.Initializer != nil) << 3)
|
||||
case ast.KindShorthandPropertyAssignment:
|
||||
n := node.AsShorthandPropertyAssignment()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.PostfixToken != nil) << 2) | (boolToByte(n.EqualsToken != nil) << 3) | (boolToByte(n.ObjectAssignmentInitializer != nil) << 4)
|
||||
case ast.KindTypeAssertionExpression:
|
||||
n := node.AsTypeAssertion()
|
||||
return (boolToByte(n.Type != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
|
||||
case ast.KindConditionalType:
|
||||
n := node.AsConditionalTypeNode()
|
||||
return (boolToByte(n.CheckType != nil) << 0) | (boolToByte(n.ExtendsType != nil) << 1) | (boolToByte(n.TrueType != nil) << 2) | (boolToByte(n.FalseType != nil) << 3)
|
||||
case ast.KindIndexedAccessType:
|
||||
n := node.AsIndexedAccessTypeNode()
|
||||
return (boolToByte(n.ObjectType != nil) << 0) | (boolToByte(n.IndexType != nil) << 1)
|
||||
case ast.KindTypeReference:
|
||||
n := node.AsTypeReferenceNode()
|
||||
return (boolToByte(n.TypeName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
|
||||
case ast.KindExpressionWithTypeArguments:
|
||||
n := node.AsExpressionWithTypeArguments()
|
||||
return (boolToByte(n.Expression != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
|
||||
case ast.KindTypePredicate:
|
||||
n := node.AsTypePredicateNode()
|
||||
return (boolToByte(n.AssertsModifier != nil) << 0) | (boolToByte(n.ParameterName != nil) << 1) | (boolToByte(n.Type != nil) << 2)
|
||||
case ast.KindImportType:
|
||||
n := node.AsImportTypeNode()
|
||||
return (boolToByte(n.Argument != nil) << 0) | (boolToByte(n.Attributes != nil) << 1) | (boolToByte(n.Qualifier != nil) << 2) | (boolToByte(n.TypeArguments != nil) << 3)
|
||||
case ast.KindImportAttribute:
|
||||
n := node.AsImportAttribute()
|
||||
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Value != nil) << 1)
|
||||
case ast.KindTypeQuery:
|
||||
n := node.AsTypeQueryNode()
|
||||
return (boolToByte(n.ExprName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1)
|
||||
case ast.KindMappedType:
|
||||
n := node.AsMappedTypeNode()
|
||||
return (boolToByte(n.ReadonlyToken != nil) << 0) | (boolToByte(n.TypeParameter != nil) << 1) | (boolToByte(n.NameType != nil) << 2) | (boolToByte(n.QuestionToken != nil) << 3) | (boolToByte(n.Type != nil) << 4) | (boolToByte(n.Members != nil) << 5)
|
||||
case ast.KindNamedTupleMember:
|
||||
n := node.AsNamedTupleMember()
|
||||
return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.QuestionToken != nil) << 2) | (boolToByte(n.Type != nil) << 3)
|
||||
case ast.KindFunctionType:
|
||||
n := node.AsFunctionTypeNode()
|
||||
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
|
||||
case ast.KindConstructorType:
|
||||
n := node.AsConstructorTypeNode()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.TypeParameters != nil) << 1) | (boolToByte(n.Parameters != nil) << 2) | (boolToByte(n.Type != nil) << 3)
|
||||
case ast.KindTemplateLiteralType:
|
||||
n := node.AsTemplateLiteralTypeNode()
|
||||
return (boolToByte(n.Head != nil) << 0) | (boolToByte(n.TemplateSpans != nil) << 1)
|
||||
case ast.KindTemplateLiteralTypeSpan:
|
||||
n := node.AsTemplateLiteralTypeSpan()
|
||||
return (boolToByte(n.Type != nil) << 0) | (boolToByte(n.Literal != nil) << 1)
|
||||
case ast.KindJsxElement:
|
||||
n := node.AsJsxElement()
|
||||
return (boolToByte(n.OpeningElement != nil) << 0) | (boolToByte(n.Children != nil) << 1) | (boolToByte(n.ClosingElement != nil) << 2)
|
||||
case ast.KindJsxNamespacedName:
|
||||
n := node.AsJsxNamespacedName()
|
||||
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Namespace != nil) << 1)
|
||||
case ast.KindJsxOpeningElement:
|
||||
n := node.AsJsxOpeningElement()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Attributes != nil) << 2)
|
||||
case ast.KindJsxSelfClosingElement:
|
||||
n := node.AsJsxSelfClosingElement()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeArguments != nil) << 1) | (boolToByte(n.Attributes != nil) << 2)
|
||||
case ast.KindJsxFragment:
|
||||
n := node.AsJsxFragment()
|
||||
return (boolToByte(n.OpeningFragment != nil) << 0) | (boolToByte(n.Children != nil) << 1) | (boolToByte(n.ClosingFragment != nil) << 2)
|
||||
case ast.KindJsxAttribute:
|
||||
n := node.AsJsxAttribute()
|
||||
return (boolToByte(n.Name() != nil) << 0) | (boolToByte(n.Initializer != nil) << 1)
|
||||
case ast.KindJsxExpression:
|
||||
n := node.AsJsxExpression()
|
||||
return (boolToByte(n.DotDotDotToken != nil) << 0) | (boolToByte(n.Expression != nil) << 1)
|
||||
case ast.KindJSDoc:
|
||||
n := node.AsJSDoc()
|
||||
return (boolToByte(n.Comment != nil) << 0) | (boolToByte(n.Tags != nil) << 1)
|
||||
case ast.KindJSDocTypeTag:
|
||||
n := node.AsJSDocTypeTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocTag:
|
||||
n := node.AsJSDocUnknownTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
|
||||
case ast.KindJSDocTemplateTag:
|
||||
n := node.AsJSDocTemplateTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Constraint != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
|
||||
case ast.KindJSDocReturnTag:
|
||||
n := node.AsJSDocReturnTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocPublicTag:
|
||||
n := node.AsJSDocPublicTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
|
||||
case ast.KindJSDocPrivateTag:
|
||||
n := node.AsJSDocPrivateTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
|
||||
case ast.KindJSDocProtectedTag:
|
||||
n := node.AsJSDocProtectedTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
|
||||
case ast.KindJSDocReadonlyTag:
|
||||
n := node.AsJSDocReadonlyTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
|
||||
case ast.KindJSDocOverrideTag:
|
||||
n := node.AsJSDocOverrideTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
|
||||
case ast.KindJSDocDeprecatedTag:
|
||||
n := node.AsJSDocDeprecatedTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Comment != nil) << 1)
|
||||
case ast.KindJSDocSeeTag:
|
||||
n := node.AsJSDocSeeTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.NameExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocImplementsTag:
|
||||
n := node.AsJSDocImplementsTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ClassName != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocAugmentsTag:
|
||||
n := node.AsJSDocAugmentsTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ClassName != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocSatisfiesTag:
|
||||
n := node.AsJSDocSatisfiesTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocThisTag:
|
||||
n := node.AsJSDocThisTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocImportTag:
|
||||
n := node.AsJSDocImportTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.ImportClause != nil) << 1) | (boolToByte(n.ModuleSpecifier != nil) << 2) | (boolToByte(n.Attributes != nil) << 3) | (boolToByte(n.Comment != nil) << 4)
|
||||
case ast.KindJSDocCallbackTag:
|
||||
n := node.AsJSDocCallbackTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.FullName != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
|
||||
case ast.KindJSDocOverloadTag:
|
||||
n := node.AsJSDocOverloadTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Comment != nil) << 2)
|
||||
case ast.KindJSDocTypedefTag:
|
||||
n := node.AsJSDocTypedefTag()
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
|
||||
case ast.KindJSDocSignature:
|
||||
n := node.AsJSDocSignature()
|
||||
return (boolToByte(n.TypeParameters != nil) << 0) | (boolToByte(n.Parameters != nil) << 1) | (boolToByte(n.Type != nil) << 2)
|
||||
case ast.KindClassStaticBlockDeclaration:
|
||||
n := node.AsClassStaticBlockDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Body != nil) << 1)
|
||||
case ast.KindClassDeclaration:
|
||||
n := node.AsClassDeclaration()
|
||||
return (boolToByte(n.Modifiers() != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeParameters != nil) << 2) | (boolToByte(n.HeritageClauses != nil) << 3) | (boolToByte(n.Members != nil) << 4)
|
||||
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
|
||||
n := node.AsJSDocParameterOrPropertyTag()
|
||||
if n.IsNameFirst {
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.Name() != nil) << 1) | (boolToByte(n.TypeExpression != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
|
||||
}
|
||||
return (boolToByte(n.TagName != nil) << 0) | (boolToByte(n.TypeExpression != nil) << 1) | (boolToByte(n.Name() != nil) << 2) | (boolToByte(n.Comment != nil) << 3)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func getNodeDefinedData(node *ast.Node) uint32 {
|
||||
switch node.Kind {
|
||||
case ast.KindJSDocTypeLiteral:
|
||||
n := node.AsJSDocTypeLiteral()
|
||||
return uint32(boolToByte(n.IsArrayType)) << 24
|
||||
case ast.KindImportSpecifier:
|
||||
n := node.AsImportSpecifier()
|
||||
return uint32(boolToByte(n.IsTypeOnly)) << 24
|
||||
case ast.KindImportClause:
|
||||
n := node.AsImportClause()
|
||||
return uint32(boolToByte(n.PhaseModifier == ast.KindTypeKeyword))<<24 | uint32(boolToByte(n.PhaseModifier == ast.KindDeferKeyword))<<25
|
||||
case ast.KindExportSpecifier:
|
||||
n := node.AsExportSpecifier()
|
||||
return uint32(boolToByte(n.IsTypeOnly)) << 24
|
||||
case ast.KindImportType:
|
||||
n := node.AsImportTypeNode()
|
||||
return uint32(boolToByte(n.IsTypeOf)) << 24
|
||||
case ast.KindImportEqualsDeclaration:
|
||||
n := node.AsImportEqualsDeclaration()
|
||||
return uint32(boolToByte(n.IsTypeOnly)) << 24
|
||||
case ast.KindExportAssignment:
|
||||
n := node.AsExportAssignment()
|
||||
return uint32(boolToByte(n.IsExportEquals)) << 24
|
||||
case ast.KindExportDeclaration:
|
||||
n := node.AsExportDeclaration()
|
||||
return uint32(boolToByte(n.IsTypeOnly)) << 24
|
||||
case ast.KindBlock:
|
||||
n := node.AsBlock()
|
||||
return uint32(boolToByte(n.Multiline)) << 24
|
||||
case ast.KindArrayLiteralExpression:
|
||||
n := node.AsArrayLiteralExpression()
|
||||
return uint32(boolToByte(n.MultiLine)) << 24
|
||||
case ast.KindObjectLiteralExpression:
|
||||
n := node.AsObjectLiteralExpression()
|
||||
return uint32(boolToByte(n.MultiLine)) << 24
|
||||
case ast.KindJSDocParameterTag, ast.KindJSDocPropertyTag:
|
||||
n := node.AsJSDocParameterOrPropertyTag()
|
||||
return uint32(boolToByte(n.IsBracketed))<<24 | uint32(boolToByte(n.IsNameFirst))<<25
|
||||
case ast.KindJsxText:
|
||||
n := node.AsJsxText()
|
||||
return uint32(boolToByte(n.ContainsOnlyTriviaWhiteSpaces)) << 24
|
||||
case ast.KindVariableDeclarationList:
|
||||
n := node.AsVariableDeclarationList()
|
||||
return uint32(n.Flags & (ast.NodeFlagsLet | ast.NodeFlagsConst) << 24)
|
||||
case ast.KindImportAttributes:
|
||||
n := node.AsImportAttributes()
|
||||
return uint32(boolToByte(n.MultiLine))<<24 | uint32(boolToByte(n.Token == ast.KindAssertKeyword))<<25
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func recordNodeStrings(node *ast.Node, strs *stringTable) uint32 {
|
||||
switch node.Kind {
|
||||
case ast.KindJsxText:
|
||||
return strs.add(node.AsJsxText().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindIdentifier:
|
||||
return strs.add(node.AsIdentifier().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindPrivateIdentifier:
|
||||
return strs.add(node.AsPrivateIdentifier().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindStringLiteral:
|
||||
return strs.add(node.AsStringLiteral().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindNumericLiteral:
|
||||
return strs.add(node.AsNumericLiteral().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindBigIntLiteral:
|
||||
return strs.add(node.AsBigIntLiteral().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindRegularExpressionLiteral:
|
||||
return strs.add(node.AsRegularExpressionLiteral().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindNoSubstitutionTemplateLiteral:
|
||||
return strs.add(node.AsNoSubstitutionTemplateLiteral().Text, node.Kind, node.Pos(), node.End())
|
||||
case ast.KindJSDocText:
|
||||
return strs.add(node.AsJSDocText().Text(), node.Kind, node.Pos(), node.End())
|
||||
default:
|
||||
panic(fmt.Sprintf("Unexpected node kind %v", node.Kind))
|
||||
}
|
||||
}
|
||||
|
||||
func recordExtendedData(node *ast.Node, strs *stringTable, extendedData *[]byte) uint32 {
|
||||
offset := uint32(len(*extendedData))
|
||||
var text, rawText string
|
||||
var templateFlags uint32
|
||||
switch node.Kind {
|
||||
case ast.KindTemplateTail:
|
||||
n := node.AsTemplateTail()
|
||||
text = n.Text
|
||||
rawText = n.RawText
|
||||
templateFlags = uint32(n.TemplateFlags)
|
||||
case ast.KindTemplateMiddle:
|
||||
n := node.AsTemplateMiddle()
|
||||
text = n.Text
|
||||
rawText = n.RawText
|
||||
templateFlags = uint32(n.TemplateFlags)
|
||||
case ast.KindTemplateHead:
|
||||
n := node.AsTemplateHead()
|
||||
text = n.Text
|
||||
rawText = n.RawText
|
||||
templateFlags = uint32(n.TemplateFlags)
|
||||
}
|
||||
textIndex := strs.add(text, node.Kind, node.Pos(), node.End())
|
||||
rawTextIndex := strs.add(rawText, node.Kind, node.Pos(), node.End())
|
||||
*extendedData = appendUint32s(*extendedData, textIndex, rawTextIndex, templateFlags)
|
||||
return offset
|
||||
}
|
||||
|
||||
func boolToByte(b bool) byte {
|
||||
if b {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
}
|
||||
@ -1,94 +0,0 @@
|
||||
package encoder_test
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/api/encoder"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/parser"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/repo"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/testutil/baseline"
|
||||
"gotest.tools/v3/assert"
|
||||
)
|
||||
|
||||
func TestEncodeSourceFile(t *testing.T) {
|
||||
t.Parallel()
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/test.ts",
|
||||
Path: "/test.ts",
|
||||
}, "import { bar } from \"bar\";\nexport function foo<T, U>(a: string, b: string): any {}\nfoo();", core.ScriptKindTS)
|
||||
t.Run("baseline", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
buf, err := encoder.EncodeSourceFile(sourceFile, "")
|
||||
assert.NilError(t, err)
|
||||
|
||||
str := formatEncodedSourceFile(buf)
|
||||
baseline.Run(t, "encodeSourceFile.txt", str, baseline.Options{
|
||||
Subfolder: "api",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkEncodeSourceFile(b *testing.B) {
|
||||
repo.SkipIfNoTypeScriptSubmodule(b)
|
||||
filePath := filepath.Join(repo.TypeScriptSubmodulePath, "src/compiler/checker.ts")
|
||||
fileContent, err := os.ReadFile(filePath)
|
||||
assert.NilError(b, err)
|
||||
sourceFile := parser.ParseSourceFile(ast.SourceFileParseOptions{
|
||||
FileName: "/checker.ts",
|
||||
Path: "/checker.ts",
|
||||
}, string(fileContent), core.ScriptKindTS)
|
||||
|
||||
for b.Loop() {
|
||||
_, err := encoder.EncodeSourceFile(sourceFile, "")
|
||||
assert.NilError(b, err)
|
||||
}
|
||||
}
|
||||
|
||||
func readUint32(buf []byte, offset int) uint32 {
|
||||
return binary.LittleEndian.Uint32(buf[offset : offset+4])
|
||||
}
|
||||
|
||||
func formatEncodedSourceFile(encoded []byte) string {
|
||||
var result strings.Builder
|
||||
var getIndent func(parentIndex uint32) string
|
||||
offsetNodes := readUint32(encoded, encoder.HeaderOffsetNodes)
|
||||
offsetStringOffsets := readUint32(encoded, encoder.HeaderOffsetStringOffsets)
|
||||
offsetStrings := readUint32(encoded, encoder.HeaderOffsetStringData)
|
||||
getIndent = func(parentIndex uint32) string {
|
||||
if parentIndex == 0 {
|
||||
return ""
|
||||
}
|
||||
return " " + getIndent(readUint32(encoded, int(offsetNodes)+int(parentIndex)*encoder.NodeSize+encoder.NodeOffsetParent))
|
||||
}
|
||||
j := 1
|
||||
for i := int(offsetNodes) + encoder.NodeSize; i < len(encoded); i += encoder.NodeSize {
|
||||
kind := readUint32(encoded, i+encoder.NodeOffsetKind)
|
||||
pos := readUint32(encoded, i+encoder.NodeOffsetPos)
|
||||
end := readUint32(encoded, i+encoder.NodeOffsetEnd)
|
||||
parentIndex := readUint32(encoded, i+encoder.NodeOffsetParent)
|
||||
result.WriteString(getIndent(parentIndex))
|
||||
if kind == encoder.SyntaxKindNodeList {
|
||||
result.WriteString("NodeList")
|
||||
} else {
|
||||
result.WriteString(ast.Kind(kind).String())
|
||||
}
|
||||
if ast.Kind(kind) == ast.KindIdentifier || ast.Kind(kind) == ast.KindStringLiteral {
|
||||
stringIndex := readUint32(encoded, i+encoder.NodeOffsetData) & encoder.NodeDataStringIndexMask
|
||||
strStart := readUint32(encoded, int(offsetStringOffsets+stringIndex*4))
|
||||
strEnd := readUint32(encoded, int(offsetStringOffsets+stringIndex*4)+4)
|
||||
str := string(encoded[offsetStrings+strStart : offsetStrings+strEnd])
|
||||
result.WriteString(fmt.Sprintf(" \"%s\"", str))
|
||||
}
|
||||
fmt.Fprintf(&result, " [%d, %d), i=%d, next=%d", pos, end, j, encoded[i+encoder.NodeOffsetNext])
|
||||
result.WriteString("\n")
|
||||
j++
|
||||
}
|
||||
return result.String()
|
||||
}
|
||||
@ -1,68 +0,0 @@
|
||||
package encoder
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
||||
)
|
||||
|
||||
type stringTable struct {
|
||||
fileText string
|
||||
otherStrings *strings.Builder
|
||||
// offsets are pos/end pairs
|
||||
offsets []uint32
|
||||
}
|
||||
|
||||
func newStringTable(fileText string, stringCount int) *stringTable {
|
||||
builder := &strings.Builder{}
|
||||
return &stringTable{
|
||||
fileText: fileText,
|
||||
otherStrings: builder,
|
||||
offsets: make([]uint32, 0, stringCount*2),
|
||||
}
|
||||
}
|
||||
|
||||
func (t *stringTable) add(text string, kind ast.Kind, pos int, end int) uint32 {
|
||||
index := uint32(len(t.offsets))
|
||||
if kind == ast.KindSourceFile {
|
||||
t.offsets = append(t.offsets, uint32(pos), uint32(end))
|
||||
return index
|
||||
}
|
||||
length := len(text)
|
||||
if end-pos > 0 {
|
||||
// pos includes leading trivia, but we can usually infer the actual start of the
|
||||
// string from the kind and end
|
||||
endOffset := 0
|
||||
if kind == ast.KindStringLiteral || kind == ast.KindTemplateTail || kind == ast.KindNoSubstitutionTemplateLiteral {
|
||||
endOffset = 1
|
||||
}
|
||||
end = end - endOffset
|
||||
start := end - length
|
||||
fileSlice := t.fileText[start:end]
|
||||
if fileSlice == text {
|
||||
t.offsets = append(t.offsets, uint32(start), uint32(end))
|
||||
return index
|
||||
}
|
||||
}
|
||||
// no exact match, so we need to add it to the string table
|
||||
offset := len(t.fileText) + t.otherStrings.Len()
|
||||
t.otherStrings.WriteString(text)
|
||||
t.offsets = append(t.offsets, uint32(offset), uint32(offset+length))
|
||||
return index
|
||||
}
|
||||
|
||||
func (t *stringTable) encode() []byte {
|
||||
result := make([]byte, 0, t.encodedLength())
|
||||
result = appendUint32s(result, t.offsets...)
|
||||
result = append(result, t.fileText...)
|
||||
result = append(result, t.otherStrings.String()...)
|
||||
return result
|
||||
}
|
||||
|
||||
func (t *stringTable) stringLength() int {
|
||||
return len(t.fileText) + t.otherStrings.Len()
|
||||
}
|
||||
|
||||
func (t *stringTable) encodedLength() int {
|
||||
return len(t.offsets)*4 + len(t.fileText) + t.otherStrings.Len()
|
||||
}
|
||||
@ -1,218 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/checker"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
|
||||
"github.com/go-json-experiment/json"
|
||||
"github.com/go-json-experiment/json/jsontext"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrInvalidRequest = errors.New("api: invalid request")
|
||||
ErrClientError = errors.New("api: client error")
|
||||
)
|
||||
|
||||
type Method string
|
||||
|
||||
type Handle[T any] string
|
||||
|
||||
const (
|
||||
handlePrefixProject = 'p'
|
||||
handlePrefixSymbol = 's'
|
||||
handlePrefixType = 't'
|
||||
handlePrefixFile = 'f'
|
||||
handlePrefixNode = 'n'
|
||||
)
|
||||
|
||||
func ProjectHandle(p *project.Project) Handle[project.Project] {
|
||||
return createHandle[project.Project](handlePrefixProject, p.Name())
|
||||
}
|
||||
|
||||
func SymbolHandle(symbol *ast.Symbol) Handle[ast.Symbol] {
|
||||
return createHandle[ast.Symbol](handlePrefixSymbol, ast.GetSymbolId(symbol))
|
||||
}
|
||||
|
||||
func TypeHandle(t *checker.Type) Handle[checker.Type] {
|
||||
return createHandle[checker.Type](handlePrefixType, t.Id())
|
||||
}
|
||||
|
||||
func FileHandle(file *ast.SourceFile) Handle[ast.SourceFile] {
|
||||
return createHandle[ast.SourceFile](handlePrefixFile, ast.GetNodeId(file.AsNode()))
|
||||
}
|
||||
|
||||
func NodeHandle(node *ast.Node) Handle[ast.Node] {
|
||||
fileHandle := FileHandle(ast.GetSourceFileOfNode(node))
|
||||
return Handle[ast.Node](fmt.Sprintf("%s.%d.%d", fileHandle, node.Pos(), node.Kind))
|
||||
}
|
||||
|
||||
func parseNodeHandle(handle Handle[ast.Node]) (Handle[ast.SourceFile], int, ast.Kind, error) {
|
||||
parts := strings.SplitN(string(handle), ".", 3)
|
||||
if len(parts) != 3 {
|
||||
return "", 0, 0, fmt.Errorf("invalid node handle %q", handle)
|
||||
}
|
||||
|
||||
fileHandle := Handle[ast.SourceFile](parts[0])
|
||||
pos, err := strconv.ParseInt(parts[1], 10, 32)
|
||||
if err != nil {
|
||||
return "", 0, 0, fmt.Errorf("invalid node handle %q: %w", handle, err)
|
||||
}
|
||||
kind, err := strconv.ParseInt(parts[2], 10, 16)
|
||||
if err != nil {
|
||||
return "", 0, 0, fmt.Errorf("invalid node handle %q: %w", handle, err)
|
||||
}
|
||||
return fileHandle, int(pos), ast.Kind(kind), nil
|
||||
}
|
||||
|
||||
func createHandle[T any](prefix rune, id any) Handle[T] {
|
||||
return Handle[T](fmt.Sprintf("%c%016x", prefix, id))
|
||||
}
|
||||
|
||||
const (
|
||||
MethodConfigure Method = "configure"
|
||||
MethodRelease Method = "release"
|
||||
|
||||
MethodParseConfigFile Method = "parseConfigFile"
|
||||
MethodLoadProject Method = "loadProject"
|
||||
MethodGetSymbolAtPosition Method = "getSymbolAtPosition"
|
||||
MethodGetSymbolsAtPositions Method = "getSymbolsAtPositions"
|
||||
MethodGetSymbolAtLocation Method = "getSymbolAtLocation"
|
||||
MethodGetSymbolsAtLocations Method = "getSymbolsAtLocations"
|
||||
MethodGetTypeOfSymbol Method = "getTypeOfSymbol"
|
||||
MethodGetTypesOfSymbols Method = "getTypesOfSymbols"
|
||||
MethodGetSourceFile Method = "getSourceFile"
|
||||
)
|
||||
|
||||
var unmarshalers = map[Method]func([]byte) (any, error){
|
||||
MethodRelease: unmarshallerFor[string],
|
||||
MethodParseConfigFile: unmarshallerFor[ParseConfigFileParams],
|
||||
MethodLoadProject: unmarshallerFor[LoadProjectParams],
|
||||
MethodGetSourceFile: unmarshallerFor[GetSourceFileParams],
|
||||
MethodGetSymbolAtPosition: unmarshallerFor[GetSymbolAtPositionParams],
|
||||
MethodGetSymbolsAtPositions: unmarshallerFor[GetSymbolsAtPositionsParams],
|
||||
MethodGetSymbolAtLocation: unmarshallerFor[GetSymbolAtLocationParams],
|
||||
MethodGetSymbolsAtLocations: unmarshallerFor[GetSymbolsAtLocationsParams],
|
||||
MethodGetTypeOfSymbol: unmarshallerFor[GetTypeOfSymbolParams],
|
||||
MethodGetTypesOfSymbols: unmarshallerFor[GetTypesOfSymbolsParams],
|
||||
}
|
||||
|
||||
type ConfigureParams struct {
|
||||
Callbacks []string `json:"callbacks"`
|
||||
LogFile string `json:"logFile"`
|
||||
}
|
||||
|
||||
type ParseConfigFileParams struct {
|
||||
FileName string `json:"fileName"`
|
||||
}
|
||||
|
||||
type ConfigFileResponse struct {
|
||||
FileNames []string `json:"fileNames"`
|
||||
Options *core.CompilerOptions `json:"options"`
|
||||
}
|
||||
|
||||
type LoadProjectParams struct {
|
||||
ConfigFileName string `json:"configFileName"`
|
||||
}
|
||||
|
||||
type ProjectResponse struct {
|
||||
Id Handle[project.Project] `json:"id"`
|
||||
ConfigFileName string `json:"configFileName"`
|
||||
RootFiles []string `json:"rootFiles"`
|
||||
CompilerOptions *core.CompilerOptions `json:"compilerOptions"`
|
||||
}
|
||||
|
||||
func NewProjectResponse(project *project.Project) *ProjectResponse {
|
||||
return &ProjectResponse{
|
||||
Id: ProjectHandle(project),
|
||||
ConfigFileName: project.Name(),
|
||||
RootFiles: project.CommandLine.FileNames(),
|
||||
CompilerOptions: project.CommandLine.CompilerOptions(),
|
||||
}
|
||||
}
|
||||
|
||||
type GetSymbolAtPositionParams struct {
|
||||
Project Handle[project.Project] `json:"project"`
|
||||
FileName string `json:"fileName"`
|
||||
Position uint32 `json:"position"`
|
||||
}
|
||||
|
||||
type GetSymbolsAtPositionsParams struct {
|
||||
Project Handle[project.Project] `json:"project"`
|
||||
FileName string `json:"fileName"`
|
||||
Positions []uint32 `json:"positions"`
|
||||
}
|
||||
|
||||
type GetSymbolAtLocationParams struct {
|
||||
Project Handle[project.Project] `json:"project"`
|
||||
Location Handle[ast.Node] `json:"location"`
|
||||
}
|
||||
|
||||
type GetSymbolsAtLocationsParams struct {
|
||||
Project Handle[project.Project] `json:"project"`
|
||||
Locations []Handle[ast.Node] `json:"locations"`
|
||||
}
|
||||
|
||||
type SymbolResponse struct {
|
||||
Id Handle[ast.Symbol] `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Flags uint32 `json:"flags"`
|
||||
CheckFlags uint32 `json:"checkFlags"`
|
||||
}
|
||||
|
||||
func NewSymbolResponse(symbol *ast.Symbol) *SymbolResponse {
|
||||
return &SymbolResponse{
|
||||
Id: SymbolHandle(symbol),
|
||||
Name: symbol.Name,
|
||||
Flags: uint32(symbol.Flags),
|
||||
CheckFlags: uint32(symbol.CheckFlags),
|
||||
}
|
||||
}
|
||||
|
||||
type GetTypeOfSymbolParams struct {
|
||||
Project Handle[project.Project] `json:"project"`
|
||||
Symbol Handle[ast.Symbol] `json:"symbol"`
|
||||
}
|
||||
|
||||
type GetTypesOfSymbolsParams struct {
|
||||
Project Handle[project.Project] `json:"project"`
|
||||
Symbols []Handle[ast.Symbol] `json:"symbols"`
|
||||
}
|
||||
|
||||
type TypeResponse struct {
|
||||
Id Handle[checker.Type] `json:"id"`
|
||||
Flags uint32 `json:"flags"`
|
||||
}
|
||||
|
||||
func NewTypeData(t *checker.Type) *TypeResponse {
|
||||
return &TypeResponse{
|
||||
Id: TypeHandle(t),
|
||||
Flags: uint32(t.Flags()),
|
||||
}
|
||||
}
|
||||
|
||||
type GetSourceFileParams struct {
|
||||
Project Handle[project.Project] `json:"project"`
|
||||
FileName string `json:"fileName"`
|
||||
}
|
||||
|
||||
func unmarshalPayload(method string, payload jsontext.Value) (any, error) {
|
||||
unmarshaler, ok := unmarshalers[Method(method)]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unknown API method %q", method)
|
||||
}
|
||||
return unmarshaler(payload)
|
||||
}
|
||||
|
||||
func unmarshallerFor[T any](data []byte) (any, error) {
|
||||
var v T
|
||||
if err := json.Unmarshal(data, &v); err != nil {
|
||||
return nil, fmt.Errorf("failed to unmarshal %T: %w", (*T)(nil), err)
|
||||
}
|
||||
return &v, nil
|
||||
}
|
||||
@ -1,497 +0,0 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"context"
|
||||
"encoding/binary"
|
||||
"fmt"
|
||||
"io"
|
||||
"runtime/debug"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/bundled"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/lsp/lsproto"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/project/logging"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/vfs/osvfs"
|
||||
"github.com/go-json-experiment/json"
|
||||
)
|
||||
|
||||
//go:generate go tool golang.org/x/tools/cmd/stringer -type=MessageType -output=stringer_generated.go
|
||||
//go:generate go tool mvdan.cc/gofumpt -w stringer_generated.go
|
||||
|
||||
type MessageType uint8
|
||||
|
||||
const (
|
||||
MessageTypeUnknown MessageType = iota
|
||||
MessageTypeRequest
|
||||
MessageTypeCallResponse
|
||||
MessageTypeCallError
|
||||
MessageTypeResponse
|
||||
MessageTypeError
|
||||
MessageTypeCall
|
||||
)
|
||||
|
||||
func (m MessageType) IsValid() bool {
|
||||
return m >= MessageTypeRequest && m <= MessageTypeCall
|
||||
}
|
||||
|
||||
type MessagePackType uint8
|
||||
|
||||
const (
|
||||
MessagePackTypeFixedArray3 MessagePackType = 0x93
|
||||
MessagePackTypeBin8 MessagePackType = 0xC4
|
||||
MessagePackTypeBin16 MessagePackType = 0xC5
|
||||
MessagePackTypeBin32 MessagePackType = 0xC6
|
||||
MessagePackTypeU8 MessagePackType = 0xCC
|
||||
)
|
||||
|
||||
type Callback int
|
||||
|
||||
const (
|
||||
CallbackDirectoryExists Callback = 1 << iota
|
||||
CallbackFileExists
|
||||
CallbackGetAccessibleEntries
|
||||
CallbackReadFile
|
||||
CallbackRealpath
|
||||
)
|
||||
|
||||
type ServerOptions struct {
|
||||
In io.Reader
|
||||
Out io.Writer
|
||||
Err io.Writer
|
||||
Cwd string
|
||||
DefaultLibraryPath string
|
||||
}
|
||||
|
||||
var _ vfs.FS = (*Server)(nil)
|
||||
|
||||
type Server struct {
|
||||
r *bufio.Reader
|
||||
w *bufio.Writer
|
||||
stderr io.Writer
|
||||
|
||||
cwd string
|
||||
newLine string
|
||||
fs vfs.FS
|
||||
defaultLibraryPath string
|
||||
|
||||
callbackMu sync.Mutex
|
||||
enabledCallbacks Callback
|
||||
logger logging.Logger
|
||||
api *API
|
||||
|
||||
requestId int
|
||||
}
|
||||
|
||||
func NewServer(options *ServerOptions) *Server {
|
||||
if options.Cwd == "" {
|
||||
panic("Cwd is required")
|
||||
}
|
||||
|
||||
server := &Server{
|
||||
r: bufio.NewReader(options.In),
|
||||
w: bufio.NewWriter(options.Out),
|
||||
stderr: options.Err,
|
||||
cwd: options.Cwd,
|
||||
fs: bundled.WrapFS(osvfs.FS()),
|
||||
defaultLibraryPath: options.DefaultLibraryPath,
|
||||
}
|
||||
logger := logging.NewLogger(options.Err)
|
||||
server.logger = logger
|
||||
server.api = NewAPI(&APIInit{
|
||||
Logger: logger,
|
||||
FS: server,
|
||||
SessionOptions: &project.SessionOptions{
|
||||
CurrentDirectory: options.Cwd,
|
||||
DefaultLibraryPath: options.DefaultLibraryPath,
|
||||
PositionEncoding: lsproto.PositionEncodingKindUTF8,
|
||||
LoggingEnabled: true,
|
||||
},
|
||||
})
|
||||
return server
|
||||
}
|
||||
|
||||
// DefaultLibraryPath implements APIHost.
|
||||
func (s *Server) DefaultLibraryPath() string {
|
||||
return s.defaultLibraryPath
|
||||
}
|
||||
|
||||
// FS implements APIHost.
|
||||
func (s *Server) FS() vfs.FS {
|
||||
return s
|
||||
}
|
||||
|
||||
// GetCurrentDirectory implements APIHost.
|
||||
func (s *Server) GetCurrentDirectory() string {
|
||||
return s.cwd
|
||||
}
|
||||
|
||||
func (s *Server) Run() error {
|
||||
for {
|
||||
messageType, method, payload, err := s.readRequest("")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
switch messageType {
|
||||
case MessageTypeRequest:
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
stack := debug.Stack()
|
||||
err = fmt.Errorf("panic handling request: %v\n%s", r, string(stack))
|
||||
if fatalErr := s.sendError(method, err); fatalErr != nil {
|
||||
panic("fatal error sending panic response")
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
result, err := s.handleRequest(method, payload)
|
||||
|
||||
if err != nil {
|
||||
if err := s.sendError(method, err); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := s.sendResponse(method, result); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("%w: expected request, received: %s", ErrInvalidRequest, messageType.String())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) readRequest(expectedMethod string) (messageType MessageType, method string, payload []byte, err error) {
|
||||
t, err := s.r.ReadByte()
|
||||
if err != nil {
|
||||
return messageType, method, payload, err
|
||||
}
|
||||
if MessagePackType(t) != MessagePackTypeFixedArray3 {
|
||||
return messageType, method, payload, fmt.Errorf("%w: expected message to be encoded as fixed 3-element array (0x93), received: 0x%2x", ErrInvalidRequest, t)
|
||||
}
|
||||
t, err = s.r.ReadByte()
|
||||
if err != nil {
|
||||
return messageType, method, payload, err
|
||||
}
|
||||
if MessagePackType(t) != MessagePackTypeU8 {
|
||||
return messageType, method, payload, fmt.Errorf("%w: expected first element of message tuple to be encoded as unsigned 8-bit int (0xcc), received: 0x%2x", ErrInvalidRequest, t)
|
||||
}
|
||||
rawMessageType, err := s.r.ReadByte()
|
||||
if err != nil {
|
||||
return messageType, method, payload, err
|
||||
}
|
||||
messageType = MessageType(rawMessageType)
|
||||
if !messageType.IsValid() {
|
||||
return messageType, method, payload, fmt.Errorf("%w: unknown message type: %d", ErrInvalidRequest, messageType)
|
||||
}
|
||||
rawMethod, err := s.readBin()
|
||||
if err != nil {
|
||||
return messageType, method, payload, err
|
||||
}
|
||||
method = string(rawMethod)
|
||||
if expectedMethod != "" && method != expectedMethod {
|
||||
return messageType, method, payload, fmt.Errorf("%w: expected method %q, received %q", ErrInvalidRequest, expectedMethod, method)
|
||||
}
|
||||
payload, err = s.readBin()
|
||||
return messageType, method, payload, err
|
||||
}
|
||||
|
||||
func (s *Server) readBin() ([]byte, error) {
|
||||
// https://github.com/msgpack/msgpack/blob/master/spec.md#bin-format-family
|
||||
t, err := s.r.ReadByte()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var size uint
|
||||
switch MessagePackType(t) {
|
||||
case MessagePackTypeBin8:
|
||||
var size8 uint8
|
||||
if err = binary.Read(s.r, binary.BigEndian, &size8); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size = uint(size8)
|
||||
case MessagePackTypeBin16:
|
||||
var size16 uint16
|
||||
if err = binary.Read(s.r, binary.BigEndian, &size16); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size = uint(size16)
|
||||
case MessagePackTypeBin32:
|
||||
var size32 uint32
|
||||
if err = binary.Read(s.r, binary.BigEndian, &size32); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
size = uint(size32)
|
||||
default:
|
||||
return nil, fmt.Errorf("%w: expected binary data length (0xc4-0xc6), received: 0x%2x", ErrInvalidRequest, t)
|
||||
}
|
||||
payload := make([]byte, size)
|
||||
bytesRead, err := io.ReadFull(s.r, payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if bytesRead != int(size) {
|
||||
return nil, fmt.Errorf("%w: expected %d bytes, read %d", ErrInvalidRequest, size, bytesRead)
|
||||
}
|
||||
return payload, nil
|
||||
}
|
||||
|
||||
func (s *Server) enableCallback(callback string) error {
|
||||
switch callback {
|
||||
case "directoryExists":
|
||||
s.enabledCallbacks |= CallbackDirectoryExists
|
||||
case "fileExists":
|
||||
s.enabledCallbacks |= CallbackFileExists
|
||||
case "getAccessibleEntries":
|
||||
s.enabledCallbacks |= CallbackGetAccessibleEntries
|
||||
case "readFile":
|
||||
s.enabledCallbacks |= CallbackReadFile
|
||||
case "realpath":
|
||||
s.enabledCallbacks |= CallbackRealpath
|
||||
default:
|
||||
return fmt.Errorf("unknown callback: %s", callback)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) handleRequest(method string, payload []byte) ([]byte, error) {
|
||||
s.requestId++
|
||||
switch method {
|
||||
case "configure":
|
||||
return nil, s.handleConfigure(payload)
|
||||
case "echo":
|
||||
return payload, nil
|
||||
default:
|
||||
return s.api.HandleRequest(core.WithRequestID(context.Background(), strconv.Itoa(s.requestId)), method, payload)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Server) handleConfigure(payload []byte) error {
|
||||
var params *ConfigureParams
|
||||
if err := json.Unmarshal(payload, ¶ms); err != nil {
|
||||
return fmt.Errorf("%w: %w", ErrInvalidRequest, err)
|
||||
}
|
||||
for _, callback := range params.Callbacks {
|
||||
if err := s.enableCallback(callback); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
// !!!
|
||||
if params.LogFile != "" {
|
||||
// s.logger.SetFile(params.LogFile)
|
||||
} else {
|
||||
// s.logger.SetFile("")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Server) sendResponse(method string, result []byte) error {
|
||||
return s.writeMessage(MessageTypeResponse, method, result)
|
||||
}
|
||||
|
||||
func (s *Server) sendError(method string, err error) error {
|
||||
return s.writeMessage(MessageTypeError, method, []byte(err.Error()))
|
||||
}
|
||||
|
||||
func (s *Server) writeMessage(messageType MessageType, method string, payload []byte) error {
|
||||
if err := s.w.WriteByte(byte(MessagePackTypeFixedArray3)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.w.WriteByte(byte(MessagePackTypeU8)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.w.WriteByte(byte(messageType)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.writeBin([]byte(method)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.writeBin(payload); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.w.Flush()
|
||||
}
|
||||
|
||||
func (s *Server) writeBin(payload []byte) error {
|
||||
length := len(payload)
|
||||
if length < 256 {
|
||||
if err := s.w.WriteByte(byte(MessagePackTypeBin8)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.w.WriteByte(byte(length)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else if length < 1<<16 {
|
||||
if err := s.w.WriteByte(byte(MessagePackTypeBin16)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(s.w, binary.BigEndian, uint16(length)); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
if err := s.w.WriteByte(byte(MessagePackTypeBin32)); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := binary.Write(s.w, binary.BigEndian, uint32(length)); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
_, err := s.w.Write(payload)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *Server) call(method string, payload any) ([]byte, error) {
|
||||
s.callbackMu.Lock()
|
||||
defer s.callbackMu.Unlock()
|
||||
jsonPayload, err := json.Marshal(payload)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err = s.writeMessage(MessageTypeCall, method, jsonPayload); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
messageType, _, responsePayload, err := s.readRequest(method)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if messageType != MessageTypeCallResponse && messageType != MessageTypeCallError {
|
||||
return nil, fmt.Errorf("%w: expected call-response or call-error, received: %s", ErrInvalidRequest, messageType.String())
|
||||
}
|
||||
|
||||
if messageType == MessageTypeCallError {
|
||||
return nil, fmt.Errorf("%w: %s", ErrClientError, responsePayload)
|
||||
}
|
||||
|
||||
return responsePayload, nil
|
||||
}
|
||||
|
||||
// DirectoryExists implements vfs.FS.
|
||||
func (s *Server) DirectoryExists(path string) bool {
|
||||
if s.enabledCallbacks&CallbackDirectoryExists != 0 {
|
||||
result, err := s.call("directoryExists", path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 {
|
||||
return string(result) == "true"
|
||||
}
|
||||
}
|
||||
return s.fs.DirectoryExists(path)
|
||||
}
|
||||
|
||||
// FileExists implements vfs.FS.
|
||||
func (s *Server) FileExists(path string) bool {
|
||||
if s.enabledCallbacks&CallbackFileExists != 0 {
|
||||
result, err := s.call("fileExists", path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 {
|
||||
return string(result) == "true"
|
||||
}
|
||||
}
|
||||
return s.fs.FileExists(path)
|
||||
}
|
||||
|
||||
// GetAccessibleEntries implements vfs.FS.
|
||||
func (s *Server) GetAccessibleEntries(path string) vfs.Entries {
|
||||
if s.enabledCallbacks&CallbackGetAccessibleEntries != 0 {
|
||||
result, err := s.call("getAccessibleEntries", path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(result) > 0 {
|
||||
var rawEntries *struct {
|
||||
Files []string `json:"files"`
|
||||
Directories []string `json:"directories"`
|
||||
}
|
||||
if err := json.Unmarshal(result, &rawEntries); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if rawEntries != nil {
|
||||
return vfs.Entries{
|
||||
Files: rawEntries.Files,
|
||||
Directories: rawEntries.Directories,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return s.fs.GetAccessibleEntries(path)
|
||||
}
|
||||
|
||||
// ReadFile implements vfs.FS.
|
||||
func (s *Server) ReadFile(path string) (contents string, ok bool) {
|
||||
if s.enabledCallbacks&CallbackReadFile != 0 {
|
||||
data, err := s.call("readFile", path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if string(data) == "null" {
|
||||
return "", false
|
||||
}
|
||||
if len(data) > 0 {
|
||||
var result string
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result, true
|
||||
}
|
||||
}
|
||||
return s.fs.ReadFile(path)
|
||||
}
|
||||
|
||||
// Realpath implements vfs.FS.
|
||||
func (s *Server) Realpath(path string) string {
|
||||
if s.enabledCallbacks&CallbackRealpath != 0 {
|
||||
data, err := s.call("realpath", path)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if len(data) > 0 {
|
||||
var result string
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
return s.fs.Realpath(path)
|
||||
}
|
||||
|
||||
// UseCaseSensitiveFileNames implements vfs.FS.
|
||||
func (s *Server) UseCaseSensitiveFileNames() bool {
|
||||
return s.fs.UseCaseSensitiveFileNames()
|
||||
}
|
||||
|
||||
// WriteFile implements vfs.FS.
|
||||
func (s *Server) WriteFile(path string, data string, writeByteOrderMark bool) error {
|
||||
return s.fs.WriteFile(path, data, writeByteOrderMark)
|
||||
}
|
||||
|
||||
// WalkDir implements vfs.FS.
|
||||
func (s *Server) WalkDir(root string, walkFn vfs.WalkDirFunc) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Stat implements vfs.FS.
|
||||
func (s *Server) Stat(path string) vfs.FileInfo {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Remove implements vfs.FS.
|
||||
func (s *Server) Remove(path string) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
|
||||
// Chtimes implements vfs.FS.
|
||||
func (s *Server) Chtimes(path string, aTime time.Time, mTime time.Time) error {
|
||||
panic("unimplemented")
|
||||
}
|
||||
@ -1,29 +0,0 @@
|
||||
// Code generated by "stringer -type=MessageType -output=stringer_generated.go"; DO NOT EDIT.
|
||||
|
||||
package api
|
||||
|
||||
import "strconv"
|
||||
|
||||
func _() {
|
||||
// An "invalid array index" compiler error signifies that the constant values have changed.
|
||||
// Re-run the stringer command to generate them again.
|
||||
var x [1]struct{}
|
||||
_ = x[MessageTypeUnknown-0]
|
||||
_ = x[MessageTypeRequest-1]
|
||||
_ = x[MessageTypeCallResponse-2]
|
||||
_ = x[MessageTypeCallError-3]
|
||||
_ = x[MessageTypeResponse-4]
|
||||
_ = x[MessageTypeError-5]
|
||||
_ = x[MessageTypeCall-6]
|
||||
}
|
||||
|
||||
const _MessageType_name = "MessageTypeUnknownMessageTypeRequestMessageTypeCallResponseMessageTypeCallErrorMessageTypeResponseMessageTypeErrorMessageTypeCall"
|
||||
|
||||
var _MessageType_index = [...]uint8{0, 18, 36, 59, 79, 98, 114, 129}
|
||||
|
||||
func (i MessageType) String() string {
|
||||
if i >= MessageType(len(_MessageType_index)-1) {
|
||||
return "MessageType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||
}
|
||||
return _MessageType_name[_MessageType_index[i]:_MessageType_index[i+1]]
|
||||
}
|
||||
@ -1,418 +0,0 @@
|
||||
package diagnosticwriter
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"maps"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"unicode"
|
||||
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/ast"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/core"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/diagnostics"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/scanner"
|
||||
"efprojects.com/kitten-ipc/kitcom/internal/tsgo/tspath"
|
||||
)
|
||||
|
||||
type FormattingOptions struct {
|
||||
tspath.ComparePathsOptions
|
||||
NewLine string
|
||||
}
|
||||
|
||||
const (
|
||||
foregroundColorEscapeGrey = "\u001b[90m"
|
||||
foregroundColorEscapeRed = "\u001b[91m"
|
||||
foregroundColorEscapeYellow = "\u001b[93m"
|
||||
foregroundColorEscapeBlue = "\u001b[94m"
|
||||
foregroundColorEscapeCyan = "\u001b[96m"
|
||||
)
|
||||
|
||||
const (
|
||||
gutterStyleSequence = "\u001b[7m"
|
||||
gutterSeparator = " "
|
||||
resetEscapeSequence = "\u001b[0m"
|
||||
ellipsis = "..."
|
||||
)
|
||||
|
||||
func FormatDiagnosticsWithColorAndContext(output io.Writer, diags []*ast.Diagnostic, formatOpts *FormattingOptions) {
|
||||
if len(diags) == 0 {
|
||||
return
|
||||
}
|
||||
for i, diagnostic := range diags {
|
||||
if i > 0 {
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
}
|
||||
FormatDiagnosticWithColorAndContext(output, diagnostic, formatOpts)
|
||||
}
|
||||
}
|
||||
|
||||
func FormatDiagnosticWithColorAndContext(output io.Writer, diagnostic *ast.Diagnostic, formatOpts *FormattingOptions) {
|
||||
if diagnostic.File() != nil {
|
||||
file := diagnostic.File()
|
||||
pos := diagnostic.Loc().Pos()
|
||||
WriteLocation(output, file, pos, formatOpts, writeWithStyleAndReset)
|
||||
fmt.Fprint(output, " - ")
|
||||
}
|
||||
|
||||
writeWithStyleAndReset(output, diagnostic.Category().Name(), getCategoryFormat(diagnostic.Category()))
|
||||
fmt.Fprintf(output, "%s TS%d: %s", foregroundColorEscapeGrey, diagnostic.Code(), resetEscapeSequence)
|
||||
WriteFlattenedDiagnosticMessage(output, diagnostic, formatOpts.NewLine)
|
||||
|
||||
if diagnostic.File() != nil && diagnostic.Code() != diagnostics.File_appears_to_be_binary.Code() {
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
writeCodeSnippet(output, diagnostic.File(), diagnostic.Pos(), diagnostic.Len(), getCategoryFormat(diagnostic.Category()), "", formatOpts)
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
}
|
||||
|
||||
if (diagnostic.RelatedInformation() != nil) && (len(diagnostic.RelatedInformation()) > 0) {
|
||||
for _, relatedInformation := range diagnostic.RelatedInformation() {
|
||||
file := relatedInformation.File()
|
||||
if file != nil {
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
fmt.Fprint(output, " ")
|
||||
pos := relatedInformation.Pos()
|
||||
WriteLocation(output, file, pos, formatOpts, writeWithStyleAndReset)
|
||||
fmt.Fprint(output, " - ")
|
||||
WriteFlattenedDiagnosticMessage(output, relatedInformation, formatOpts.NewLine)
|
||||
writeCodeSnippet(output, file, pos, relatedInformation.Len(), foregroundColorEscapeCyan, " ", formatOpts)
|
||||
}
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func writeCodeSnippet(writer io.Writer, sourceFile *ast.SourceFile, start int, length int, squiggleColor string, indent string, formatOpts *FormattingOptions) {
|
||||
firstLine, firstLineChar := scanner.GetECMALineAndCharacterOfPosition(sourceFile, start)
|
||||
lastLine, lastLineChar := scanner.GetECMALineAndCharacterOfPosition(sourceFile, start+length)
|
||||
if length == 0 {
|
||||
lastLineChar++ // When length is zero, squiggle the character right after the start position.
|
||||
}
|
||||
|
||||
lastLineOfFile, _ := scanner.GetECMALineAndCharacterOfPosition(sourceFile, len(sourceFile.Text()))
|
||||
|
||||
hasMoreThanFiveLines := lastLine-firstLine >= 4
|
||||
gutterWidth := len(strconv.Itoa(lastLine + 1))
|
||||
if hasMoreThanFiveLines {
|
||||
gutterWidth = max(len(ellipsis), gutterWidth)
|
||||
}
|
||||
|
||||
for i := firstLine; i <= lastLine; i++ {
|
||||
fmt.Fprint(writer, formatOpts.NewLine)
|
||||
|
||||
// If the error spans over 5 lines, we'll only show the first 2 and last 2 lines,
|
||||
// so we'll skip ahead to the second-to-last line.
|
||||
if hasMoreThanFiveLines && firstLine+1 < i && i < lastLine-1 {
|
||||
fmt.Fprint(writer, indent)
|
||||
fmt.Fprint(writer, gutterStyleSequence)
|
||||
fmt.Fprintf(writer, "%*s", gutterWidth, ellipsis)
|
||||
fmt.Fprint(writer, resetEscapeSequence)
|
||||
fmt.Fprint(writer, gutterSeparator)
|
||||
fmt.Fprint(writer, formatOpts.NewLine)
|
||||
i = lastLine - 1
|
||||
}
|
||||
|
||||
lineStart := scanner.GetECMAPositionOfLineAndCharacter(sourceFile, i, 0)
|
||||
var lineEnd int
|
||||
if i < lastLineOfFile {
|
||||
lineEnd = scanner.GetECMAPositionOfLineAndCharacter(sourceFile, i+1, 0)
|
||||
} else {
|
||||
lineEnd = sourceFile.Loc.End()
|
||||
}
|
||||
|
||||
lineContent := strings.TrimRightFunc(sourceFile.Text()[lineStart:lineEnd], unicode.IsSpace) // trim from end
|
||||
lineContent = strings.ReplaceAll(lineContent, "\t", " ") // convert tabs to single spaces
|
||||
|
||||
// Output the gutter and the actual contents of the line.
|
||||
fmt.Fprint(writer, indent)
|
||||
fmt.Fprint(writer, gutterStyleSequence)
|
||||
fmt.Fprintf(writer, "%*d", gutterWidth, i+1)
|
||||
fmt.Fprint(writer, resetEscapeSequence)
|
||||
fmt.Fprint(writer, gutterSeparator)
|
||||
fmt.Fprint(writer, lineContent)
|
||||
fmt.Fprint(writer, formatOpts.NewLine)
|
||||
|
||||
// Output the gutter and the error span for the line using tildes.
|
||||
fmt.Fprint(writer, indent)
|
||||
fmt.Fprint(writer, gutterStyleSequence)
|
||||
fmt.Fprintf(writer, "%*s", gutterWidth, "")
|
||||
fmt.Fprint(writer, resetEscapeSequence)
|
||||
fmt.Fprint(writer, gutterSeparator)
|
||||
fmt.Fprint(writer, squiggleColor)
|
||||
switch i {
|
||||
case firstLine:
|
||||
// If we're on the last line, then limit it to the last character of the last line.
|
||||
// Otherwise, we'll just squiggle the rest of the line, giving 'slice' no end position.
|
||||
var lastCharForLine int
|
||||
if i == lastLine {
|
||||
lastCharForLine = lastLineChar
|
||||
} else {
|
||||
lastCharForLine = len(lineContent)
|
||||
}
|
||||
|
||||
// Fill with spaces until the first character,
|
||||
// then squiggle the remainder of the line.
|
||||
fmt.Fprint(writer, strings.Repeat(" ", firstLineChar))
|
||||
fmt.Fprint(writer, strings.Repeat("~", lastCharForLine-firstLineChar))
|
||||
case lastLine:
|
||||
// Squiggle until the final character.
|
||||
fmt.Fprint(writer, strings.Repeat("~", lastLineChar))
|
||||
default:
|
||||
// Squiggle the entire line.
|
||||
fmt.Fprint(writer, strings.Repeat("~", len(lineContent)))
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, resetEscapeSequence)
|
||||
}
|
||||
}
|
||||
|
||||
func FlattenDiagnosticMessage(d *ast.Diagnostic, newLine string) string {
|
||||
var output strings.Builder
|
||||
WriteFlattenedDiagnosticMessage(&output, d, newLine)
|
||||
return output.String()
|
||||
}
|
||||
|
||||
func WriteFlattenedDiagnosticMessage(writer io.Writer, diagnostic *ast.Diagnostic, newline string) {
|
||||
fmt.Fprint(writer, diagnostic.Message())
|
||||
|
||||
for _, chain := range diagnostic.MessageChain() {
|
||||
flattenDiagnosticMessageChain(writer, chain, newline, 1 /*level*/)
|
||||
}
|
||||
}
|
||||
|
||||
func flattenDiagnosticMessageChain(writer io.Writer, chain *ast.Diagnostic, newLine string, level int) {
|
||||
fmt.Fprint(writer, newLine)
|
||||
for range level {
|
||||
fmt.Fprint(writer, " ")
|
||||
}
|
||||
|
||||
fmt.Fprint(writer, chain.Message())
|
||||
for _, child := range chain.MessageChain() {
|
||||
flattenDiagnosticMessageChain(writer, child, newLine, level+1)
|
||||
}
|
||||
}
|
||||
|
||||
func getCategoryFormat(category diagnostics.Category) string {
|
||||
switch category {
|
||||
case diagnostics.CategoryError:
|
||||
return foregroundColorEscapeRed
|
||||
case diagnostics.CategoryWarning:
|
||||
return foregroundColorEscapeYellow
|
||||
case diagnostics.CategorySuggestion:
|
||||
return foregroundColorEscapeGrey
|
||||
case diagnostics.CategoryMessage:
|
||||
return foregroundColorEscapeBlue
|
||||
}
|
||||
panic("Unhandled diagnostic category")
|
||||
}
|
||||
|
||||
type FormattedWriter func(output io.Writer, text string, formatStyle string)
|
||||
|
||||
func writeWithStyleAndReset(output io.Writer, text string, formatStyle string) {
|
||||
fmt.Fprint(output, formatStyle)
|
||||
fmt.Fprint(output, text)
|
||||
fmt.Fprint(output, resetEscapeSequence)
|
||||
}
|
||||
|
||||
func WriteLocation(output io.Writer, file *ast.SourceFile, pos int, formatOpts *FormattingOptions, writeWithStyleAndReset FormattedWriter) {
|
||||
firstLine, firstChar := scanner.GetECMALineAndCharacterOfPosition(file, pos)
|
||||
var relativeFileName string
|
||||
if formatOpts != nil {
|
||||
relativeFileName = tspath.ConvertToRelativePath(file.FileName(), formatOpts.ComparePathsOptions)
|
||||
} else {
|
||||
relativeFileName = file.FileName()
|
||||
}
|
||||
|
||||
writeWithStyleAndReset(output, relativeFileName, foregroundColorEscapeCyan)
|
||||
fmt.Fprint(output, ":")
|
||||
writeWithStyleAndReset(output, strconv.Itoa(firstLine+1), foregroundColorEscapeYellow)
|
||||
fmt.Fprint(output, ":")
|
||||
writeWithStyleAndReset(output, strconv.Itoa(firstChar+1), foregroundColorEscapeYellow)
|
||||
}
|
||||
|
||||
// Some of these lived in watch.ts, but they're not specific to the watch API.
|
||||
|
||||
type ErrorSummary struct {
|
||||
TotalErrorCount int
|
||||
GlobalErrors []*ast.Diagnostic
|
||||
ErrorsByFiles map[*ast.SourceFile][]*ast.Diagnostic
|
||||
SortedFileList []*ast.SourceFile
|
||||
}
|
||||
|
||||
func WriteErrorSummaryText(output io.Writer, allDiagnostics []*ast.Diagnostic, formatOpts *FormattingOptions) {
|
||||
// Roughly corresponds to 'getErrorSummaryText' from watch.ts
|
||||
|
||||
errorSummary := getErrorSummary(allDiagnostics)
|
||||
totalErrorCount := errorSummary.TotalErrorCount
|
||||
if totalErrorCount == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
firstFile := &ast.SourceFile{}
|
||||
if len(errorSummary.SortedFileList) > 0 {
|
||||
firstFile = errorSummary.SortedFileList[0]
|
||||
}
|
||||
firstFileName := prettyPathForFileError(firstFile, errorSummary.ErrorsByFiles[firstFile], formatOpts)
|
||||
numErroringFiles := len(errorSummary.ErrorsByFiles)
|
||||
|
||||
var message string
|
||||
if totalErrorCount == 1 {
|
||||
// Special-case a single error.
|
||||
if len(errorSummary.GlobalErrors) > 0 || firstFileName == "" {
|
||||
message = diagnostics.Found_1_error.Format()
|
||||
} else {
|
||||
message = diagnostics.Found_1_error_in_0.Format(firstFileName)
|
||||
}
|
||||
} else {
|
||||
switch numErroringFiles {
|
||||
case 0:
|
||||
// No file-specific errors.
|
||||
message = diagnostics.Found_0_errors.Format(totalErrorCount)
|
||||
case 1:
|
||||
// One file with errors.
|
||||
message = diagnostics.Found_0_errors_in_the_same_file_starting_at_Colon_1.Format(totalErrorCount, firstFileName)
|
||||
default:
|
||||
// Multiple files with errors.
|
||||
message = diagnostics.Found_0_errors_in_1_files.Format(totalErrorCount, numErroringFiles)
|
||||
}
|
||||
}
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
fmt.Fprint(output, message)
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
if numErroringFiles > 1 {
|
||||
writeTabularErrorsDisplay(output, errorSummary, formatOpts)
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
}
|
||||
}
|
||||
|
||||
func getErrorSummary(diags []*ast.Diagnostic) *ErrorSummary {
|
||||
var totalErrorCount int
|
||||
var globalErrors []*ast.Diagnostic
|
||||
var errorsByFiles map[*ast.SourceFile][]*ast.Diagnostic
|
||||
|
||||
for _, diagnostic := range diags {
|
||||
if diagnostic.Category() != diagnostics.CategoryError {
|
||||
continue
|
||||
}
|
||||
|
||||
totalErrorCount++
|
||||
if diagnostic.File() == nil {
|
||||
globalErrors = append(globalErrors, diagnostic)
|
||||
} else {
|
||||
if errorsByFiles == nil {
|
||||
errorsByFiles = make(map[*ast.SourceFile][]*ast.Diagnostic)
|
||||
}
|
||||
errorsByFiles[diagnostic.File()] = append(errorsByFiles[diagnostic.File()], diagnostic)
|
||||
}
|
||||
}
|
||||
|
||||
// !!!
|
||||
// Need an ordered map here, but sorting for consistency.
|
||||
sortedFileList := slices.SortedFunc(maps.Keys(errorsByFiles), func(a, b *ast.SourceFile) int {
|
||||
return strings.Compare(a.FileName(), b.FileName())
|
||||
})
|
||||
|
||||
return &ErrorSummary{
|
||||
TotalErrorCount: totalErrorCount,
|
||||
GlobalErrors: globalErrors,
|
||||
ErrorsByFiles: errorsByFiles,
|
||||
SortedFileList: sortedFileList,
|
||||
}
|
||||
}
|
||||
|
||||
func writeTabularErrorsDisplay(output io.Writer, errorSummary *ErrorSummary, formatOpts *FormattingOptions) {
|
||||
sortedFiles := errorSummary.SortedFileList
|
||||
|
||||
maxErrors := 0
|
||||
for _, errorsForFile := range errorSummary.ErrorsByFiles {
|
||||
maxErrors = max(maxErrors, len(errorsForFile))
|
||||
}
|
||||
|
||||
// !!!
|
||||
// TODO (drosen): This was never localized.
|
||||
// Should make this better.
|
||||
headerRow := diagnostics.Errors_Files.Message()
|
||||
leftColumnHeadingLength := len(strings.Split(headerRow, " ")[0])
|
||||
lengthOfBiggestErrorCount := len(strconv.Itoa(maxErrors))
|
||||
leftPaddingGoal := max(leftColumnHeadingLength, lengthOfBiggestErrorCount)
|
||||
headerPadding := max(lengthOfBiggestErrorCount-leftColumnHeadingLength, 0)
|
||||
|
||||
fmt.Fprint(output, strings.Repeat(" ", headerPadding))
|
||||
fmt.Fprint(output, headerRow)
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
|
||||
for _, file := range sortedFiles {
|
||||
fileErrors := errorSummary.ErrorsByFiles[file]
|
||||
errorCount := len(fileErrors)
|
||||
|
||||
fmt.Fprintf(output, "%*d ", leftPaddingGoal, errorCount)
|
||||
fmt.Fprint(output, prettyPathForFileError(file, fileErrors, formatOpts))
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
}
|
||||
}
|
||||
|
||||
func prettyPathForFileError(file *ast.SourceFile, fileErrors []*ast.Diagnostic, formatOpts *FormattingOptions) string {
|
||||
if file == nil || len(fileErrors) == 0 {
|
||||
return ""
|
||||
}
|
||||
line, _ := scanner.GetECMALineAndCharacterOfPosition(file, fileErrors[0].Loc().Pos())
|
||||
fileName := file.FileName()
|
||||
if tspath.PathIsAbsolute(fileName) && tspath.PathIsAbsolute(formatOpts.CurrentDirectory) {
|
||||
fileName = tspath.ConvertToRelativePath(file.FileName(), formatOpts.ComparePathsOptions)
|
||||
}
|
||||
return fmt.Sprintf("%s%s:%d%s",
|
||||
fileName,
|
||||
foregroundColorEscapeGrey,
|
||||
line+1,
|
||||
resetEscapeSequence,
|
||||
)
|
||||
}
|
||||
|
||||
func WriteFormatDiagnostics(output io.Writer, diagnostics []*ast.Diagnostic, formatOpts *FormattingOptions) {
|
||||
for _, diagnostic := range diagnostics {
|
||||
WriteFormatDiagnostic(output, diagnostic, formatOpts)
|
||||
}
|
||||
}
|
||||
|
||||
func WriteFormatDiagnostic(output io.Writer, diagnostic *ast.Diagnostic, formatOpts *FormattingOptions) {
|
||||
if diagnostic.File() != nil {
|
||||
line, character := scanner.GetECMALineAndCharacterOfPosition(diagnostic.File(), diagnostic.Loc().Pos())
|
||||
fileName := diagnostic.File().FileName()
|
||||
relativeFileName := tspath.ConvertToRelativePath(fileName, formatOpts.ComparePathsOptions)
|
||||
fmt.Fprintf(output, "%s(%d,%d): ", relativeFileName, line+1, character+1)
|
||||
}
|
||||
|
||||
fmt.Fprintf(output, "%s TS%d: ", diagnostic.Category().Name(), diagnostic.Code())
|
||||
WriteFlattenedDiagnosticMessage(output, diagnostic, formatOpts.NewLine)
|
||||
fmt.Fprint(output, formatOpts.NewLine)
|
||||
}
|
||||
|
||||
func FormatDiagnosticsStatusWithColorAndTime(output io.Writer, time string, diag *ast.Diagnostic, formatOpts *FormattingOptions) {
|
||||
fmt.Fprint(output, "[")
|
||||
writeWithStyleAndReset(output, time, foregroundColorEscapeGrey)
|
||||
fmt.Fprint(output, "] ")
|
||||
WriteFlattenedDiagnosticMessage(output, diag, formatOpts.NewLine)
|
||||
}
|
||||
|
||||
func FormatDiagnosticsStatusAndTime(output io.Writer, time string, diag *ast.Diagnostic, formatOpts *FormattingOptions) {
|
||||
fmt.Fprint(output, time, " - ")
|
||||
WriteFlattenedDiagnosticMessage(output, diag, formatOpts.NewLine)
|
||||
}
|
||||
|
||||
var ScreenStartingCodes = []int32{
|
||||
diagnostics.Starting_compilation_in_watch_mode.Code(),
|
||||
diagnostics.File_change_detected_Starting_incremental_compilation.Code(),
|
||||
}
|
||||
|
||||
func TryClearScreen(output io.Writer, diag *ast.Diagnostic, options *core.CompilerOptions) bool {
|
||||
if !options.PreserveWatchOutput.IsTrue() &&
|
||||
!options.ExtendedDiagnostics.IsTrue() &&
|
||||
!options.Diagnostics.IsTrue() &&
|
||||
slices.Contains(ScreenStartingCodes, diag.Code()) {
|
||||
fmt.Fprint(output, "\x1B[2J\x1B[3J\x1B[H") // Clear screen and move cursor to home position
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user