Skip to content

Basic completion structure + dot completions #811

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 29 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
8dbdd93
start completions and getSymbolsInScope
gabritto Mar 25, 2025
8ab310e
Merge branch 'main' into gabritto/completion
gabritto Mar 25, 2025
4e2514b
some of getcompletiondata
gabritto Apr 1, 2025
8db06be
finish initial port of getTypeScriptMemberSymbols
gabritto Apr 2, 2025
5becbaa
Merge branch 'main' into gabritto/completion
gabritto Apr 2, 2025
0c29714
update submodule
gabritto Apr 2, 2025
601c4ea
more completions
gabritto Apr 9, 2025
9d8edd1
more completions: finish create completion entry
gabritto Apr 10, 2025
1e26fa8
Merge branch 'main' into gabritto/completion
gabritto Apr 10, 2025
75a781c
more completions
gabritto Apr 11, 2025
bb92255
format
gabritto Apr 11, 2025
d515d6c
sketch of unit test
gabritto Apr 11, 2025
71fce1a
move service test setup to its own pacakge for reuse
gabritto Apr 14, 2025
9d729a2
WIP: findPrecedingToken
gabritto Apr 16, 2025
df5086e
find preceding token
gabritto Apr 17, 2025
b4cc421
completion basic member test passing
gabritto Apr 21, 2025
cb966b8
Merge branch 'main' into gabritto/completion
gabritto Apr 21, 2025
1aa96a4
refactoring and fixes after merge
gabritto Apr 21, 2025
62b46e1
re-add baselines
gabritto Apr 21, 2025
29ec6ac
fix lint
gabritto Apr 21, 2025
c9ce06e
skip completion server test if no embeded
gabritto Apr 21, 2025
1514cfe
address CR
gabritto Apr 21, 2025
4b2821f
more refactoring
gabritto Apr 21, 2025
34f5b83
more fixes
gabritto Apr 22, 2025
37ceade
Merge branch 'main' into gabritto/completion
gabritto Apr 22, 2025
3b005d1
add panic recovery, and send completion provider in lsp initialize
gabritto Apr 22, 2025
1745cd3
Merge branch 'main' into gabritto/completion
gabritto Apr 22, 2025
fa9885a
fix infinite loop
gabritto Apr 22, 2025
967fb76
fix map
gabritto Apr 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 24 additions & 2 deletions internal/ast/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -741,6 +741,18 @@ func (n *Node) Children() *NodeList {
panic("Unhandled case in Node.Children: " + n.Kind.String())
}

func (n *Node) ModuleSpecifier() *Expression {
switch n.Kind {
case KindImportDeclaration:
return n.AsImportDeclaration().ModuleSpecifier
case KindExportDeclaration:
return n.AsExportDeclaration().ModuleSpecifier
case KindJSDocImportTag:
return n.AsJSDocImportTag().ModuleSpecifier
}
panic("Unhandled case in Node.ModuleSpecifier: " + n.Kind.String())
}

