remove unused packages

This commit is contained in:
Egor Aristov 2025-10-15 17:26:13 +03:00
parent 079ff1ee3e
commit b1f5661baf
Signed by: egor3f
GPG Key ID: 40482A264AAEC85F
8 changed files with 0 additions and 2459 deletions

View File

@ -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)
}

View File

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

View File

@ -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()
}

View File

@ -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()
}

View File

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

View File

@ -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, &params); 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")
}

View File

@ -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]]
}

View File

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