diff --git a/kitcom/internal/tsgo/api/api.go b/kitcom/internal/tsgo/api/api.go deleted file mode 100644 index 49e7d3d..0000000 --- a/kitcom/internal/tsgo/api/api.go +++ /dev/null @@ -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) -} diff --git a/kitcom/internal/tsgo/api/encoder/encoder.go b/kitcom/internal/tsgo/api/encoder/encoder.go deleted file mode 100644 index b3867a8..0000000 --- a/kitcom/internal/tsgo/api/encoder/encoder.go +++ /dev/null @@ -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 -} diff --git a/kitcom/internal/tsgo/api/encoder/encoder_test.go b/kitcom/internal/tsgo/api/encoder/encoder_test.go deleted file mode 100644 index cc215eb..0000000 --- a/kitcom/internal/tsgo/api/encoder/encoder_test.go +++ /dev/null @@ -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(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() -} diff --git a/kitcom/internal/tsgo/api/encoder/stringtable.go b/kitcom/internal/tsgo/api/encoder/stringtable.go deleted file mode 100644 index 8abf6f7..0000000 --- a/kitcom/internal/tsgo/api/encoder/stringtable.go +++ /dev/null @@ -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() -} diff --git a/kitcom/internal/tsgo/api/proto.go b/kitcom/internal/tsgo/api/proto.go deleted file mode 100644 index fcdb0e7..0000000 --- a/kitcom/internal/tsgo/api/proto.go +++ /dev/null @@ -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 -} diff --git a/kitcom/internal/tsgo/api/server.go b/kitcom/internal/tsgo/api/server.go deleted file mode 100644 index f11639c..0000000 --- a/kitcom/internal/tsgo/api/server.go +++ /dev/null @@ -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") -} diff --git a/kitcom/internal/tsgo/api/stringer_generated.go b/kitcom/internal/tsgo/api/stringer_generated.go deleted file mode 100644 index 0a740d3..0000000 --- a/kitcom/internal/tsgo/api/stringer_generated.go +++ /dev/null @@ -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]] -} diff --git a/kitcom/internal/tsgo/diagnosticwriter/diagnosticwriter.go b/kitcom/internal/tsgo/diagnosticwriter/diagnosticwriter.go deleted file mode 100644 index afb7520..0000000 --- a/kitcom/internal/tsgo/diagnosticwriter/diagnosticwriter.go +++ /dev/null @@ -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 -}