// Determines if `n` contains `descendant` by walking up the `Parent` pointers from `descendant`. This method panics if
// `descendant` or one of its ancestors is not parented except when that node is a `SourceFile`.
func (n *Node) Contains(descendant *Node) bool {
Expand Down Expand Up @@ -1647,6 +1659,10 @@ type (
JSDocComment = Node // JSDocText | JSDocLink | JSDocLinkCode | JSDocLinkPlain;
JSDocTag = Node // Node with JSDocTagBase
SignatureDeclaration = Node // CallSignatureDeclaration | ConstructSignatureDeclaration | MethodSignature | IndexSignatureDeclaration | FunctionTypeNode | ConstructorTypeNode | FunctionDeclaration | MethodDeclaration | ConstructorDeclaration | AccessorDeclaration | FunctionExpression | ArrowFunction;
StringLiteralLike = Node // StringLiteral | NoSubstitutionTemplateLiteral
AnyValidImportOrReExport = Node // (ImportDeclaration | ExportDeclaration | JSDocImportTag) & { moduleSpecifier: StringLiteral } | ImportEqualsDeclaration & { moduleReference: ExternalModuleReference & { expression: StringLiteral }} | RequireOrImportCall | ValidImportTypeNode
ValidImportTypeNode = Node // ImportTypeNode & { argument: LiteralTypeNode & { literal: StringLiteral } }
NumericOrStringLikeLiteral = Node // StringLiteralLike | NumericLiteral
)

// Aliases for node singletons
Expand All @@ -1665,6 +1681,7 @@ type (
CatchClauseNode = Node
CaseBlockNode = Node
CaseOrDefaultClauseNode = Node
CaseClauseNode = Node
VariableDeclarationNode = Node
VariableDeclarationListNode = Node
BindingElementNode = Node
Expand All @@ -1687,6 +1704,7 @@ type (
JsxOpeningFragmentNode = Node
JsxClosingFragmentNode = Node
SourceFileNode = Node
PropertyAccessExpressionNode = Node
)

type (
Expand Down Expand Up @@ -8211,6 +8229,10 @@ func (node *JsxFragment) computeSubtreeFacts() SubtreeFacts {
SubtreeContainsJsx
}

func IsJsxFragment(node *Node) bool {
return node.Kind == KindJsxFragment
}

/// The opening element of a <>...</> JsxFragment

type JsxOpeningFragment struct {
Expand Down Expand Up @@ -9438,7 +9460,7 @@ func (node *JSDocThisTag) Clone(f *NodeFactory) *Node {
type JSDocImportTag struct {
JSDocTagBase
ImportClause *Declaration
ModuleSpecifier *Node
ModuleSpecifier *Expression
Attributes *Node
}

Expand Down Expand Up @@ -9939,7 +9961,7 @@ func (node *SourceFile) GetOrCreateToken(
panic(fmt.Sprintf("Token cache mismatch: %v != %v", token.Kind, kind))
}
if token.Parent != parent {
panic("Token cache mismatch: parent")
panic(fmt.Sprintf("Token cache mismatch: parent. Expected parent of kind %v, got %v", token.Parent.Kind, parent.Kind))
}
return token
}
Expand Down
2 changes: 2 additions & 0 deletions internal/ast/nodeflags.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ const (
NodeFlagsJsonFile NodeFlags = 1 << 25 // If node was parsed in a Json
NodeFlagsDeprecated NodeFlags = 1 << 26 // If has '@deprecated' JSDoc tag

NodeFlagsSkipDirectInference NodeFlags = 1 << 27 // If the node should skip direct type inference.

NodeFlagsBlockScoped = NodeFlagsLet | NodeFlagsConst | NodeFlagsUsing
NodeFlagsConstant = NodeFlagsConst | NodeFlagsUsing
NodeFlagsAwaitUsing = NodeFlagsConst | NodeFlagsUsing // Variable declaration (NOTE: on a single node these flags would otherwise be mutually exclusive)
Expand Down
136 changes: 134 additions & 2 deletions internal/ast/utilities.go
Original file line number Diff line number Diff line change
Expand Up @@ -510,7 +510,7 @@ func IsFunctionLikeDeclaration(node *Node) bool {
return node != nil && isFunctionLikeDeclarationKind(node.Kind)
}

func isFunctionLikeKind(kind Kind) bool {
func IsFunctionLikeKind(kind Kind) bool {
switch kind {
case KindMethodSignature,
KindCallSignature,
Expand All @@ -527,7 +527,7 @@ func isFunctionLikeKind(kind Kind) bool {
// Determines if a node is function- or signature-like.
func IsFunctionLike(node *Node) bool {
// TODO(rbuckton): Move `node != nil` test to call sites
return node != nil && isFunctionLikeKind(node.Kind)
return node != nil && IsFunctionLikeKind(node.Kind)
}

func IsFunctionLikeOrClassStaticBlockDeclaration(node *Node) bool {
Expand Down Expand Up @@ -927,6 +927,13 @@ const (
FindAncestorQuit
)

func ToFindAncestorResult(b bool) FindAncestorResult {
if b {
return FindAncestorTrue
}
return FindAncestorFalse
}

// Walks up the parents of a node to find the ancestor that matches the callback
func FindAncestorOrQuit(node *Node, callback func(*Node) FindAncestorResult) *Node {
for node != nil {
Expand Down Expand Up @@ -1966,6 +1973,7 @@ func TryGetTextOfPropertyName(name *Node) (string, bool) {
return "", false
}

// True if node is of a JSDoc kind that may contain comment text.
func IsJSDocCommentContainingNode(node *Node) bool {
return node.Kind == KindJSDoc ||
node.Kind == KindJSDocText ||
Expand Down Expand Up @@ -2651,3 +2659,127 @@ func GetPragmaArgument(pragma *Pragma, name string) string {
}
return ""
}

func IsCheckJSEnabledForFile(sourceFile *SourceFile, compilerOptions *core.CompilerOptions) bool {
if sourceFile.CheckJsDirective != nil {
return sourceFile.CheckJsDirective.Enabled
}
return compilerOptions.CheckJs == core.TSTrue
}

func GetLeftmostAccessExpression(expr *Node) *Node {
for IsAccessExpression(expr) {
expr = expr.Expression()
}
return expr
}

func IsTypeOnlyImportDeclaration(node *Node) bool {
switch node.Kind {
case KindImportSpecifier:
return node.AsImportSpecifier().IsTypeOnly || node.Parent.Parent.AsImportClause().IsTypeOnly
case KindNamespaceImport:
return node.Parent.AsImportClause().IsTypeOnly
case KindImportClause:
return node.AsImportClause().IsTypeOnly
case KindImportEqualsDeclaration:
return node.AsImportEqualsDeclaration().IsTypeOnly
}
return false
}

func isTypeOnlyExportDeclaration(node *Node) bool {
switch node.Kind {
case KindExportSpecifier:
return node.AsExportSpecifier().IsTypeOnly || node.Parent.Parent.AsExportDeclaration().IsTypeOnly
case KindExportDeclaration:
d := node.AsExportDeclaration()
return d.IsTypeOnly && d.ModuleSpecifier != nil && d.ExportClause == nil
case KindNamespaceExport:
return node.Parent.AsExportDeclaration().IsTypeOnly
}
return false
}

func IsTypeOnlyImportOrExportDeclaration(node *Node) bool {
return IsTypeOnlyImportDeclaration(node) || isTypeOnlyExportDeclaration(node)
}

func GetSourceFileOfModule(module *Symbol) *SourceFile {
declaration := module.ValueDeclaration
if declaration == nil {
declaration = getNonAugmentationDeclaration(module)
}
return GetSourceFileOfNode(declaration)
}

func getNonAugmentationDeclaration(symbol *Symbol) *Node {
return core.Find(symbol.Declarations, func(d *Node) bool {
return !IsExternalModuleAugmentation(d) && !IsGlobalScopeAugmentation(d)
})
}

func IsExternalModuleAugmentation(node *Node) bool {
return IsAmbientModule(node) && IsModuleAugmentationExternal(node)
}

func GetClassLikeDeclarationOfSymbol(symbol *Symbol) *Node {
return core.Find(symbol.Declarations, IsClassLike)
}

func GetLanguageVariant(scriptKind core.ScriptKind) core.LanguageVariant {
switch scriptKind {
case core.ScriptKindTSX, core.ScriptKindJSX, core.ScriptKindJS, core.ScriptKindJSON:
// .tsx and .jsx files are treated as jsx language variant.
return core.LanguageVariantJSX
}
return core.LanguageVariantStandard
}

func IsCallLikeExpression(node *Node) bool {
switch node.Kind {
case KindJsxOpeningElement, KindJsxSelfClosingElement, KindCallExpression, KindNewExpression,
KindTaggedTemplateExpression, KindDecorator:
return true
}
return false
}

func IsCallLikeOrFunctionLikeExpression(node *Node) bool {
return IsCallLikeExpression(node) || IsFunctionExpressionOrArrowFunction(node)
}

func NodeHasKind(node *Node, kind Kind) bool {
if node == nil {
return false
}
return node.Kind == kind
}

func IsContextualKeyword(token Kind) bool {
return KindFirstContextualKeyword <= token && token <= KindLastContextualKeyword
}

func IsThisInTypeQuery(node *Node) bool {
if !IsThisIdentifier(node) {
return false
}
for IsQualifiedName(node.Parent) && node.Parent.AsQualifiedName().Left == node {
node = node.Parent
}
return node.Parent.Kind == KindTypeQuery
}

// Gets whether a bound `VariableDeclaration` or `VariableDeclarationList` is part of a `let` declaration.
func IsLet(node *Node) bool {
return GetCombinedNodeFlags(node)&NodeFlagsBlockScoped == NodeFlagsLet
}

func IsClassMemberModifier(token Kind) bool {
return IsParameterPropertyModifier(token) || token == KindStaticKeyword ||
token == KindOverrideKeyword || token == KindAccessorKeyword
}

func IsParameterPropertyModifier(kind Kind) bool {
return ModifierToFlag(kind)&ModifierFlagsParameterPropertyModifier != 0
}
13 changes: 9 additions & 4 deletions internal/astnav/tokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ func FindPrecedingTokenEx(sourceFile *ast.SourceFile, position int, startNode *a
// 1) `position` precedes `child`'s tokens or `child` has no tokens (ie: in a comment or whitespace preceding `child`):
// we need to find the last token in a previous child node or child tokens.
// 2) `position` is within the same span: we recurse on `child`.
start := getStartOfNode(foundChild, sourceFile, !excludeJSDoc /*includeJSDoc*/)
start := GetStartOfNode(foundChild, sourceFile, !excludeJSDoc /*includeJSDoc*/)
lookInPreviousChild := start >= position || // cursor in the leading trivia or preceding tokens
!isValidPrecedingNode(foundChild, sourceFile)
if lookInPreviousChild {
Expand Down Expand Up @@ -398,12 +398,12 @@ func FindPrecedingTokenEx(sourceFile *ast.SourceFile, position int, startNode *a
}

func isValidPrecedingNode(node *ast.Node, sourceFile *ast.SourceFile) bool {
start := getStartOfNode(node, sourceFile, false /*includeJSDoc*/)
start := GetStartOfNode(node, sourceFile, false /*includeJSDoc*/)
width := node.End() - start
return !(ast.IsWhitespaceOnlyJsxText(node) || width == 0)
}

func getStartOfNode(node *ast.Node, file *ast.SourceFile, includeJSDoc bool) int {
func GetStartOfNode(node *ast.Node, file *ast.SourceFile, includeJSDoc bool) int {
return scanner.GetTokenPosOfNode(node, file, includeJSDoc)
}

Expand Down Expand Up @@ -432,7 +432,7 @@ func findRightmostValidToken(endPos int, sourceFile *ast.SourceFile, containingN
hasChildren := false
test := func(node *ast.Node) bool {
if node.Flags&ast.NodeFlagsReparsed != 0 ||
node.End() > endPos || getStartOfNode(node, sourceFile, !excludeJSDoc /*includeJSDoc*/) >= position {
node.End() > endPos || GetStartOfNode(node, sourceFile, !excludeJSDoc /*includeJSDoc*/) >= position {
return false
}
rightmostVisitedNode = node
Expand Down Expand Up @@ -529,3 +529,8 @@ func findRightmostValidToken(endPos int, sourceFile *ast.SourceFile, containingN

return find(containingNode)
}

// !!!
func FindNextToken(previousToken *ast.Node, parent *ast.Node, file *ast.SourceFile) *ast.Node {
return nil
}
Loading